block: BlockIRNode,
context: CodegenContext,
root?: boolean,
- customReturns?: (returns: CodeFragment[]) => CodeFragment[],
+ genEffectsExtraFrag?: () => CodeFragment[],
): CodeFragment[] {
const [frag, push] = buildCodeFragment()
- const { dynamic, effect, operation, returns } = block
+ const { dynamic, effect, operation, returns, key } = block
const resetBlock = context.enterBlock(block)
+ if (block.hasDeferredVShow) {
+ push(NEWLINE, `const deferredApplyVShows = []`)
+ }
+
if (root) {
for (let name of context.ir.component) {
const id = toValidAssetId(name, 'component')
}
push(...genOperations(operation, context))
- push(...genEffects(effect, context))
+ push(...genEffects(effect, context, genEffectsExtraFrag))
+ if (block.hasDeferredVShow) {
+ push(NEWLINE, `deferredApplyVShows.forEach(fn => fn())`)
+ }
+
+ if (dynamic.needsKey) {
+ for (const child of dynamic.children) {
+ const keyValue = key
+ ? genExpression(key, context)
+ : JSON.stringify(child.id)
+ push(NEWLINE, `n${child.id}.$key = `, ...keyValue)
+ }
+ }
+
push(NEWLINE, `return `)
const returnNodes = returns.map(n => `n${n}`)
import { warn } from './warning'
import type { VNode } from './vnode'
import { devtoolsInitApp, devtoolsUnmountApp } from './devtools'
- import { NO, extend, isFunction, isObject } from '@vue/shared'
+ import { NO, extend, hasOwn, isFunction, isObject } from '@vue/shared'
-import { version } from '.'
+import { type TransitionHooks, version } from '.'
import { installAppCompatProperties } from './compat/global'
import type { NormalizedPropsOptions } from './componentProps'
import type { ObjectEmitsOptions } from './componentEmits'
}
// #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) {
+ performTransitionEnter(
+ 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)
- dirs && invokeDirectiveHook(vnode, null, parentComponent, 'mounted')
- }, parentSuspense)
+ queuePostRenderEffect(
+ () => {
+ vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, vnode)
- needCallTransitionHooks && transition!.enter(el)
+ dirs && invokeDirectiveHook(vnode, null, parentComponent, 'mounted')
+ },
+ undefined,
+ parentSuspense,
+ )
}
}
}
}
-function getVaporInterface(
+// shared between vdom and vapor
+export function performTransitionEnter(
+ el: RendererElement,
+ transition: TransitionHooks,
+ insert: () => void,
+ parentSuspense: SuspenseBoundary | null,
+): void {
+ if (needTransition(parentSuspense, transition)) {
+ transition.beforeEnter(el)
+ insert()
- queuePostRenderEffect(() => transition.enter(el), parentSuspense)
++ queuePostRenderEffect(() => transition.enter(el), undefined, parentSuspense)
+ } else {
+ insert()
+ }
+}
+
+// shared between vdom and vapor
+export function performTransitionLeave(
+ 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()
+ }
+}
+
+export function getVaporInterface(
instance: ComponentInternalInstance | null,
vnode: VNode,
): VaporInteropInterface {
itemRef,
keyRef,
indexRef,
- getKey && getKey(item, key, index),
+ key2,
))
+ // apply transition for new nodes
+ if (frag.$transition) {
+ applyTransitionHooks(block.nodes, frag.$transition, false)
+ }
+
if (parent) insert(block.nodes, parent, anchor)
return block
unmountComponent,
} from './component'
import { createComment, createTextNode } from './dom/node'
- import { EffectScope, pauseTracking, resetTracking } from '@vue/reactivity'
+ import { EffectScope, setActiveSub } from '@vue/reactivity'
import { isHydrating } from './dom/hydration'
+import {
+ type TransitionHooks,
+ type TransitionProps,
+ type TransitionState,
+ performTransitionEnter,
+ performTransitionLeave,
+} from '@vue/runtime-dom'
+import {
+ applyTransitionHooks,
+ applyTransitionLeaveHooks,
+} from './components/Transition'
+
+export interface TransitionOptions {
+ $key?: any
+ $transition?: VaporTransitionHooks
+}
+
+export interface VaporTransitionHooks extends TransitionHooks {
+ state: TransitionState
+ props: TransitionProps
+ instance: VaporComponentInstance
+ // mark transition hooks as disabled so that it skips during
+ // inserting
+ disabled?: boolean
+}
+
+export type TransitionBlock =
+ | (Node & TransitionOptions)
+ | (VaporFragment & TransitionOptions)
+ | (DynamicFragment & TransitionOptions)
-export type Block =
- | Node
- | VaporFragment
- | DynamicFragment
- | VaporComponentInstance
- | Block[]
+export type Block = TransitionBlock | VaporComponentInstance | Block[]
export type BlockFn = (...args: any[]) => Block
}
this.current = key
- pauseTracking()
+ const prevSub = setActiveSub()
const parent = this.anchor.parentNode
+ const transition = this.$transition
+ const renderBranch = () => {
+ if (render) {
+ this.scope = new EffectScope()
+ this.nodes = this.scope.run(render) || []
+ if (transition) {
+ this.$transition = applyTransitionHooks(this.nodes, transition)
+ }
+ if (parent) insert(this.nodes, parent, this.anchor)
+ } else {
+ this.scope = undefined
+ this.nodes = []
+ }
+ }
// teardown previous branch
if (this.scope) {
this.scope.stop()
- parent && remove(this.nodes, parent)
+ const mode = transition && transition.mode
+ if (mode) {
+ applyTransitionLeaveHooks(this.nodes, transition, renderBranch)
+ parent && remove(this.nodes, parent)
+ if (mode === 'out-in') {
- resetTracking()
++ setActiveSub(prevSub)
+ return
+ }
+ } else {
+ parent && remove(this.nodes, parent)
+ }
}
- if (render) {
- this.scope = new EffectScope()
- this.nodes = this.scope.run(render) || []
- if (parent) insert(this.nodes, parent, this.anchor)
- } else {
- this.scope = undefined
- this.nodes = []
- }
+ renderBranch()
if (this.fallback && !isValidBlock(this.nodes)) {
parent && remove(this.nodes, parent)
*/
export function devRender(instance: VaporComponentInstance): void {
instance.block =
- callWithErrorHandling(
- instance.type.render!,
- instance,
- ErrorCodes.RENDER_FUNCTION,
- [
- instance.setupState,
- instance.props,
- instance.emit,
- instance.attrs,
- instance.slots,
- ],
- ) || []
+ (instance.type.render
+ ? callWithErrorHandling(
+ instance.type.render,
+ instance,
+ ErrorCodes.RENDER_FUNCTION,
+ [
+ instance.setupState,
+ instance.props,
+ instance.emit,
+ instance.attrs,
+ instance.slots,
+ ],
+ )
+ : callWithErrorHandling(
+ isFunction(instance.type) ? instance.type : instance.type.setup!,
+ instance,
+ ErrorCodes.SETUP_FUNCTION,
+ [
+ instance.props,
+ {
+ slots: instance.slots,
+ attrs: instance.attrs,
+ emit: instance.emit,
+ expose: instance.expose,
+ },
+ ],
+ )) || []
}
-const emptyContext: GenericAppContext = {
+export const emptyContext: GenericAppContext = {
app: null as any,
config: {},
provides: /*@__PURE__*/ Object.create(null),
isEmitListener,
onScopeDispose,
renderSlot,
+ setTransitionHooks as setVNodeTransitionHooks,
+ shallowReactive,
shallowRef,
simpleSetCurrentInstance,
} from '@vue/runtime-dom'