Explorar o código

fix(runtime-vapor): track root slot updates in TransitionGroup

daiwei hai 3 semanas
pai
achega
f23b0b5b0c

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

@@ -984,6 +984,44 @@ describe('vapor transition-group', () => {
     E2E_TIMEOUT,
   )
 
+  test(
+    'root slot component move',
+    async () => {
+      const btnSelector = '.root-slot-component-move > button'
+      const containerSelector = '.root-slot-component-move > div'
+
+      await expect
+        .element(css(containerSelector))
+        .toContainHTML(
+          `<div class="test">a</div>` +
+            `<div class="test">b</div>` +
+            `<div class="test">c</div>` +
+            `<!--for--><!--slot--><!--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--><!--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',

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

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

+ 5 - 0
packages-private/vapor-e2e-test/transition-group/components/RootSlot.vue

@@ -0,0 +1,5 @@
+<script setup vapor></script>
+
+<template>
+  <slot />
+</template>

+ 19 - 3
packages/runtime-vapor/src/components/TransitionGroup.ts

@@ -42,7 +42,12 @@ import {
 import { resolveDynamicProps } from '../componentProps'
 import { isForBlock, setForHydrationAnchorResolver } from '../apiCreateFor'
 import { createComment, createElement, createTextNode } from '../dom/node'
-import { DynamicFragment, type VaporFragment, isFragment } from '../fragment'
+import {
+  DynamicFragment,
+  SlotFragment,
+  type VaporFragment,
+  isFragment,
+} from '../fragment'
 import {
   type DefineVaporComponent,
   defineVaporComponent,
@@ -394,8 +399,19 @@ function getTransitionBlocks(
   if (block instanceof Element) {
     children.push(block)
   } else if (isVaporComponent(block)) {
-    if (onUpdateOwner) onUpdateOwner(block)
-    const blocks = getTransitionBlocks(block.block, onFragment, onUpdateOwner)
+    // A normal component child can move when parent-driven props update its
+    // root layout without re-running the surrounding v-for fragment.
+    // When the component root is a slot, the TransitionGroup children are the
+    // slotted blocks, so track the SlotFragment instead of the component.
+    const isRootSlot = block.block instanceof SlotFragment
+    if (onUpdateOwner && !isRootSlot) onUpdateOwner(block)
+    const blocks = getTransitionBlocks(
+      block.block,
+      onFragment,
+      // Only a root slot exposes nested blocks as TransitionGroup children.
+      // Other component internals should not trigger group move bookkeeping.
+      isRootSlot ? onUpdateOwner : undefined,
+    )
     inheritKey(blocks, block.$key)
     children.push(...blocks)
   } else if (isArray(block)) {