diff --git a/packages-private/vapor-e2e-test/__tests__/transition.spec.ts b/packages-private/vapor-e2e-test/__tests__/transition.spec.ts index 2a9de7e73..0bfc30598 100644 --- a/packages-private/vapor-e2e-test/__tests__/transition.spec.ts +++ b/packages-private/vapor-e2e-test/__tests__/transition.spec.ts @@ -1061,23 +1061,103 @@ describe('vapor transition', () => { E2E_TIMEOUT, ) - test.todo( + test( 'transition on appear with v-show', async () => { const btnSelector = '.show-appear > button' const containerSelector = '.show-appear > div' const childSelector = `${containerSelector} > div` + + let calls = await page().evaluate(() => { + return (window as any).getCalls('showAppear') + }) + expect(calls).toStrictEqual(['beforeEnter', 'onEnter']) + + // appear + expect(await classList(childSelector)).contains('test-appear-active') + + await transitionFinish() + expect(await html(containerSelector)).toBe( + '
content
', + ) + calls = await page().evaluate(() => { + return (window as any).getCalls('showAppear') + }) + expect(calls).toStrictEqual(['beforeEnter', 'onEnter', 'afterEnter']) + + // leave + expect( + (await transitionStart(btnSelector, childSelector)).classNames, + ).toStrictEqual(['test', 'test-leave-from', 'test-leave-active']) + await nextFrame() + expect(await classList(childSelector)).toStrictEqual([ + 'test', + 'test-leave-active', + 'test-leave-to', + ]) + await transitionFinish() + expect(await isVisible(childSelector)).toBe(false) + + // enter + expect( + (await transitionStart(btnSelector, childSelector)).classNames, + ).toStrictEqual(['test', 'test-enter-from', 'test-enter-active']) + await nextFrame() + expect(await classList(childSelector)).toStrictEqual([ + 'test', + 'test-enter-active', + 'test-enter-to', + ]) + await transitionFinish() + expect(await html(containerSelector)).toBe( + '
content
', + ) }, E2E_TIMEOUT, ) - test.todo( + test( 'transition events should not call onEnter with v-show false', - async () => {}, + async () => { + const btnSelector = '.show-appear-not-enter > button' + const containerSelector = '.show-appear-not-enter > div' + const childSelector = `${containerSelector} > div` + + expect(await isVisible(childSelector)).toBe(false) + let calls = await page().evaluate(() => { + return (window as any).getCalls('notEnter') + }) + expect(calls).toStrictEqual([]) + + // enter + expect( + (await transitionStart(btnSelector, childSelector)).classNames, + ).toStrictEqual(['test', 'test-enter-from', 'test-enter-active']) + calls = await page().evaluate(() => { + return (window as any).getCalls('notEnter') + }) + expect(calls).toStrictEqual(['beforeEnter', 'onEnter']) + await nextFrame() + expect(await classList(childSelector)).toStrictEqual([ + 'test', + 'test-enter-active', + 'test-enter-to', + ]) + calls = await page().evaluate(() => { + return (window as any).getCalls('notEnter') + }) + expect(calls).not.contain('afterEnter') + await transitionFinish() + expect(await html(containerSelector)).toBe( + '
content
', + ) + calls = await page().evaluate(() => { + return (window as any).getCalls('notEnter') + }) + expect(calls).toStrictEqual(['beforeEnter', 'onEnter', 'afterEnter']) + }, E2E_TIMEOUT, ) - - test.todo('transition on appear with v-show', async () => {}, E2E_TIMEOUT) }) describe('explicit durations', () => { diff --git a/packages-private/vapor-e2e-test/transition/App.vue b/packages-private/vapor-e2e-test/transition/App.vue index a5b32a621..485509824 100644 --- a/packages-private/vapor-e2e-test/transition/App.vue +++ b/packages-private/vapor-e2e-test/transition/App.vue @@ -27,6 +27,7 @@ let calls = { show: [], showLeaveCancel: [], showAppear: [], + notEnter: [], } window.getCalls = key => calls[key] window.resetCalls = key => (calls[key] = []) @@ -398,6 +399,20 @@ function changeViewInOut() { +
+
+ +
content
+
+
+ +
diff --git a/packages/compiler-vapor/__tests__/transforms/TransformTransition.spec.ts b/packages/compiler-vapor/__tests__/transforms/TransformTransition.spec.ts index 7c86b9f37..bcb7b44ce 100644 --- a/packages/compiler-vapor/__tests__/transforms/TransformTransition.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/TransformTransition.spec.ts @@ -28,6 +28,13 @@ const compileWithElementTransform = makeCompile({ describe('compiler: transition', () => { test('basic', () => { + const { code } = compileWithElementTransform( + `

foo

`, + ) + expect(code).toMatchSnapshot() + }) + + test('v-show + appear', () => { const { code } = compileWithElementTransform( `

foo

`, ) diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/TransformTransition.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/TransformTransition.spec.ts.snap index a1de229f5..37ae42ed4 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/TransformTransition.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/TransformTransition.spec.ts.snap @@ -5,10 +5,7 @@ exports[`compiler: transition > basic 1`] = ` const t0 = _template("

foo

") export function render(_ctx) { - const n1 = _createComponent(_VaporTransition, { - appear: () => (""), - persisted: () => ("") - }, { + const n1 = _createComponent(_VaporTransition, { persisted: () => ("") }, { "default": () => { const n0 = t0() _applyVShow(n0, () => (_ctx.show)) @@ -72,6 +69,27 @@ export function render(_ctx) { }" `; +exports[`compiler: transition > v-show + appear 1`] = ` +"import { VaporTransition as _VaporTransition, applyVShow as _applyVShow, createComponent as _createComponent, template as _template } from 'vue'; +const t0 = _template("

foo

") + +export function render(_ctx) { + const lazyApplyVShowFn = [] + const n1 = _createComponent(_VaporTransition, { + appear: () => (""), + persisted: () => ("") + }, { + "default": () => { + const n0 = t0() + lazyApplyVShowFn.push(() => _applyVShow(n0, () => (_ctx.show))) + return n0 + } + }, true) + lazyApplyVShowFn.forEach(fn => fn()) + return n1 +}" +`; + exports[`compiler: transition > work with dynamic keyed children 1`] = ` "import { VaporTransition as _VaporTransition, createKeyedFragment as _createKeyedFragment, createComponent as _createComponent, template as _template } from 'vue'; const t0 = _template("

foo

") diff --git a/packages/compiler-vapor/src/generators/block.ts b/packages/compiler-vapor/src/generators/block.ts index 654816e09..e7f0610ac 100644 --- a/packages/compiler-vapor/src/generators/block.ts +++ b/packages/compiler-vapor/src/generators/block.ts @@ -44,6 +44,10 @@ export function genBlockContent( const { dynamic, effect, operation, returns, key } = block const resetBlock = context.enterBlock(block) + if (block.hasLazyApplyVShow) { + push(NEWLINE, `const lazyApplyVShowFn = []`) + } + if (root) { genResolveAssets('component', 'resolveComponent') genResolveAssets('directive', 'resolveDirective') @@ -56,6 +60,10 @@ export function genBlockContent( push(...genOperations(operation, context)) push(...genEffects(effect, context)) + if (block.hasLazyApplyVShow) { + push(NEWLINE, `lazyApplyVShowFn.forEach(fn => fn())`) + } + if (dynamic.needsKey) { for (const child of dynamic.children) { const keyValue = key diff --git a/packages/compiler-vapor/src/generators/vShow.ts b/packages/compiler-vapor/src/generators/vShow.ts index 9a6ccefcd..701127916 100644 --- a/packages/compiler-vapor/src/generators/vShow.ts +++ b/packages/compiler-vapor/src/generators/vShow.ts @@ -7,12 +7,15 @@ export function genVShow( oper: DirectiveIRNode, context: CodegenContext, ): CodeFragment[] { + const { lazy, element } = oper return [ NEWLINE, - ...genCall(context.helper('applyVShow'), `n${oper.element}`, [ + lazy ? `lazyApplyVShowFn.push(() => ` : undefined, + ...genCall(context.helper('applyVShow'), `n${element}`, [ `() => (`, ...genExpression(oper.dir.exp!, context), `)`, ]), + lazy ? `)` : undefined, ] } diff --git a/packages/compiler-vapor/src/ir/index.ts b/packages/compiler-vapor/src/ir/index.ts index cc0c9dcf3..7cd93b015 100644 --- a/packages/compiler-vapor/src/ir/index.ts +++ b/packages/compiler-vapor/src/ir/index.ts @@ -56,6 +56,7 @@ export interface BlockIRNode extends BaseIRNode { operation: OperationNode[] expressions: SimpleExpressionNode[] returns: number[] + hasLazyApplyVShow: boolean } export interface RootIRNode { @@ -187,6 +188,7 @@ export interface DirectiveIRNode extends BaseIRNode { builtin?: boolean asset?: boolean modelType?: 'text' | 'dynamic' | 'radio' | 'checkbox' | 'select' + lazy?: boolean } export interface CreateComponentIRNode extends BaseIRNode { diff --git a/packages/compiler-vapor/src/transforms/utils.ts b/packages/compiler-vapor/src/transforms/utils.ts index b8e7adc60..99056d44c 100644 --- a/packages/compiler-vapor/src/transforms/utils.ts +++ b/packages/compiler-vapor/src/transforms/utils.ts @@ -31,6 +31,7 @@ export const newBlock = (node: BlockIRNode['node']): BlockIRNode => ({ returns: [], expressions: [], tempId: 0, + hasLazyApplyVShow: false, }) export function wrapTemplate(node: ElementNode, dirs: string[]): TemplateNode { diff --git a/packages/compiler-vapor/src/transforms/vShow.ts b/packages/compiler-vapor/src/transforms/vShow.ts index f1135d6b0..3622cf0ff 100644 --- a/packages/compiler-vapor/src/transforms/vShow.ts +++ b/packages/compiler-vapor/src/transforms/vShow.ts @@ -2,11 +2,13 @@ import { DOMErrorCodes, ElementTypes, ErrorCodes, + NodeTypes, createCompilerError, createDOMCompilerError, } from '@vue/compiler-dom' import type { DirectiveTransform } from '../transform' import { IRNodeTypes } from '../ir' +import { findProp, isTransitionTag } from '../utils' export const transformVShow: DirectiveTransform = (dir, node, context) => { const { exp, loc } = dir @@ -27,11 +29,26 @@ export const transformVShow: DirectiveTransform = (dir, node, context) => { return } + // lazy apply vshow if the node is inside a transition with appear + let lazyApplyVShow = false + const parentNode = context.parent && context.parent.node + if (parentNode && parentNode.type === NodeTypes.ELEMENT) { + lazyApplyVShow = !!( + isTransitionTag(parentNode.tag) && + findProp(parentNode, 'appear', false, true) + ) + + if (lazyApplyVShow) { + context.parent!.parent!.block.hasLazyApplyVShow = true + } + } + context.registerOperation({ type: IRNodeTypes.DIRECTIVE, element: context.reference(), dir, name: 'show', builtin: true, + lazy: lazyApplyVShow, }) } diff --git a/packages/runtime-vapor/src/directives/vShow.ts b/packages/runtime-vapor/src/directives/vShow.ts index 410f0da23..5cd9c66f2 100644 --- a/packages/runtime-vapor/src/directives/vShow.ts +++ b/packages/runtime-vapor/src/directives/vShow.ts @@ -52,9 +52,15 @@ function setDisplay(target: Block, value: unknown): void { el.style.display = el[vShowOriginalDisplay]! $transition.enter(target) } else { - $transition.leave(target, () => { + // during initial render, the element is not yet inserted into the + // DOM, and it is hidden, no need to trigger transition + if (target.isConnected) { + $transition.leave(target, () => { + el.style.display = 'none' + }) + } else { el.style.display = 'none' - }) + } } } else { el.style.display = value ? el[vShowOriginalDisplay]! : 'none'