const [ids, handlers] = processInlineHandlers(props, context)
const rawProps = context.withId(() => genRawProps(props, context), ids)
const inlineHandlers: CodeFragment[] = handlers.reduce<CodeFragment[]>(
- (acc, { name, value }) => {
+ (acc, { name, value }: InlineHandler) => {
const handler = genEventHandler(context, value, undefined, false)
return [...acc, `const ${name} = `, ...handler, NEWLINE]
},
[],
)
-
return [
NEWLINE,
...inlineHandlers,
import {
type ComponentInternalInstance,
type ComponentOptions,
+ type GenericComponentInstance,
type SetupContext,
getCurrentInstance,
} from '../component'
vnode: VNode,
props: BaseTransitionProps<any>,
state: TransitionState,
- instance: ComponentInternalInstance,
+ instance: GenericComponentInstance,
postClone?: (hooks: TransitionHooks) => void,
): TransitionHooks {
const {
* @internal
*/
export { initFeatureFlags } from './featureFlags'
+/**
+ * @internal
+ */
+export { applyTransitionEnter, applyTransitionLeave } from './renderer'
}
// #1583 For inside suspense + suspense not resolved case, enter hook should call when suspense resolved
// #1689 For inside suspense + suspense resolved case, just call it
- const needCallTransitionHooks = needTransition(parentSuspense, transition)
- if (needCallTransitionHooks) {
- transition!.beforeEnter(el)
+ if (transition) {
+ applyTransitionEnter(
+ el,
+ transition,
+ () => hostInsert(el, container, anchor),
+ parentSuspense,
+ )
+ } else {
+ hostInsert(el, container, anchor)
}
- hostInsert(el, container, anchor)
- if (
- (vnodeHook = props && props.onVnodeMounted) ||
- needCallTransitionHooks ||
- dirs
- ) {
+
+ if ((vnodeHook = props && props.onVnodeMounted) || dirs) {
queuePostRenderEffect(() => {
vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, vnode)
- needCallTransitionHooks && transition!.enter(el)
dirs && invokeDirectiveHook(vnode, null, parentComponent, 'mounted')
}, parentSuspense)
}
transition
if (needTransition) {
if (moveType === MoveType.ENTER) {
- transition!.beforeEnter(el!)
- hostInsert(el!, container, anchor)
- queuePostRenderEffect(() => transition!.enter(el!), parentSuspense)
+ applyTransitionEnter(
+ el!,
+ transition,
+ () => hostInsert(el!, container, anchor),
+ parentSuspense,
+ )
} else {
const { leave, delayLeave, afterLeave } = transition!
const remove = () => hostInsert(el!, container, anchor)
return
}
- const performRemove = () => {
- hostRemove(el!)
- if (transition && !transition.persisted && transition.afterLeave) {
- transition.afterLeave()
- }
- }
-
- if (
- vnode.shapeFlag & ShapeFlags.ELEMENT &&
- transition &&
- !transition.persisted
- ) {
- const { leave, delayLeave } = transition
- const performLeave = () => leave(el!, performRemove)
- if (delayLeave) {
- delayLeave(vnode.el!, performRemove, performLeave)
- } else {
- performLeave()
- }
+ if (transition) {
+ applyTransitionLeave(
+ el!,
+ transition,
+ () => hostRemove(el!),
+ !!(vnode.shapeFlag & ShapeFlags.ELEMENT),
+ )
} else {
- performRemove()
+ hostRemove(el!)
}
}
}
}
+export function applyTransitionEnter(
+ el: RendererElement,
+ transition: TransitionHooks,
+ insert: () => void,
+ parentSuspense: SuspenseBoundary | null,
+): void {
+ if (needTransition(parentSuspense, transition)) {
+ transition.beforeEnter(el)
+ insert()
+ queuePostRenderEffect(() => transition.enter(el), parentSuspense)
+ } else {
+ insert()
+ }
+}
+
+export function applyTransitionLeave(
+ el: RendererElement,
+ transition: TransitionHooks,
+ remove: () => void,
+ isElement: boolean = true,
+): void {
+ const performRemove = () => {
+ remove()
+ if (transition && !transition.persisted && transition.afterLeave) {
+ transition.afterLeave()
+ }
+ }
+
+ if (isElement && transition && !transition.persisted) {
+ const { leave, delayLeave } = transition
+ const performLeave = () => leave(el, performRemove)
+ if (delayLeave) {
+ delayLeave(el, performRemove, performLeave)
+ } else {
+ performLeave()
+ }
+ } else {
+ performRemove()
+ }
+}
+
function getVaporInterface(
instance: ComponentInternalInstance | null,
vnode: VNode,
leaveToClass?: string
}
+export interface VaporTransitionInterface {
+ applyTransition: (
+ props: TransitionProps,
+ slots: { default: () => any },
+ ) => void
+}
+
+let vaporTransitionImpl: VaporTransitionInterface | null = null
+export const registerVaporTransition = (
+ impl: VaporTransitionInterface,
+): void => {
+ vaporTransitionImpl = impl
+}
+
export const vtcKey: unique symbol = Symbol('_vtc')
export interface ElementWithTransition extends HTMLElement {
* base Transition component, with DOM-specific logic.
*/
export const Transition: FunctionalComponent<TransitionProps> =
- /*@__PURE__*/ decorate((props, { slots }) =>
- h(BaseTransition, resolveTransitionProps(props), slots),
- )
+ /*@__PURE__*/ decorate((props, { slots, vapor }: any) => {
+ const resolvedProps = resolveTransitionProps(props)
+ if (vapor) {
+ return vaporTransitionImpl!.applyTransition(resolvedProps, slots)
+ }
+ return h(BaseTransition, resolvedProps, slots)
+ })
/**
* #3227 Incoming hooks may be merged into arrays when wrapping Transition
vModelSelectInit,
vModelSetSelected,
} from './directives/vModel'
+/**
+ * @internal
+ */
+export {
+ resolveTransitionProps,
+ TransitionPropsValidators,
+ registerVaporTransition,
+} from './components/Transition'
+/**
+ * @internal
+ */
+export type { VaporTransitionInterface } from './components/Transition'
import type { RawProps } from './componentProps'
import { getGlobalThis } from '@vue/shared'
import { optimizePropertyLookup } from './dom/prop'
+import { ensureVaporTransition } from './components/Transition'
let _createApp: CreateAppFunction<ParentNode, VaporComponent>
const mountApp: AppMountFn<ParentNode> = (app, container) => {
optimizePropertyLookup()
+ ensureVaporTransition()
// clear content before mounting
if (container.nodeType === 1 /* Node.ELEMENT_NODE */) {
} from './component'
import { createComment, createTextNode } from './dom/node'
import { EffectScope, pauseTracking, resetTracking } from '@vue/reactivity'
+import {
+ type TransitionHooks,
+ applyTransitionEnter,
+ applyTransitionLeave,
+} from '@vue/runtime-dom'
-export type Block =
+export type Block = (
| Node
| VaporFragment
| DynamicFragment
| VaporComponentInstance
| Block[]
+) & { transition?: TransitionHooks }
export type BlockFn = (...args: any[]) => Block
anchor?: Node
insert?: (parent: ParentNode, anchor: Node | null) => void
remove?: (parent?: ParentNode) => void
+ transition?: TransitionHooks
constructor(nodes: Block) {
this.nodes = nodes
// teardown previous branch
if (this.scope) {
this.scope.stop()
- parent && remove(this.nodes, parent)
+ parent && remove(this.nodes, parent, this.transition)
}
if (render) {
this.scope = new EffectScope()
this.nodes = this.scope.run(render) || []
- if (parent) insert(this.nodes, parent, this.anchor)
+ if (parent) insert(this.nodes, parent, this.anchor, this.transition)
} else {
this.scope = undefined
this.nodes = []
this.nodes =
(this.scope || (this.scope = new EffectScope())).run(this.fallback) ||
[]
- parent && insert(this.nodes, parent, this.anchor)
+ parent && insert(this.nodes, parent, this.anchor, this.transition)
}
resetTracking()
block: Block,
parent: ParentNode,
anchor: Node | null | 0 = null, // 0 means prepend
+ transition: TransitionHooks | undefined = block.transition,
+ parentSuspense?: any, // TODO Suspense
): void {
anchor = anchor === 0 ? parent.firstChild : anchor
if (block instanceof Node) {
- parent.insertBefore(block, anchor)
+ if (transition) {
+ applyTransitionEnter(
+ block,
+ transition,
+ () => parent.insertBefore(block, anchor),
+ parentSuspense,
+ )
+ } else {
+ parent.insertBefore(block, anchor)
+ }
} else if (isVaporComponent(block)) {
- mountComponent(block, parent, anchor)
+ mountComponent(block, parent, anchor, transition)
} else if (isArray(block)) {
for (let i = 0; i < block.length; i++) {
insert(block[i], parent, anchor)
if (block.insert) {
block.insert(parent, anchor)
} else {
- insert(block.nodes, parent, anchor)
+ insert(block.nodes, parent, anchor, block.transition)
}
if (block.anchor) insert(block.anchor, parent, anchor)
}
while (i--) insert(blocks[i], parent, 0)
}
-export function remove(block: Block, parent?: ParentNode): void {
+export function remove(
+ block: Block,
+ parent?: ParentNode,
+ transition: TransitionHooks | undefined = block.transition,
+): void {
if (block instanceof Node) {
- parent && parent.removeChild(block)
+ if (transition) {
+ applyTransitionLeave(
+ block,
+ transition,
+ () => parent && parent.removeChild(block),
+ )
+ } else {
+ parent && parent.removeChild(block)
+ }
} else if (isVaporComponent(block)) {
- unmountComponent(block, parent)
+ unmountComponent(block, parent, transition)
} else if (isArray(block)) {
for (let i = 0; i < block.length; i++) {
remove(block[i], parent)
if (block.remove) {
block.remove(parent)
} else {
- remove(block.nodes, parent)
+ remove(block.nodes, parent, block.transition)
}
if (block.anchor) remove(block.anchor, parent)
if ((block as DynamicFragment).scope) {
type NormalizedPropsOptions,
type ObjectEmitsOptions,
type SuspenseBoundary,
+ type TransitionHooks,
callWithErrorHandling,
currentInstance,
endMeasure,
instance: VaporComponentInstance,
parent: ParentNode,
anchor?: Node | null | 0,
+ transition?: TransitionHooks,
): void {
if (__DEV__) {
startMeasure(instance, `mount`)
}
if (!instance.isMounted) {
if (instance.bm) invokeArrayFns(instance.bm)
- insert(instance.block, parent, anchor)
+ insert(instance.block, parent, anchor, transition)
if (instance.m) queuePostFlushCb(() => invokeArrayFns(instance.m!))
instance.isMounted = true
} else {
- insert(instance.block, parent, anchor)
+ insert(instance.block, parent, anchor, transition)
}
if (__DEV__) {
endMeasure(instance, `mount`)
export function unmountComponent(
instance: VaporComponentInstance,
parentNode?: ParentNode,
+ transition?: TransitionHooks,
): void {
if (instance.isMounted && !instance.isUnmounted) {
if (__DEV__ && instance.type.__hmrId) {
}
if (parentNode) {
- remove(instance.block, parentNode)
+ remove(instance.block, parentNode, transition)
}
}
--- /dev/null
+import {
+ type TransitionHooks,
+ type TransitionProps,
+ type VaporTransitionInterface,
+ currentInstance,
+ registerVaporTransition,
+ resolveTransitionHooks,
+ useTransitionState,
+} from '@vue/runtime-dom'
+import type { Block } from '../block'
+import { isVaporComponent } from '../component'
+
+export const vaporTransitionImpl: VaporTransitionInterface = {
+ applyTransition: (props: TransitionProps, slots: { default: () => any }) => {
+ const children = slots.default && slots.default()
+ if (!children) {
+ return
+ }
+
+ // TODO find non-comment node
+ const child = children
+
+ const state = useTransitionState()
+ let enterHooks = resolveTransitionHooks(
+ child as any,
+ props,
+ state,
+ currentInstance!,
+ hooks => (enterHooks = hooks),
+ )
+ setTransitionHooks(child, enterHooks)
+
+ // TODO handle mode
+
+ return children
+ },
+}
+
+function setTransitionHooks(block: Block, hooks: TransitionHooks) {
+ if (isVaporComponent(block)) {
+ setTransitionHooks(block.block, hooks)
+ } else {
+ block.transition = hooks
+ }
+}
+
+let registered = false
+export function ensureVaporTransition(): void {
+ if (!registered) {
+ registerVaporTransition(vaporTransitionImpl)
+ registered = true
+ }
+}
if (target instanceof DynamicFragment) {
return setDisplay(target.nodes, value)
}
+ const { transition } = target
if (target instanceof Element) {
const el = target as VShowElement
if (!(vShowOriginalDisplay in el)) {
el[vShowOriginalDisplay] =
el.style.display === 'none' ? '' : el.style.display
}
- el.style.display = value ? el[vShowOriginalDisplay]! : 'none'
+ if (transition) {
+ if (value) {
+ transition.beforeEnter(target)
+ el.style.display = el[vShowOriginalDisplay]!
+ transition.enter(target)
+ } else {
+ transition.leave(target, () => {
+ el.style.display = 'none'
+ })
+ }
+ } else {
+ el.style.display = value ? el[vShowOriginalDisplay]! : 'none'
+ }
el[vShowHidden] = !value
} else if (__DEV__) {
warn(
import { renderEffect } from './renderEffect'
import { createTextNode } from './dom/node'
import { optimizePropertyLookup } from './dom/prop'
+import { ensureVaporTransition } from './components/Transition'
// mounting vapor components and slots in vdom
const vaporInteropImpl: Omit<
const mount = app.mount
app.mount = ((...args) => {
optimizePropertyLookup()
+ ensureVaporTransition()
return mount(...args)
}) satisfies App['mount']
}