Просмотр исходного кода

fix(transition-group): support vdom children with comment roots

daiwei 1 месяц назад
Родитель
Сommit
9830c4a43c

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

@@ -746,6 +746,52 @@ describe('vapor transition-group', () => {
   )
 
   describe('interop', () => {
+    test(
+      'avoid set transition hooks for comment node',
+      async () => {
+        const btnSelector =
+          '.avoid-set-transition-hooks-for-comment-node > button'
+        const containerSelector =
+          '.avoid-set-transition-hooks-for-comment-node > div'
+
+        expect(await html(containerSelector)).toBe(`<!--v-if-->`)
+
+        expect(
+          (await transitionStart(btnSelector, containerSelector)).innerHTML,
+        ).toBe(
+          `<div class="test test-enter-from test-enter-active">a</div>` +
+            `<div class="test test-enter-from test-enter-active">b</div>` +
+            `<div class="test test-enter-from test-enter-active">c</div>` +
+            `<!--v-if-->`,
+        )
+
+        await waitForInnerHTML(
+          containerSelector,
+          `<div class="test">a</div>` +
+            `<div class="test">b</div>` +
+            `<div class="test">c</div>` +
+            `<!--v-if-->`,
+        )
+
+        await waitForInnerHTML(
+          containerSelector,
+          `<div class="test">a</div>` +
+            `<div class="test">b</div>` +
+            `<div class="test">c</div>` +
+            `<div class="test test-enter-active test-enter-to">child</div>`,
+        )
+
+        await waitForInnerHTML(
+          containerSelector,
+          `<div class="test">a</div>` +
+            `<div class="test">b</div>` +
+            `<div class="test">c</div>` +
+            `<div class="test">child</div>`,
+        )
+      },
+      E2E_TIMEOUT,
+    )
+
     test('unkeyed vdom component update', async () => {
       const btnSelector = '.unkeyed-vdom-component-update > button'
       const containerSelector = '.unkeyed-vdom-component-update > div'

+ 26 - 0
packages-private/vapor-e2e-test/transition-group/cases/interop/avoid-set-transition-hooks-for-comment-node.vue

@@ -0,0 +1,26 @@
+<script setup vapor lang="ts">
+import { ref } from 'vue'
+import VdomCommentToggle from '../../components/VdomCommentToggle.vue'
+
+const items = ref<string[]>([])
+const show = ref(false)
+
+const click = () => {
+  items.value = ['a', 'b', 'c']
+  setTimeout(() => {
+    show.value = true
+  }, 100)
+}
+</script>
+
+<template>
+  <div class="avoid-set-transition-hooks-for-comment-node">
+    <button @click="click">button</button>
+    <div>
+      <transition-group name="test">
+        <div v-for="item in items" :key="item" class="test">{{ item }}</div>
+        <VdomCommentToggle key="child" :show="show" />
+      </transition-group>
+    </div>
+  </div>
+</template>

+ 16 - 0
packages-private/vapor-e2e-test/transition-group/components/VdomCommentToggle.vue

@@ -0,0 +1,16 @@
+<script lang="ts">
+import { createCommentVNode, defineComponent, h } from 'vue'
+
+export default defineComponent({
+  name: 'VdomCommentToggle',
+  props: {
+    show: Boolean,
+  },
+  setup(props) {
+    return () =>
+      props.show
+        ? h('div', { class: 'test' }, 'child')
+        : createCommentVNode('v-if', true)
+  },
+})
+</script>

+ 30 - 12
packages/runtime-vapor/src/components/TransitionGroup.ts

@@ -37,6 +37,7 @@ import {
   type DefineVaporComponent,
   defineVaporComponent,
 } from '../apiDefineComponent'
+import { isInteropEnabled } from '../vdomInteropState'
 
 const positionMap = new WeakMap<TransitionBlock, DOMRect>()
 const newPositionMap = new WeakMap<TransitionBlock, DOMRect>()
@@ -84,16 +85,17 @@ const VaporTransitionGroupImpl = defineVaporComponent({
       const children = getTransitionBlocks(slottedBlock)
       for (let i = 0; i < children.length; i++) {
         const child = children[i]
-        if (isValidTransitionBlock(child) && child.$transition) {
+        const el =
+          isValidTransitionBlock(child) && child.$transition
+            ? getTransitionElement(child)
+            : undefined
+        if (el) {
           prevChildren.push(child)
           // disabled transition during enter, so the children will be
           // inserted into the correct position immediately. this prevents
           // `recordPosition` from getting incorrect positions in `onUpdated`
           child.$transition!.disabled = true
-          positionMap.set(
-            child,
-            getTransitionElement(child).getBoundingClientRect(),
-          )
+          positionMap.set(child, el.getBoundingClientRect())
         }
       }
     })
@@ -229,7 +231,7 @@ function getTransitionBlocks(
       children.push(...blocks)
     }
   } else if (isFragment(block)) {
-    if (block.vnode) {
+    if (isInteropEnabled && block.vnode) {
       // vdom component
       children.push(block)
     } else {
@@ -246,25 +248,41 @@ function getTransitionBlocks(
 function isValidTransitionBlock(
   block: Block,
 ): block is ResolvedTransitionBlock {
-  return !!(block instanceof Element || (isFragment(block) && block.insert))
+  return !!(block instanceof Element || (isFragment(block) && block.vnode))
 }
 
-function getTransitionElement(c: ResolvedTransitionBlock): Element {
-  return (isFragment(c) ? (c.nodes as Element) : c) as Element
+function getTransitionElement(
+  block: ResolvedTransitionBlock,
+): Element | undefined {
+  if (block instanceof Element) return block
+
+  // vdom interop
+  if (
+    isInteropEnabled &&
+    isFragment(block) &&
+    block.vnode &&
+    !isArray(block.nodes) &&
+    (block.nodes instanceof Element || isFragment(block.nodes))
+  ) {
+    return getTransitionElement(block.nodes)
+  }
 }
 
 function recordPosition(c: ResolvedTransitionBlock) {
-  newPositionMap.set(c, getTransitionElement(c).getBoundingClientRect())
+  const el = getTransitionElement(c)
+  if (el) newPositionMap.set(c, el.getBoundingClientRect())
 }
 
 function applyTranslation(
   c: ResolvedTransitionBlock,
 ): ResolvedTransitionBlock | undefined {
+  const el = getTransitionElement(c)
   if (
+    el &&
     baseApplyTranslation(
       positionMap.get(c)!,
       newPositionMap.get(c)!,
-      getTransitionElement(c) as ElementWithTransition,
+      el as ElementWithTransition,
     )
   ) {
     return c
@@ -277,6 +295,6 @@ function getFirstConnectedChild(
   for (let i = 0; i < children.length; i++) {
     const child = children[i]
     const el = getTransitionElement(child)
-    if (el.isConnected) return el
+    if (el && el.isConnected) return el
   }
 }