Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 62 additions & 1 deletion packages-private/vapor-e2e-test/__tests__/transition.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ const {
nextFrame,
timeout,
isVisible,
count,
html,
transitionStart,
waitForElement,
Expand All @@ -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')
})
Expand Down Expand Up @@ -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(
'<div class="v-enter-from v-enter-active">vapor compA</div>',
)
await nextFrame()
expect(await html(containerSelector)).toBe(
'<div class="v-enter-active v-enter-to">vapor compA</div>',
)
await transitionFinish()
expect(await html(containerSelector)).toBe(
'<div class="">vapor compA</div>',
)

// leave
await click(btnSelector)
await nextTick()
expect(await html(containerSelector)).toBe(
'<div class="v-leave-from v-leave-active">vapor compA</div>',
)
await nextFrame()
expect(await html(containerSelector)).toBe(
'<div class="v-leave-active v-leave-to">vapor compA</div>',
)
await transitionFinish()
expect(await html(containerSelector)).toBe('')

// enter again
await click(btnSelector)
// use the already resolved component
expect(await html(containerSelector)).toBe(
'<div class="v-enter-from v-enter-active">vapor compA</div>',
)
await nextFrame()
expect(await html(containerSelector)).toBe(
'<div class="v-enter-active v-enter-to">vapor compA</div>',
)
await transitionFinish()
expect(await html(containerSelector)).toBe(
'<div class="">vapor compA</div>',
)
})
})

describe('transition with v-show', () => {
test(
'named transition with v-show',
Expand Down
18 changes: 17 additions & 1 deletion packages-private/vapor-e2e-test/transition/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ import {
VaporTransition,
createIf,
template,
defineVaporAsyncComponent,
onUnmounted,
} from 'vue'
const show = ref(true)
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: [],
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -503,6 +508,17 @@ const click = () => {
</div>
<!-- mode end -->

<!-- async component -->
<div class="async">
<div id="container">
<transition>
<AsyncComp v-if="!toggle"></AsyncComp>
</transition>
</div>
<button @click="toggle = !toggle">button</button>
</div>
<!-- async component end -->

<!-- with teleport -->
<div class="with-teleport">
<div class="target"></div>
Expand Down
15 changes: 11 additions & 4 deletions packages/runtime-vapor/src/apiDefineAsyncComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T extends VaporComponent>(
Expand Down Expand Up @@ -109,7 +110,8 @@ export function defineVaporAsyncComponent<T extends VaporComponent>(
},

setup() {
const instance = currentInstance as VaporComponentInstance
const instance = currentInstance as VaporComponentInstance &
TransitionOptions
markAsyncBoundary(instance)

const frag =
Expand Down Expand Up @@ -166,6 +168,8 @@ export function defineVaporAsyncComponent<T extends VaporComponent>(
} else if (loadingComponent && !delayed.value) {
render = () => createComponent(loadingComponent)
}

if (instance.$transition) frag!.$transition = instance.$transition
frag!.update(render)
})

Expand All @@ -176,10 +180,10 @@ export function defineVaporAsyncComponent<T extends VaporComponent>(

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,
Expand All @@ -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)
Expand Down
18 changes: 13 additions & 5 deletions packages/runtime-vapor/src/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<unknown>): val is Block {
Expand Down
36 changes: 20 additions & 16 deletions packages/runtime-vapor/src/components/Transition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
checkTransitionMode,
currentInstance,
getComponentName,
isAsyncWrapper,
isTemplateNode,
leaveCbKey,
queuePostFlushCb,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -141,7 +142,7 @@ export const VaporTransition: FunctionalVaporComponent = /*@__PURE__*/ decorate(
)

const getTransitionHooksContext = (
key: String,
key: string,
props: TransitionProps,
state: TransitionState,
instance: GenericComponentInstance,
Expand Down Expand Up @@ -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)) {
Expand All @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -266,7 +270,7 @@ export function applyTransitionLeaveHooks(
state,
instance,
)
setTransitionHooks(leavingBlock, leavingHooks)
leavingBlock.$transition = leavingHooks

const { mode } = props
if (mode === 'out-in') {
Expand Down Expand Up @@ -300,25 +304,25 @@ export function applyTransitionLeaveHooks(
}
}

const transitionBlockCache = new WeakMap<Block, TransitionBlock>()
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) {
Expand Down Expand Up @@ -369,7 +373,7 @@ export function setTransitionHooksOnFragment(
}

export function setTransitionHooks(
block: TransitionBlock | VaporComponentInstance,
block: TransitionBlock,
hooks: VaporTransitionHooks,
): void {
if (isVaporComponent(block)) {
Expand Down
Loading