Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
07e02dc
fix(transition): optimize prop handling in VaporTransition using Proxy
edison1105 Dec 19, 2025
b6d633d
fix(runtime-vapor): optimize prop handling in VaporTransitionGroup us…
edison1105 Dec 19, 2025
1120298
fix(runtime-vapor): remove unused isResolved parameter from applyTran…
edison1105 Dec 19, 2025
17372a9
fix(runtime-vapor): refactor transition hooks application to use call…
edison1105 Dec 19, 2025
1f5b70c
fix(runtime-vapor): add move function for block repositioning with tr…
edison1105 Dec 19, 2025
f7c9c6e
fix(runtime-vapor): refactor move function into insert with MoveType …
edison1105 Dec 19, 2025
e2ca9d1
refactor(runtime-core): move MoveType export to public API
edison1105 Dec 19, 2025
84be655
test(vapor-e2e): add transition test for keep-alive include update
edison1105 Dec 19, 2025
f04a024
fix(Transition): prevent unmounted block from being inserted after tr…
edison1105 Dec 19, 2025
8633e30
fix(runtime-vapor): fix scope preservation logic in onBeforeTeardown …
edison1105 Dec 19, 2025
c64c19b
fix(KeepAlive): preserve fragment's scope only if it include a compon…
edison1105 Dec 19, 2025
d462cb4
chore(runtime-vapor): simplify HMR unregistering condition in unmount…
edison1105 Dec 19, 2025
3ddba38
chore(test): add todo tests
edison1105 Dec 19, 2025
05a6d08
Merge branch 'minor' into edison/fix/KeepAliveWorkWithTransition
edison1105 Dec 22, 2025
5fc82b8
test: add more tests
edison1105 Dec 22, 2025
2e6fe33
test: fix lint error
edison1105 Dec 22, 2025
092e052
fix(transition): move kept-alive node before v-show transition leave …
edison1105 Dec 22, 2025
585913c
test: add SVG transition tests
edison1105 Dec 22, 2025
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
7 changes: 2 additions & 5 deletions packages/runtime-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ export {
export { withDirectives } from './directives'
// SSR context
export { useSSRContext, ssrContextKey } from './helpers/useSsrContext'
export { MoveType } from './renderer'

// Custom Renderer API ---------------------------------------------------------

Expand Down Expand Up @@ -519,11 +520,7 @@ export { type VaporInteropInterface } from './apiCreateApp'
/**
* @internal
*/
export {
type RendererInternals,
MoveType,
getInheritedScopeIds,
} from './renderer'
export { type RendererInternals, getInheritedScopeIds } from './renderer'
/**
* @internal
*/
Expand Down
8 changes: 6 additions & 2 deletions packages/runtime-vapor/__tests__/hmr.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,11 +276,14 @@ describe('hot module replacement', () => {
components: { Child },
setup() {
const toggle = ref(true)
return { toggle }
function onLeave(_: any, done: Function) {
setTimeout(done, 0)
}
return { toggle, onLeave }
},
render: compileToFunction(
`<button @click="toggle = !toggle" />
<Transition>
<Transition @leave="onLeave">
<KeepAlive><Child v-if="toggle" /></KeepAlive>
</Transition>`,
),
Expand All @@ -303,6 +306,7 @@ describe('hot module replacement', () => {
render: compileToFunction(`<div>{{ count }}</div>`),
})
await nextTick()
await new Promise(r => setTimeout(r, 0))
expect(root.innerHTML).toBe(`<button></button><div>1</div><!--if-->`)
expect(unmountSpy).toHaveBeenCalledTimes(1)
expect(mountSpy).toHaveBeenCalledTimes(1)
Expand Down
9 changes: 8 additions & 1 deletion packages/runtime-vapor/src/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import { _child } from './dom/node'
import { isComment, isHydrating } from './dom/hydration'
import {
MoveType,
type TransitionHooks,
type TransitionProps,
type TransitionState,
Expand Down Expand Up @@ -77,6 +78,7 @@ export function insert(
block: Block,
parent: ParentNode & { $fc?: Node | null },
anchor: Node | null | 0 = null, // 0 means prepend
moveType: MoveType = MoveType.ENTER,
parentSuspense?: any, // TODO Suspense
): void {
anchor = anchor === 0 ? parent.$fc || _child(parent) : anchor
Expand All @@ -88,7 +90,12 @@ export function insert(
(block as TransitionBlock).$transition &&
!(block as TransitionBlock).$transition!.disabled
) {
performTransitionEnter(
const action =
moveType === MoveType.LEAVE
? performTransitionLeave
: performTransitionEnter

action(
block,
(block as TransitionBlock).$transition as TransitionHooks,
() => parent.insertBefore(block, anchor as Node),
Expand Down
3 changes: 2 additions & 1 deletion packages/runtime-vapor/src/components/KeepAlive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
type GenericComponent,
type GenericComponentInstance,
type KeepAliveProps,
MoveType,
type VNode,
currentInstance,
devtoolsComponentAdded,
Expand Down Expand Up @@ -358,7 +359,7 @@ export function deactivate(
instance: VaporComponentInstance,
container: ParentNode,
): void {
insert(instance.block, container)
insert(instance.block, container, null, MoveType.LEAVE)

queuePostFlushCb(() => {
if (instance.da) invokeArrayFns(instance.da)
Expand Down
64 changes: 23 additions & 41 deletions packages/runtime-vapor/src/components/Transition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
} from '../component'
import { isArray } from '@vue/shared'
import { renderEffect } from '../renderEffect'
import { isFragment } from '../fragment'
import { type VaporFragment, isFragment } from '../fragment'
import {
currentHydrationNode,
isHydrating,
Expand Down Expand Up @@ -80,29 +80,16 @@ export const VaporTransition: FunctionalVaporComponent<TransitionProps> =
checkTransitionMode(mode)

let resolvedProps: BaseTransitionProps<Element>
let isMounted = false
renderEffect(() => {
resolvedProps = resolveTransitionProps(props)
if (isMounted) {
// only update props for Fragment transition, for later reusing
if (isFragment(children)) {
children.$transition!.props = resolvedProps
} else {
const child = findTransitionBlock(children)
if (child) {
// replace existing transition hooks
child.$transition!.props = resolvedProps
applyTransitionHooks(child, child.$transition!, true)
}
}
} else {
isMounted = true
}
})
renderEffect(() => (resolvedProps = resolveTransitionProps(props)))

const hooks = applyTransitionHooks(children, {
state: useTransitionState(),
props: resolvedProps!,
// use proxy to keep props reference stable
props: new Proxy({} as BaseTransitionProps<Element>, {
get(_, key) {
return resolvedProps[key as keyof BaseTransitionProps<Element>]
},
}),
instance: instance,
} as VaporTransitionHooks)

Expand Down Expand Up @@ -185,7 +172,6 @@ export function resolveTransitionHooks(
export function applyTransitionHooks(
block: Block,
hooks: VaporTransitionHooks,
isResolved: boolean = false,
): VaporTransitionHooks {
// filter out comment nodes
if (isArray(block)) {
Expand All @@ -197,13 +183,15 @@ export function applyTransitionHooks(
}
}

const isFrag = isFragment(block)
const child = isResolved
? (block as TransitionBlock)
: findTransitionBlock(block, isFrag)
const fragments: VaporFragment[] = []
const child = findTransitionBlock(block, frag => fragments.push(frag))
if (!child) {
// set transition hooks on fragment for reusing during it's updating
if (isFrag) setTransitionHooksOnFragment(block, hooks)
// set transition hooks on fragments for later use
fragments.forEach(f => (f.$transition = hooks))
// warn if no child and no fragments
if (__DEV__ && fragments.length === 0) {
warn('Transition component has no valid child element')
}
return hooks
}

Expand All @@ -217,7 +205,7 @@ export function applyTransitionHooks(
)
resolvedHooks.delayedLeave = delayedLeave
child.$transition = resolvedHooks
if (isFrag) setTransitionHooksOnFragment(block, resolvedHooks)
fragments.forEach(f => (f.$transition = resolvedHooks))

return resolvedHooks
}
Expand Down Expand Up @@ -273,7 +261,7 @@ export function applyTransitionLeaveHooks(

export function findTransitionBlock(
block: Block,
inFragment: boolean = false,
onFragment?: (frag: VaporFragment) => void,
): TransitionBlock | undefined {
let child: TransitionBlock | undefined
if (block instanceof Node) {
Expand All @@ -286,17 +274,15 @@ export function findTransitionBlock(
} else {
// stop searching if encountering nested Transition component
if (getComponentName(block.type) === displayName) return undefined
child = findTransitionBlock(block.block, inFragment)
child = findTransitionBlock(block.block, onFragment)
// 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) {
if (c instanceof Comment) continue
// check if the child is a fragment to suppress warnings
if (isFragment(c)) inFragment = true
const item = findTransitionBlock(c, inFragment)
const item = findTransitionBlock(c, onFragment)
if (__DEV__ && hasFound) {
// warn more than one non-comment child
warn(
Expand All @@ -310,19 +296,15 @@ export function findTransitionBlock(
if (!__DEV__) break
}
} else if (isFragment(block)) {
// mark as in fragment to suppress warnings
inFragment = true
if (block.insert) {
child = block
} else {
child = findTransitionBlock(block.nodes, true)
// collect fragments for setting transition hooks
if (onFragment) onFragment(block)
child = findTransitionBlock(block.nodes, onFragment)
}
}

if (__DEV__ && !child && !inFragment) {
warn('Transition component has no valid child element')
}

return child
}

Expand Down
18 changes: 15 additions & 3 deletions packages/runtime-vapor/src/components/TransitionGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
type VaporTransitionHooks,
insert,
} from '../block'
import { renderEffect } from '../renderEffect'
import {
resolveTransitionHooks,
setTransitionHooks,
Expand Down Expand Up @@ -55,7 +56,18 @@ export const VaporTransitionGroup: ObjectVaporComponent = decorate({
setup(props: TransitionGroupProps, { slots }) {
const instance = currentInstance as VaporComponentInstance
const state = useTransitionState()
const cssTransitionProps = resolveTransitionProps(props)

// use proxy to keep props reference stable
let cssTransitionProps = resolveTransitionProps(props)
const propsProxy = new Proxy({} as typeof cssTransitionProps, {
get(_, key) {
return cssTransitionProps[key as keyof typeof cssTransitionProps]
},
})

renderEffect(() => {
cssTransitionProps = resolveTransitionProps(props)
})

let prevChildren: TransitionBlock[]
let children: TransitionBlock[]
Expand Down Expand Up @@ -121,7 +133,7 @@ export const VaporTransitionGroup: ObjectVaporComponent = decorate({

// store props and state on fragment for reusing during insert new items
setTransitionHooksOnFragment(slottedBlock, {
props: cssTransitionProps,
props: propsProxy,
state,
instance,
} as VaporTransitionHooks)
Expand All @@ -133,7 +145,7 @@ export const VaporTransitionGroup: ObjectVaporComponent = decorate({
if (child.$key != null) {
const hooks = resolveTransitionHooks(
child,
cssTransitionProps,
propsProxy,
state,
instance!,
)
Expand Down
Loading