Explorar o código

fix(transition): move kept-alive node before v-show transition leave finishes

daiwei hai 4 meses
pai
achega
e393552feb

+ 12 - 3
packages-private/vapor-e2e-test/__tests__/transition.spec.ts

@@ -1006,14 +1006,23 @@ describe('vapor transition', () => {
         // expect v-show element's display to be none
         await click(btnToggle)
         await nextTick()
-        // different from vdom behavior, the leaving element is removed immediately
-        // vdom's behavior is hidden but still in DOM until leave transition finishes
-        await waitForInnerHTML(containerSelector, `<h2>This is page2</h2>`)
+        await waitForInnerHTML(
+          containerSelector,
+          `<div class="test-leave-from test-leave-active" style="display: none;"><h2>I shouldn't show </h2></div>` +
+            `<h2>This is page2</h2>`,
+        )
 
         // switch back to page1
         // expect v-show element's display to be none
         await click(btnToggle)
         await nextTick()
+        await waitForInnerHTML(
+          containerSelector,
+          `<div class="test-enter-from test-enter-active" style="display: none;"><h2>I shouldn't show </h2></div>` +
+            `<h2>This is page1</h2>` +
+            `<button id="changeShowBtn">false</button>`,
+        )
+
         await waitForInnerHTML(
           containerSelector,
           `<div class="" style="display: none;"><h2>I shouldn't show </h2></div>` +

+ 11 - 2
packages/runtime-core/src/renderer.ts

@@ -2822,6 +2822,7 @@ export function performTransitionLeave(
   transition: TransitionHooks,
   remove: () => void,
   isElement: boolean = true,
+  force: boolean = false,
 ): void {
   const performRemove = () => {
     remove()
@@ -2830,9 +2831,17 @@ export function performTransitionLeave(
     }
   }
 
-  if (isElement && transition && !transition.persisted) {
+  if (force || (isElement && transition && !transition.persisted)) {
     const { leave, delayLeave } = transition
-    const performLeave = () => leave(el, performRemove)
+    const performLeave = () => {
+      // #13153 move kept-alive node before v-show transition leave finishes
+      // it needs to call the leaving callback to ensure element's `display`
+      // is `none`
+      if (el!._isLeaving && force) {
+        el![leaveCbKey](true /* cancelled */)
+      }
+      leave(el, performRemove)
+    }
     if (delayLeave) {
       delayLeave(el, performRemove, performLeave)
     } else {

+ 94 - 15
packages/runtime-vapor/src/block.ts

@@ -78,8 +78,6 @@ export function insert(
   block: Block,
   parent: ParentNode & { $fc?: Node | null },
   anchor: Node | null | 0 = null, // 0 means prepend
-  moveType: MoveType = MoveType.ENTER,
-  parentComponent?: VaporComponentInstance,
   parentSuspense?: any, // TODO Suspense
 ): void {
   anchor = anchor === 0 ? parent.$fc || _child(parent) : anchor
@@ -91,49 +89,130 @@ export function insert(
         (block as TransitionBlock).$transition &&
         !(block as TransitionBlock).$transition!.disabled
       ) {
-        const action =
-          moveType === MoveType.LEAVE
-            ? performTransitionLeave
-            : performTransitionEnter
+        performTransitionEnter(
+          block,
+          (block as TransitionBlock).$transition as TransitionHooks,
+          () => parent.insertBefore(block, anchor as Node),
+          parentSuspense,
+        )
+      } else {
+        parent.insertBefore(block, anchor)
+      }
+    }
+  } else if (isVaporComponent(block)) {
+    if (block.isMounted && !block.isDeactivated) {
+      insert(block.block!, parent, anchor)
+    } else {
+      mountComponent(block, parent, anchor)
+    }
+  } else if (isArray(block)) {
+    for (const b of block) {
+      insert(b, parent, anchor)
+    }
+  } else {
+    if (block.anchor) {
+      insert(block.anchor, parent, anchor)
+      anchor = block.anchor
+    }
+    // fragment
+    if (block.insert) {
+      block.insert(parent, anchor, (block as TransitionBlock).$transition)
+    } else {
+      insert(block.nodes, parent, anchor, parentSuspense)
+    }
+  }
+}
 
-        action(
+export function move(
+  block: Block,
+  parent: ParentNode & { $fc?: Node | null },
+  anchor: Node | null | 0 = null, // 0 means prepend
+  moveType: MoveType = MoveType.LEAVE,
+  parentComponent?: VaporComponentInstance,
+  parentSuspense?: any, // TODO Suspense
+): void {
+  anchor = anchor === 0 ? parent.$fc || _child(parent) : anchor
+  if (block instanceof Node) {
+    // only apply transition on Element nodes
+    if (
+      block instanceof Element &&
+      (block as TransitionBlock).$transition &&
+      !(block as TransitionBlock).$transition!.disabled &&
+      moveType !== MoveType.REORDER
+    ) {
+      if (moveType === MoveType.ENTER) {
+        performTransitionEnter(
+          block,
+          (block as TransitionBlock).$transition as TransitionHooks,
+          () => parent.insertBefore(block, anchor as Node),
+          parentSuspense,
+          true,
+        )
+      } else {
+        performTransitionLeave(
           block,
           (block as TransitionBlock).$transition as TransitionHooks,
           () => {
             // if the component is unmounted after leave finish, remove the block
             // to avoid retaining a detached node.
-            if (moveType === MoveType.LEAVE && parentComponent!.isUnmounted) {
+            if (
+              moveType === MoveType.LEAVE &&
+              parentComponent &&
+              parentComponent.isUnmounted
+            ) {
               block.remove()
             } else {
               parent.insertBefore(block, anchor as Node)
             }
           },
           parentSuspense,
+          true,
         )
-      } else {
-        parent.insertBefore(block, anchor)
       }
+    } else {
+      parent.insertBefore(block, anchor)
     }
   } else if (isVaporComponent(block)) {
-    if (block.isMounted && !block.isDeactivated) {
-      insert(block.block!, parent, anchor)
+    if (block.isMounted) {
+      move(
+        block.block!,
+        parent,
+        anchor,
+        moveType,
+        parentComponent,
+        parentSuspense,
+      )
     } else {
       mountComponent(block, parent, anchor)
     }
   } else if (isArray(block)) {
     for (const b of block) {
-      insert(b, parent, anchor)
+      move(b, parent, anchor, moveType, parentComponent, parentSuspense)
     }
   } else {
     if (block.anchor) {
-      insert(block.anchor, parent, anchor)
+      move(
+        block.anchor,
+        parent,
+        anchor,
+        moveType,
+        parentComponent,
+        parentSuspense,
+      )
       anchor = block.anchor
     }
     // fragment
     if (block.insert) {
       block.insert(parent, anchor, (block as TransitionBlock).$transition)
     } else {
-      insert(block.nodes, parent, anchor, parentSuspense)
+      move(
+        block.nodes,
+        parent,
+        anchor,
+        moveType,
+        parentComponent,
+        parentSuspense,
+      )
     }
   }
 }

+ 3 - 3
packages/runtime-vapor/src/components/KeepAlive.ts

@@ -19,7 +19,7 @@ import {
   warn,
   watch,
 } from '@vue/runtime-dom'
-import { type Block, insert, remove } from '../block'
+import { type Block, move, remove } from '../block'
 import {
   type ObjectVaporComponent,
   type VaporComponent,
@@ -348,7 +348,7 @@ export function activate(
   parentNode: ParentNode,
   anchor?: Node | null | 0,
 ): void {
-  insert(instance.block, parentNode, anchor)
+  move(instance.block, parentNode, anchor, MoveType.ENTER, instance)
 
   queuePostFlushCb(() => {
     instance.isDeactivated = false
@@ -364,7 +364,7 @@ export function deactivate(
   instance: VaporComponentInstance,
   container: ParentNode,
 ): void {
-  insert(instance.block, container, null, MoveType.LEAVE, instance)
+  move(instance.block, container, null, MoveType.LEAVE, instance)
 
   queuePostFlushCb(() => {
     if (instance.da) invokeArrayFns(instance.da)

+ 21 - 4
packages/runtime-vapor/src/fragment.ts

@@ -78,6 +78,7 @@ export class DynamicFragment extends VaporFragment {
   anchor!: Node
   scope: EffectScope | undefined
   current?: BlockFn
+  pending?: { render?: BlockFn; key: any }
   fallback?: BlockFn
   anchorLabel?: string
 
@@ -118,13 +119,22 @@ export class DynamicFragment extends VaporFragment {
       if (isHydrating) this.hydrate(true)
       return
     }
+
+    const transition = this.$transition
+    // currently leaving: defer mounting the next branch until
+    // the leave finishes.
+    if (transition && transition.state.isLeaving) {
+      this.current = key
+      this.pending = { render, key }
+      return
+    }
+
     const prevKey = this.current
     this.current = key
 
     const instance = currentInstance
     const prevSub = setActiveSub()
     const parent = isHydrating ? null : this.anchor.parentNode
-    const transition = this.$transition
     // teardown previous branch
     if (this.scope) {
       let preserveScope = false
@@ -142,9 +152,16 @@ export class DynamicFragment extends VaporFragment {
       }
       const mode = transition && transition.mode
       if (mode) {
-        applyTransitionLeaveHooks(this.nodes, transition, () =>
-          this.renderBranch(render, transition, parent, instance),
-        )
+        applyTransitionLeaveHooks(this.nodes, transition, () => {
+          const pending = this.pending
+          if (pending) {
+            this.pending = undefined
+            this.current = pending.key
+            this.renderBranch(pending.render, transition, parent, instance)
+          } else {
+            this.renderBranch(render, transition, parent, instance)
+          }
+        })
         parent && remove(this.nodes, parent)
         if (mode === 'out-in') {
           setActiveSub(prevSub)