Przeglądaj źródła

fix(runtime-vapor): TransitionGroup with v-if + v-for not applying transition hooks (#14571)

close #14564
close #14569
edison 1 miesiąc temu
rodzic
commit
508edbe3c2

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

@@ -92,6 +92,60 @@ describe('vapor transition-group', () => {
     E2E_TIMEOUT,
   )
 
+  test(
+    'if + for enter',
+    async () => {
+      const btnSelector = '.if-for-enter > button.toggle'
+      const addBtnSelector = '.if-for-enter > button.add'
+      const containerSelector = '.if-for-enter > 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 () => {

+ 20 - 0
packages-private/vapor-e2e-test/transition-group/cases/vapor-transition-group/if-for-enter.vue

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

+ 2 - 0
packages/runtime-vapor/src/block.ts

@@ -29,6 +29,8 @@ export interface VaporTransitionHooks extends TransitionHooks {
   instance: VaporComponentInstance
   // mark transition hooks as disabled
   disabled?: boolean
+  // TransitionGroup sets this to handle applying hooks to list children
+  applyGroup?: (block: Block, hooks: VaporTransitionHooks) => void
 }
 
 export interface TransitionOptions {

+ 7 - 0
packages/runtime-vapor/src/components/Transition.ts

@@ -34,6 +34,7 @@ import { isArray } from '@vue/shared'
 import { renderEffect } from '../renderEffect'
 import {
   type DynamicFragment,
+  ForFragment,
   type VaporFragment,
   isFragment,
 } from '../fragment'
@@ -206,6 +207,12 @@ function applyTransitionHooksImpl(
     }
   }
 
+  // delegate to TransitionGroup's apply logic for list children
+  if (hooks.applyGroup && block instanceof ForFragment) {
+    hooks.applyGroup(block, hooks)
+    return hooks
+  }
+
   const fragments: VaporFragment[] = []
   const child = findTransitionBlock(block, frag => fragments.push(frag))
   if (!child) {

+ 26 - 22
packages/runtime-vapor/src/components/TransitionGroup.ts

@@ -80,12 +80,11 @@ const VaporTransitionGroupImpl = defineVaporComponent({
     })
 
     let prevChildren: TransitionBlock[]
-    let children: TransitionBlock[]
     const slottedBlock = slots.default && slots.default()
 
     onBeforeUpdate(() => {
       prevChildren = []
-      children = getTransitionBlocks(slottedBlock)
+      const children = getTransitionBlocks(slottedBlock)
       if (children) {
         for (let i = 0; i < children.length; i++) {
           const child = children[i]
@@ -141,31 +140,13 @@ const VaporTransitionGroupImpl = defineVaporComponent({
       prevChildren = []
     })
 
-    // store props and state on fragment for reusing during insert new items
-    setTransitionHooksOnFragment(slottedBlock, {
+    applyGroupTransitionHooks(slottedBlock, {
       props: propsProxy,
       state,
       instance,
+      applyGroup: applyGroupTransitionHooks,
     } as VaporTransitionHooks)
 
-    children = getTransitionBlocks(slottedBlock)
-    for (let i = 0; i < children.length; i++) {
-      const child = children[i]
-      if (isValidTransitionBlock(child)) {
-        if (child.$key != null) {
-          const hooks = resolveTransitionHooks(
-            child,
-            propsProxy,
-            state,
-            instance!,
-          )
-          setTransitionHooks(child, hooks)
-        } else if (__DEV__) {
-          warn(`<transition-group> children must be keyed`)
-        }
-      }
-    }
-
     const tag = props.tag
     if (tag) {
       const container = createElement(tag)
@@ -183,6 +164,29 @@ export const VaporTransitionGroup: DefineVaporComponent<
   TransitionGroupProps
 > = /*@__PURE__*/ decorate(VaporTransitionGroupImpl)
 
+function applyGroupTransitionHooks(
+  block: Block,
+  hooks: VaporTransitionHooks,
+): void {
+  // propagate hooks to inner fragments for reusing during insert new items
+  setTransitionHooksOnFragment(block, hooks)
+  const { props, state, instance } = hooks
+  const children = getTransitionBlocks(block)
+  for (let i = 0; i < children.length; i++) {
+    const child = children[i]
+    if (isValidTransitionBlock(child)) {
+      if (child.$key != null) {
+        setTransitionHooks(
+          child,
+          resolveTransitionHooks(child, props, state, instance),
+        )
+      } else if (__DEV__) {
+        warn(`<transition-group> children must be keyed`)
+      }
+    }
+  }
+}
+
 function getTransitionBlocks(block: Block) {
   let children: TransitionBlock[] = []
   if (block instanceof Node) {