Browse Source

fix(transition-group): handle v-if dynamic slots (#14628)

Jack 4 weeks ago
parent
commit
9ac929a481

+ 53 - 0
packages-private/vapor-e2e-test/__tests__/transition-group.spec.ts

@@ -265,6 +265,59 @@ describe('vapor transition-group', () => {
     E2E_TIMEOUT,
   )
 
+  test(
+    'dynamic slot with v-if',
+    async () => {
+      const btnSelector = '.dynamic-slot-with-v-if > button.toggle'
+      const addBtnSelector = '.dynamic-slot-with-v-if > button.add'
+      const containerSelector = '.dynamic-slot-with-v-if > div'
+      expect(await html(containerSelector)).toBe(`<ul></ul>`)
+
+      expect(
+        (await transitionStart(btnSelector, containerSelector)).innerHTML,
+      ).toBe(
+        `<ul>` +
+          `<li class="test v-enter-from v-enter-active">0</li>` +
+          `<li class="test v-enter-from v-enter-active">1</li>` +
+          `</ul>`,
+      )
+
+      await waitForInnerHTML(
+        containerSelector,
+        `<ul>` +
+          `<li class="test v-enter-active v-enter-to">0</li>` +
+          `<li class="test v-enter-active v-enter-to">1</li>` +
+          `</ul>`,
+      )
+
+      await waitForInnerHTML(
+        containerSelector,
+        `<ul><li class="test">0</li><li class="test">1</li></ul>`,
+      )
+
+      // add a new item
+      expect(
+        (await transitionStart(addBtnSelector, containerSelector)).innerHTML,
+      ).toBe(
+        `<ul>` +
+          `<li class="test">0</li>` +
+          `<li class="test">1</li>` +
+          `<li class="test v-enter-from v-enter-active">2</li>` +
+          `</ul>`,
+      )
+
+      await waitForInnerHTML(
+        containerSelector,
+        `<ul>` +
+          `<li class="test">0</li>` +
+          `<li class="test">1</li>` +
+          `<li class="test">2</li>` +
+          `</ul>`,
+      )
+    },
+    E2E_TIMEOUT,
+  )
+
   test(
     'leave',
     async () => {

+ 21 - 0
packages-private/vapor-e2e-test/transition-group/cases/vapor-transition-group/dynamic-slot-with-v-if.vue

@@ -0,0 +1,21 @@
+<script setup vapor>
+import { ref } from 'vue'
+
+const show = ref(false)
+const list = ref([0, 1])
+</script>
+
+<template>
+  <div class="dynamic-slot-with-v-if">
+    <button class="toggle" @click="show = !show">toggle button</button>
+    <button class="add" @click="list.push(list.length)">add button</button>
+
+    <div>
+      <TransitionGroup tag="ul">
+        <template v-if="show" #default>
+          <li class="test" v-for="value in list" :key="value">{{ value }}</li>
+        </template>
+      </TransitionGroup>
+    </div>
+  </div>
+</template>

+ 23 - 17
packages/runtime-vapor/src/components/TransitionGroup.ts

@@ -17,7 +17,12 @@ import {
   warn,
 } from '@vue/runtime-dom'
 import { extend, isArray } from '@vue/shared'
-import { type Block, type TransitionBlock, insert } from '../block'
+import {
+  type Block,
+  type BlockFn,
+  type TransitionBlock,
+  insert,
+} from '../block'
 import { renderEffect } from '../renderEffect'
 import {
   type ResolvedTransitionBlock,
@@ -140,29 +145,30 @@ const VaporTransitionGroupImpl = defineVaporComponent({
 
     const frag = new DynamicFragment('transition-group')
     let currentTag: string | undefined
+    let currentSlot: BlockFn | undefined
     let isMounted = false
+
     renderEffect(() => {
       const tag = props.tag
-      // tag is not changed, do nothing
-      if (isMounted && tag === currentTag) return
+      const slot = slots.default
+      // if the tag and slot are the same as previous render, no need to update.
+      if (isMounted && tag === currentTag && slot === currentSlot) return
 
+      const container = tag ? createElement(tag) : undefined
       let block: Block = slottedBlock
-      frag.update(
-        () => {
-          block = (slots.default && slots.default()) || []
-          applyGroupTransitionHooks(block, propsProxy, state, instance)
-          if (tag) {
-            const container = createElement(tag)
-            insert(block, container)
-            return container
-          }
-          return block
-        },
-        // Avoid `undefined` falling back to the render function as the key.
-        tag ?? null,
-      )
+      frag.update(() => {
+        block = (slot && slot()) || []
+        applyGroupTransitionHooks(block, propsProxy, state, instance)
+        if (container) {
+          insert(block, container)
+          return container
+        }
+        return block
+      })
       slottedBlock = block
+
       currentTag = tag
+      currentSlot = slot
       isMounted = true
     })
     return frag