nextFrame,
timeout,
isVisible,
- count,
html,
transitionStart,
waitForElement,
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')
})
)
})
+ 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',
VaporTransition,
createIf,
template,
+ defineVaporAsyncComponent,
onUnmounted,
} from 'vue'
const show = 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: [],
viewInOut.value = viewInOut.value === SimpleOne ? Two : SimpleOne
}
+const AsyncComp = defineVaporAsyncComponent(() => {
+ return new Promise(resolve => setTimeout(() => resolve(VaporCompA), 50))
+})
+
const TrueBranch = defineVaporComponent({
name: 'TrueBranch',
setup() {
</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>
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>(
},
setup() {
- const instance = currentInstance as VaporComponentInstance
+ const instance = currentInstance as VaporComponentInstance &
+ TransitionOptions
markAsyncBoundary(instance)
const frag =
} else if (loadingComponent && !delayed.value) {
render = () => createComponent(loadingComponent)
}
+
+ if (instance.$transition) frag!.$transition = instance.$transition
frag!.update(render)
})
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,
appContext,
)
+ // set transition hooks
+ if ($transition) setTransitionHooks(instance, $transition)
+
// set ref
// @ts-expect-error
frag && frag.setRef && frag.setRef(instance)
$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 {
checkTransitionMode,
currentInstance,
getComponentName,
+ isAsyncWrapper,
isTemplateNode,
leaveCbKey,
queuePostFlushCb,
if (child) {
// replace existing transition hooks
child.$transition!.props = resolvedProps
- applyTransitionHooks(child, child.$transition!)
+ applyTransitionHooks(child, child.$transition!, undefined, true)
}
}
} else {
)
const getTransitionHooksContext = (
- key: String,
+ key: string,
props: TransitionProps,
state: TransitionState,
instance: GenericComponentInstance,
block: Block,
hooks: VaporTransitionHooks,
fallthroughAttrs: boolean = true,
+ isResolved: boolean = false,
): VaporTransitionHooks {
// filter out comment nodes
if (isArray(block)) {
}
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)
hooks => (resolvedHooks = hooks as VaporTransitionHooks),
)
resolvedHooks.delayedLeave = delayedLeave
- setTransitionHooks(child, resolvedHooks)
+ child.$transition = resolvedHooks
if (isFrag) setTransitionHooksOnFragment(block, resolvedHooks)
// fallthrough attrs
state,
instance,
)
- setTransitionHooks(leavingBlock, leavingHooks)
+ leavingBlock.$transition = leavingHooks
const { mode } = props
if (mode === 'out-in') {
}
}
-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) {
}
export function setTransitionHooks(
- block: TransitionBlock | VaporComponentInstance,
+ block: TransitionBlock,
hooks: VaporTransitionHooks,
): void {
if (isVaporComponent(block)) {