diff --git a/packages-private/vapor-e2e-test/__tests__/transition.spec.ts b/packages-private/vapor-e2e-test/__tests__/transition.spec.ts
index 180b0157aaa..1dce93782e1 100644
--- a/packages-private/vapor-e2e-test/__tests__/transition.spec.ts
+++ b/packages-private/vapor-e2e-test/__tests__/transition.spec.ts
@@ -13,7 +13,6 @@ const {
nextFrame,
timeout,
isVisible,
- count,
html,
transitionStart,
waitForElement,
@@ -40,6 +39,9 @@ describe('vapor transition', () => {
beforeEach(async () => {
const baseUrl = `http://localhost:${port}/transition/`
+ await page().evaluateOnNewDocument(dur => {
+ ;(window as any).__TRANSITION_DURATION__ = dur
+ }, duration)
await page().goto(baseUrl)
await page().waitForSelector('#app')
})
@@ -972,6 +974,65 @@ describe('vapor transition', () => {
)
})
+ describe('transition with AsyncComponent', () => {
+ test('apply transition to inner component', async () => {
+ const btnSelector = '.async > button'
+ const containerSelector = '.async > div'
+
+ expect(await html(containerSelector)).toBe('')
+
+ // toggle
+ await click(btnSelector)
+ await nextTick()
+ // not yet resolved
+ expect(await html(containerSelector)).toBe('')
+
+ // wait resolving
+ await timeout(50)
+
+ // enter (resolved)
+ expect(await html(containerSelector)).toBe(
+ '
vapor compA
',
+ )
+ await nextFrame()
+ expect(await html(containerSelector)).toBe(
+ 'vapor compA
',
+ )
+ await transitionFinish()
+ expect(await html(containerSelector)).toBe(
+ 'vapor compA
',
+ )
+
+ // leave
+ await click(btnSelector)
+ await nextTick()
+ expect(await html(containerSelector)).toBe(
+ 'vapor compA
',
+ )
+ await nextFrame()
+ expect(await html(containerSelector)).toBe(
+ 'vapor compA
',
+ )
+ await transitionFinish()
+ expect(await html(containerSelector)).toBe('')
+
+ // enter again
+ await click(btnSelector)
+ // use the already resolved component
+ expect(await html(containerSelector)).toBe(
+ 'vapor compA
',
+ )
+ await nextFrame()
+ expect(await html(containerSelector)).toBe(
+ 'vapor compA
',
+ )
+ await transitionFinish()
+ expect(await html(containerSelector)).toBe(
+ 'vapor compA
',
+ )
+ })
+ })
+
describe('transition with v-show', () => {
test(
'named transition with v-show',
diff --git a/packages-private/vapor-e2e-test/transition/App.vue b/packages-private/vapor-e2e-test/transition/App.vue
index 8b07a5ac4be..e7227ded01e 100644
--- a/packages-private/vapor-e2e-test/transition/App.vue
+++ b/packages-private/vapor-e2e-test/transition/App.vue
@@ -7,6 +7,7 @@ import {
VaporTransition,
createIf,
template,
+ defineVaporAsyncComponent,
onUnmounted,
} from 'vue'
const show = ref(true)
@@ -14,7 +15,7 @@ const toggle = ref(true)
const count = ref(0)
const timeout = (fn, time) => setTimeout(fn, time)
-const duration = typeof process !== 'undefined' && process.env.CI ? 200 : 50
+const duration = window.__TRANSITION_DURATION__ || 50
let calls = {
basic: [],
@@ -94,6 +95,10 @@ function changeViewInOut() {
viewInOut.value = viewInOut.value === SimpleOne ? Two : SimpleOne
}
+const AsyncComp = defineVaporAsyncComponent(() => {
+ return new Promise(resolve => setTimeout(() => resolve(VaporCompA), 50))
+})
+
const TrueBranch = defineVaporComponent({
name: 'TrueBranch',
setup() {
@@ -503,6 +508,17 @@ const click = () => {
+
+
+
+
diff --git a/packages/runtime-vapor/src/apiDefineAsyncComponent.ts b/packages/runtime-vapor/src/apiDefineAsyncComponent.ts
index dd6143950e3..4cde9454f09 100644
--- a/packages/runtime-vapor/src/apiDefineAsyncComponent.ts
+++ b/packages/runtime-vapor/src/apiDefineAsyncComponent.ts
@@ -26,8 +26,9 @@ import {
removeFragmentNodes,
} from './dom/hydration'
import { invokeArrayFns } from '@vue/shared'
-import { insert, remove } from './block'
+import { type TransitionOptions, insert, remove } from './block'
import { parentNode } from './dom/node'
+import { setTransitionHooks } from './components/Transition'
/*@ __NO_SIDE_EFFECTS__ */
export function defineVaporAsyncComponent
(
@@ -109,7 +110,8 @@ export function defineVaporAsyncComponent(
},
setup() {
- const instance = currentInstance as VaporComponentInstance
+ const instance = currentInstance as VaporComponentInstance &
+ TransitionOptions
markAsyncBoundary(instance)
const frag =
@@ -166,6 +168,8 @@ export function defineVaporAsyncComponent(
} else if (loadingComponent && !delayed.value) {
render = () => createComponent(loadingComponent)
}
+
+ if (instance.$transition) frag!.$transition = instance.$transition
frag!.update(render)
})
@@ -176,10 +180,10 @@ export function defineVaporAsyncComponent(
function createInnerComp(
comp: VaporComponent,
- parent: VaporComponentInstance,
+ parent: VaporComponentInstance & TransitionOptions,
frag?: DynamicFragment,
): VaporComponentInstance {
- const { rawProps, rawSlots, isSingleRoot, appContext } = parent
+ const { rawProps, rawSlots, isSingleRoot, appContext, $transition } = parent
const instance = createComponent(
comp,
rawProps,
@@ -189,6 +193,9 @@ function createInnerComp(
appContext,
)
+ // set transition hooks
+ if ($transition) setTransitionHooks(instance, $transition)
+
// set ref
// @ts-expect-error
frag && frag.setRef && frag.setRef(instance)
diff --git a/packages/runtime-vapor/src/block.ts b/packages/runtime-vapor/src/block.ts
index 729ebee16fc..0f6d32ae8a5 100644
--- a/packages/runtime-vapor/src/block.ts
+++ b/packages/runtime-vapor/src/block.ts
@@ -36,12 +36,20 @@ export interface TransitionOptions {
$transition?: VaporTransitionHooks
}
-export type TransitionBlock =
- | (Node & TransitionOptions)
- | (VaporFragment & TransitionOptions)
- | (DynamicFragment & TransitionOptions)
+export type TransitionBlock = (
+ | Node
+ | VaporFragment
+ | DynamicFragment
+ | VaporComponentInstance
+) &
+ TransitionOptions
-export type Block = TransitionBlock | VaporComponentInstance | Block[]
+export type Block =
+ | Node
+ | VaporFragment
+ | DynamicFragment
+ | VaporComponentInstance
+ | Block[]
export type BlockFn = (...args: any[]) => Block
export function isBlock(val: NonNullable): val is Block {
diff --git a/packages/runtime-vapor/src/components/Transition.ts b/packages/runtime-vapor/src/components/Transition.ts
index 6b1580a9221..131154e2b6c 100644
--- a/packages/runtime-vapor/src/components/Transition.ts
+++ b/packages/runtime-vapor/src/components/Transition.ts
@@ -11,6 +11,7 @@ import {
checkTransitionMode,
currentInstance,
getComponentName,
+ isAsyncWrapper,
isTemplateNode,
leaveCbKey,
queuePostFlushCb,
@@ -92,7 +93,7 @@ export const VaporTransition: FunctionalVaporComponent = /*@__PURE__*/ decorate(
if (child) {
// replace existing transition hooks
child.$transition!.props = resolvedProps
- applyTransitionHooks(child, child.$transition!)
+ applyTransitionHooks(child, child.$transition!, undefined, true)
}
}
} else {
@@ -141,7 +142,7 @@ export const VaporTransition: FunctionalVaporComponent = /*@__PURE__*/ decorate(
)
const getTransitionHooksContext = (
- key: String,
+ key: string,
props: TransitionProps,
state: TransitionState,
instance: GenericComponentInstance,
@@ -210,6 +211,7 @@ export function applyTransitionHooks(
block: Block,
hooks: VaporTransitionHooks,
fallthroughAttrs: boolean = true,
+ isResolved: boolean = false,
): VaporTransitionHooks {
// filter out comment nodes
if (isArray(block)) {
@@ -222,7 +224,9 @@ export function applyTransitionHooks(
}
const isFrag = isFragment(block)
- const child = findTransitionBlock(block, isFrag)
+ const child = isResolved
+ ? (block as TransitionBlock)
+ : findTransitionBlock(block, isFrag)
if (!child) {
// set transition hooks on fragment for reusing during it's updating
if (isFrag) setTransitionHooksOnFragment(block, hooks)
@@ -238,7 +242,7 @@ export function applyTransitionHooks(
hooks => (resolvedHooks = hooks as VaporTransitionHooks),
)
resolvedHooks.delayedLeave = delayedLeave
- setTransitionHooks(child, resolvedHooks)
+ child.$transition = resolvedHooks
if (isFrag) setTransitionHooksOnFragment(block, resolvedHooks)
// fallthrough attrs
@@ -266,7 +270,7 @@ export function applyTransitionLeaveHooks(
state,
instance,
)
- setTransitionHooks(leavingBlock, leavingHooks)
+ leavingBlock.$transition = leavingHooks
const { mode } = props
if (mode === 'out-in') {
@@ -300,25 +304,25 @@ export function applyTransitionLeaveHooks(
}
}
-const transitionBlockCache = new WeakMap()
export function findTransitionBlock(
block: Block,
inFragment: boolean = false,
): TransitionBlock | undefined {
- if (transitionBlockCache.has(block)) {
- return transitionBlockCache.get(block)
- }
-
let child: TransitionBlock | undefined
if (block instanceof Node) {
// transition can only be applied on Element child
if (block instanceof Element) child = block
} else if (isVaporComponent(block)) {
- // stop searching if encountering nested Transition component
- if (getComponentName(block.type) === displayName) return undefined
- child = findTransitionBlock(block.block, inFragment)
- // use component id as key
- if (child && child.$key === undefined) child.$key = block.uid
+ // should save hooks on unresolved async wrapper, so that it can be applied after resolved
+ if (isAsyncWrapper(block) && !block.type.__asyncResolved) {
+ child = block
+ } else {
+ // stop searching if encountering nested Transition component
+ if (getComponentName(block.type) === displayName) return undefined
+ child = findTransitionBlock(block.block, inFragment)
+ // use component id as key
+ if (child && child.$key === undefined) child.$key = block.uid
+ }
} else if (isArray(block)) {
let hasFound = false
for (const c of block) {
@@ -369,7 +373,7 @@ export function setTransitionHooksOnFragment(
}
export function setTransitionHooks(
- block: TransitionBlock | VaporComponentInstance,
+ block: TransitionBlock,
hooks: VaporTransitionHooks,
): void {
if (isVaporComponent(block)) {