ソースを参照

fix(runtime-vapor): delegate async root slot wrappers to TransitionGroup

daiwei 3 週間 前
コミット
4c98166ef2

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

@@ -1022,6 +1022,50 @@ describe('vapor transition-group', () => {
     E2E_TIMEOUT,
   )
 
+  test(
+    'async root slot component move',
+    async () => {
+      const btnSelector = '.async-root-slot-component-move > button'
+      const containerSelector = '.async-root-slot-component-move > div'
+
+      await waitForInnerHTML(
+        containerSelector,
+        `<div class="test">a</div>` +
+          `<div class="test">b</div>` +
+          `<div class="test">c</div>`,
+      )
+      await expect
+        .element(css(containerSelector))
+        .toContainHTML(
+          `<div class="test">a</div>` +
+            `<div class="test">b</div>` +
+            `<div class="test">c</div>` +
+            `<!--for--><!--slot--><!--async component--><!--transition-group-->`,
+        )
+
+      click(btnSelector)
+      await nextTick()
+      await nextFrame()
+      expect(html(containerSelector)).toContain(
+        `<div class="test group-enter-from group-enter-active">d</div>` +
+          `<div class="test">b</div>` +
+          `<div class="test group-move" style="">a</div>` +
+          `<div class="test group-leave-from group-leave-active group-move" style="">c</div>` +
+          `<!--for--><!--slot--><!--async component--><!--transition-group-->`,
+      )
+
+      await transitionFinish()
+      await expect
+        .element(css(containerSelector))
+        .toContainHTML(
+          `<div class="test">d</div>` +
+            `<div class="test">b</div>` +
+            `<div class="test" style="">a</div>`,
+        )
+    },
+    E2E_TIMEOUT,
+  )
+
   describe('interop', () => {
     test(
       'avoid set transition hooks for comment node',

+ 21 - 0
packages-private/vapor-e2e-test/transition-group/cases/vapor-transition-group/async-root-slot-component-move.vue

@@ -0,0 +1,21 @@
+<script setup vapor>
+import { defineVaporAsyncComponent, ref } from 'vue'
+import RootSlot from '../../components/RootSlot.vue'
+
+const AsyncRootSlot = defineVaporAsyncComponent(() => Promise.resolve(RootSlot))
+const items = ref(['a', 'b', 'c'])
+const moveClick = () => (items.value = ['d', 'b', 'a'])
+</script>
+
+<template>
+  <div class="async-root-slot-component-move">
+    <button @click="moveClick">async root slot button</button>
+    <div>
+      <transition-group name="group">
+        <AsyncRootSlot key="async-root-slot">
+          <div v-for="item in items" :key="item" class="test">{{ item }}</div>
+        </AsyncRootSlot>
+      </transition-group>
+    </div>
+  </div>
+</template>

+ 10 - 2
packages/runtime-vapor/src/components/Transition.ts

@@ -40,6 +40,7 @@ import { renderEffect } from '../renderEffect'
 import {
   DynamicFragment,
   ForFragment,
+  SlotFragment,
   type VaporFragment,
   isFragment,
 } from '../fragment'
@@ -326,8 +327,15 @@ function applyResolvedTransitionHooks(
     }
   }
 
-  // delegate to TransitionGroup's apply logic for list children
-  if (hooks.applyGroup && block instanceof ForFragment) {
+  // Delegate list/root-slot wrappers back to TransitionGroup's apply logic.
+  // Other fragment shapes, such as keyed v-if branches, still need normal
+  // enter/leave hooks for their resolved single child.
+  if (
+    hooks.applyGroup &&
+    (block instanceof ForFragment ||
+      block instanceof SlotFragment ||
+      (isVaporComponent(block) && block.block instanceof SlotFragment))
+  ) {
     hooks.applyGroup(block, hooks.props, hooks.state, hooks.instance)
     return { hooks }
   }