Skip to content

Commit d14c5a2

Browse files
authored
feat(runtime-vapor): vapor transition work with vapor teleport (#14047)
1 parent c6bbc4a commit d14c5a2

File tree

6 files changed

+123
-38
lines changed

6 files changed

+123
-38
lines changed

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

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -923,7 +923,54 @@ describe('vapor transition', () => {
923923
})
924924

925925
describe.todo('transition with Suspense', () => {})
926-
describe.todo('transition with Teleport', () => {})
926+
927+
describe('transition with Teleport', () => {
928+
test(
929+
'apply transition to teleport child',
930+
async () => {
931+
const btnSelector = '.with-teleport > button'
932+
const containerSelector = '.with-teleport > .container'
933+
const targetSelector = `.with-teleport > .target`
934+
935+
await transitionFinish()
936+
expect(await html(containerSelector)).toBe('')
937+
expect(await html(targetSelector)).toBe('')
938+
939+
// enter
940+
expect(
941+
(await transitionStart(btnSelector, `${targetSelector} div`))
942+
.classNames,
943+
).toStrictEqual(['test', 'v-enter-from', 'v-enter-active'])
944+
await nextFrame()
945+
expect(await classList(`${targetSelector} div`)).toStrictEqual([
946+
'test',
947+
'v-enter-active',
948+
'v-enter-to',
949+
])
950+
await transitionFinish()
951+
expect(await html(targetSelector)).toBe(
952+
'<div class="test">vapor compB</div>',
953+
)
954+
expect(await html(containerSelector)).toBe('')
955+
956+
// leave
957+
expect(
958+
(await transitionStart(btnSelector, `${targetSelector} div`))
959+
.classNames,
960+
).toStrictEqual(['test', 'v-leave-from', 'v-leave-active'])
961+
await nextFrame()
962+
expect(await classList(`${targetSelector} div`)).toStrictEqual([
963+
'test',
964+
'v-leave-active',
965+
'v-leave-to',
966+
])
967+
await transitionFinish()
968+
expect(await html(targetSelector)).toBe('')
969+
expect(await html(containerSelector)).toBe('')
970+
},
971+
E2E_TIMEOUT,
972+
)
973+
})
927974

928975
describe('transition with v-show', () => {
929976
test(

packages-private/vapor-e2e-test/transition/App.vue

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,21 @@ const click = () => {
503503
</div>
504504
<!-- mode end -->
505505

506+
<!-- with teleport -->
507+
<div class="with-teleport">
508+
<div class="target"></div>
509+
<div class="container">
510+
<Transition>
511+
<Teleport to=".target" defer>
512+
<!-- comment -->
513+
<VaporCompB v-if="!toggle" class="test"></VaporCompB>
514+
</Teleport>
515+
</Transition>
516+
</div>
517+
<button @click="toggle = !toggle">button</button>
518+
</div>
519+
<!-- with teleport end -->
520+
506521
<!-- with keep-alive -->
507522
<div class="keep-alive">
508523
<div>

packages/compiler-vapor/src/transforms/vSlot.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,17 @@ function transformComponentSlot(
9090

9191
let slotKey
9292
if (isTransitionNode(node) && nonSlotTemplateChildren.length) {
93-
const keyProp = findProp(
94-
nonSlotTemplateChildren[0] as ElementNode,
95-
'key',
96-
) as VaporDirectiveNode
97-
if (keyProp) {
98-
slotKey = keyProp.exp
93+
const nonCommentChild = nonSlotTemplateChildren.find(
94+
n => n.type !== NodeTypes.COMMENT,
95+
)
96+
if (nonCommentChild) {
97+
const keyProp = findProp(
98+
nonCommentChild as ElementNode,
99+
'key',
100+
) as VaporDirectiveNode
101+
if (keyProp) {
102+
slotKey = keyProp.exp
103+
}
99104
}
100105
}
101106

packages/runtime-vapor/src/component.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -381,10 +381,7 @@ export function setupComponent(
381381
component.inheritAttrs !== false &&
382382
Object.keys(instance.attrs).length
383383
) {
384-
const el = getRootElement(instance)
385-
if (el) {
386-
renderEffect(() => applyFallthroughProps(el, instance.attrs))
387-
}
384+
renderEffect(() => applyFallthroughProps(instance.block, instance.attrs))
388385
}
389386

390387
setActiveSub(prevSub)
@@ -402,9 +399,12 @@ export function applyFallthroughProps(
402399
block: Block,
403400
attrs: Record<string, any>,
404401
): void {
405-
isApplyingFallthroughProps = true
406-
setDynamicProps(block as Element, [attrs])
407-
isApplyingFallthroughProps = false
402+
const el = getRootElement(block)
403+
if (el) {
404+
isApplyingFallthroughProps = true
405+
setDynamicProps(el, [attrs])
406+
isApplyingFallthroughProps = false
407+
}
408408
}
409409

410410
/**
@@ -761,9 +761,7 @@ export function getExposed(
761761
}
762762
}
763763

764-
function getRootElement({
765-
block,
766-
}: VaporComponentInstance): Element | undefined {
764+
function getRootElement(block: Block): Element | undefined {
767765
if (block instanceof Element) {
768766
return block
769767
}

packages/runtime-vapor/src/components/Teleport.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
runWithoutHydration,
3030
setCurrentHydrationNode,
3131
} from '../dom/hydration'
32+
import { applyTransitionHooks } from './Transition'
3233

3334
export const VaporTeleportImpl = {
3435
name: 'VaporTeleport',
@@ -122,6 +123,9 @@ export class TeleportFragment extends VaporFragment {
122123
if (!this.parent || isHydrating) return
123124

124125
const mount = (parent: ParentNode, anchor: Node | null) => {
126+
if (this.$transition) {
127+
applyTransitionHooks(this.nodes, this.$transition)
128+
}
125129
insert(
126130
this.nodes,
127131
(this.mountContainer = parent),

packages/runtime-vapor/src/components/Transition.ts

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
baseResolveTransitionHooks,
1111
checkTransitionMode,
1212
currentInstance,
13+
getComponentName,
1314
isTemplateNode,
1415
leaveCbKey,
1516
queuePostFlushCb,
@@ -33,8 +34,10 @@ import {
3334
setCurrentHydrationNode,
3435
} from '../dom/hydration'
3536

37+
const displayName = 'VaporTransition'
38+
3639
const decorate = (t: typeof VaporTransition) => {
37-
t.displayName = 'VaporTransition'
40+
t.displayName = displayName
3841
t.props = TransitionPropsValidators
3942
t.__vapor = true
4043
return t
@@ -208,8 +211,18 @@ export function applyTransitionHooks(
208211
hooks: VaporTransitionHooks,
209212
fallthroughAttrs: boolean = true,
210213
): VaporTransitionHooks {
214+
// filter out comment nodes
215+
if (isArray(block)) {
216+
block = block.filter(b => !(b instanceof Comment))
217+
if (block.length === 1) {
218+
block = block[0]
219+
} else if (block.length === 0) {
220+
return hooks
221+
}
222+
}
223+
211224
const isFrag = isFragment(block)
212-
const child = findTransitionBlock(block)
225+
const child = findTransitionBlock(block, isFrag)
213226
if (!child) {
214227
// set transition hooks on fragment for reusing during it's updating
215228
if (isFrag) setTransitionHooksOnFragment(block, hooks)
@@ -296,43 +309,46 @@ export function findTransitionBlock(
296309
return transitionBlockCache.get(block)
297310
}
298311

299-
let isFrag = false
300312
let child: TransitionBlock | undefined
301313
if (block instanceof Node) {
302314
// transition can only be applied on Element child
303315
if (block instanceof Element) child = block
304316
} else if (isVaporComponent(block)) {
305-
child = findTransitionBlock(block.block)
317+
// stop searching if encountering nested Transition component
318+
if (getComponentName(block.type) === displayName) return undefined
319+
child = findTransitionBlock(block.block, inFragment)
306320
// use component id as key
307321
if (child && child.$key === undefined) child.$key = block.uid
308322
} else if (isArray(block)) {
309-
child = block[0] as TransitionBlock
310323
let hasFound = false
311324
for (const c of block) {
312-
const item = findTransitionBlock(c)
313-
if (item instanceof Element) {
314-
if (__DEV__ && hasFound) {
315-
// warn more than one non-comment child
316-
warn(
317-
'<transition> can only be used on a single element or component. ' +
318-
'Use <transition-group> for lists.',
319-
)
320-
break
321-
}
322-
child = item
323-
hasFound = true
324-
if (!__DEV__) break
325+
if (c instanceof Comment) continue
326+
// check if the child is a fragment to suppress warnings
327+
if (isFragment(c)) inFragment = true
328+
const item = findTransitionBlock(c, inFragment)
329+
if (__DEV__ && hasFound) {
330+
// warn more than one non-comment child
331+
warn(
332+
'<transition> can only be used on a single element or component. ' +
333+
'Use <transition-group> for lists.',
334+
)
335+
break
325336
}
337+
child = item
338+
hasFound = true
339+
if (!__DEV__) break
326340
}
327-
} else if ((isFrag = isFragment(block))) {
341+
} else if (isFragment(block)) {
342+
// mark as in fragment to suppress warnings
343+
inFragment = true
328344
if (block.insert) {
329345
child = block
330346
} else {
331347
child = findTransitionBlock(block.nodes, true)
332348
}
333349
}
334350

335-
if (__DEV__ && !child && !inFragment && !isFrag) {
351+
if (__DEV__ && !child && !inFragment) {
336352
warn('Transition component has no valid child element')
337353
}
338354

@@ -344,7 +360,7 @@ export function setTransitionHooksOnFragment(
344360
hooks: VaporTransitionHooks,
345361
): void {
346362
if (isFragment(block)) {
347-
setTransitionHooks(block, hooks)
363+
block.$transition = hooks
348364
} else if (isArray(block)) {
349365
for (let i = 0; i < block.length; i++) {
350366
setTransitionHooksOnFragment(block[i], hooks)

0 commit comments

Comments
 (0)