--- /dev/null
- "import { VaporTransition as _VaporTransition, createIf as _createIf, prepend as _prepend, createComponent as _createComponent, template as _template } from 'vue';
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`compiler: transition > basic 1`] = `
+"import { VaporTransition as _VaporTransition, applyVShow as _applyVShow, createComponent as _createComponent, template as _template } from 'vue';
+const t0 = _template("<h1>foo</h1>")
+
+export function render(_ctx) {
+ const n1 = _createComponent(_VaporTransition, { persisted: () => ("") }, {
+ "default": () => {
+ const n0 = t0()
+ _applyVShow(n0, () => (_ctx.show))
+ return n0
+ }
+ }, true)
+ return n1
+}"
+`;
+
+exports[`compiler: transition > inject persisted when child has v-show 1`] = `
+"import { VaporTransition as _VaporTransition, applyVShow as _applyVShow, createComponent as _createComponent, template as _template } from 'vue';
+const t0 = _template("<div></div>")
+
+export function render(_ctx) {
+ const n1 = _createComponent(_VaporTransition, { persisted: () => ("") }, {
+ "default": () => {
+ const n0 = t0()
+ _applyVShow(n0, () => (_ctx.ok))
+ return n0
+ }
+ }, true)
+ return n1
+}"
+`;
+
+exports[`compiler: transition > the v-if/else-if/else branches in Transition should ignore comments 1`] = `
- _prepend(n14, n9)
++"import { VaporTransition as _VaporTransition, setInsertionState as _setInsertionState, createIf as _createIf, createComponent as _createComponent, template as _template } from 'vue';
+const t0 = _template("<div>hey</div>")
+const t1 = _template("<p></p>")
+const t2 = _template("<div></div>")
+
+export function render(_ctx) {
+ const n16 = _createComponent(_VaporTransition, null, {
+ "default": () => {
+ const n0 = _createIf(() => (_ctx.a), () => {
+ const n2 = t0()
+ n2.$key = 2
+ return n2
+ }, () => _createIf(() => (_ctx.b), () => {
+ const n5 = t0()
+ n5.$key = 5
+ return n5
+ }, () => {
+ const n14 = t2()
++ _setInsertionState(n14, 0)
+ const n9 = _createIf(() => (_ctx.c), () => {
+ const n11 = t1()
+ return n11
+ }, () => {
+ const n13 = t1()
+ return n13
+ })
+ n14.$key = 14
+ return n14
+ }))
+ return [n0, n3, n7]
+ }
+ }, true)
+ return n16
+}"
+`;
+
+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("<h1>foo</h1>")
+
+export function render(_ctx) {
+ const deferredApplyVShows = []
+ const n1 = _createComponent(_VaporTransition, {
+ appear: () => (""),
+ persisted: () => ("")
+ }, {
+ "default": () => {
+ const n0 = t0()
+ deferredApplyVShows.push(() => _applyVShow(n0, () => (_ctx.show)))
+ return n0
+ }
+ }, true)
+ deferredApplyVShows.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("<h1>foo</h1>")
+
+export function render(_ctx) {
+ const n1 = _createComponent(_VaporTransition, null, {
+ "default": () => {
+ return _createKeyedFragment(() => _ctx.key, () => {
+ const n0 = t0()
+ n0.$key = _ctx.key
+ return n0
+ })
+ }
+ }, true)
+ return n1
+}"
+`;
+
+exports[`compiler: transition > work with v-if 1`] = `
+"import { VaporTransition as _VaporTransition, createIf as _createIf, createComponent as _createComponent, template as _template } from 'vue';
+const t0 = _template("<h1>foo</h1>")
+
+export function render(_ctx) {
+ const n3 = _createComponent(_VaporTransition, null, {
+ "default": () => {
+ const n0 = _createIf(() => (_ctx.show), () => {
+ const n2 = t0()
+ n2.$key = 2
+ return n2
+ })
+ return n0
+ }
+ }, true)
+ return n3
+}"
+`;
} from './utils'
import type { CodegenContext } from '../generate'
import { genEffects, genOperations } from './operation'
- import { genChildren } from './template'
+ import { genChildren, genSelf } from './template'
import { toValidAssetId } from '@vue/compiler-dom'
+import { genExpression } from './expression'
export function genBlock(
oper: BlockIRNode,
customReturns?: (returns: CodeFragment[]) => 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) {
- genResolveAssets('component', 'resolveComponent')
+ for (let name of context.ir.component) {
+ const id = toValidAssetId(name, 'component')
+ const maybeSelfReference = name.endsWith('__self')
+ if (maybeSelfReference) name = name.slice(0, -6)
+ push(
+ NEWLINE,
+ `const ${id} = `,
+ ...genCall(
+ context.helper('resolveComponent'),
+ JSON.stringify(name),
+ // pass additional `maybeSelfReference` flag
+ maybeSelfReference ? 'true' : undefined,
+ ),
+ )
+ }
genResolveAssets('directive', 'resolveDirective')
}
const rawSlots = genRawSlots(slots, context)
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]
},
tempId: number
effect: IREffect[]
operation: OperationNode[]
- expressions: SimpleExpressionNode[]
returns: number[]
+ hasDeferredVShow: boolean
}
export interface RootIRNode {
children: IRDynamicInfo[]
template?: number
hasDynamicChild?: boolean
+ needsKey?: boolean
+ operation?: OperationNode
}
export interface IREffect {
effect: [],
operation: [],
returns: [],
- expressions: [],
tempId: 0,
+ hasDeferredVShow: false,
})
export function wrapTemplate(node: ElementNode, dirs: string[]): TemplateNode {
type SlotBlockIRNode,
type VaporDirectiveNode,
} from '../ir'
-import { findDir, resolveExpression } from '../utils'
+import {
+ findDir,
+ findProp,
+ isTransitionNode,
+ resolveExpression,
+} from '../utils'
+ import { markNonTemplate } from './transformText'
export const transformVSlot: NodeTransform = (node, context) => {
if (node.type !== NodeTypes.ELEMENT) return
) {
const { children } = node
const arg = dir && dir.arg
- const nonSlotTemplateChildren = children.filter(
- n =>
- isNonWhitespaceContent(node) &&
- !(n.type === NodeTypes.ELEMENT && n.props.some(isVSlot)),
- )
+
+ // whitespace: 'preserve'
+ const emptyTextNodes: TemplateChildNode[] = []
+ const nonSlotTemplateChildren = children.filter(n => {
+ if (isNonWhitespaceContent(n)) {
+ return !(n.type === NodeTypes.ELEMENT && n.props.some(isVSlot))
+ } else {
+ emptyTextNodes.push(n)
+ }
+ })
+ if (!nonSlotTemplateChildren.length) {
+ emptyTextNodes.forEach(n => {
+ markNonTemplate(n, context)
+ })
+ }
- const [block, onExit] = createSlotBlock(node, dir, context)
+ let slotKey
- if (isTransitionNode(node)) {
++ if (isTransitionNode(node) && nonSlotTemplateChildren.length) {
+ const keyProp = findProp(
+ nonSlotTemplateChildren[0] as ElementNode,
+ 'key',
+ ) as VaporDirectiveNode
+ if (keyProp) {
+ slotKey = keyProp.exp
+ }
+ }
+
+ const [block, onExit] = createSlotBlock(node, dir, context, slotKey)
const { slots } = context
* @internal
*/
export { initFeatureFlags } from './featureFlags'
+/**
+ * @internal
+ */
+export { performTransitionEnter, performTransitionLeave } from './renderer'
+ /**
+ * @internal
+ */
+ export { createInternalObject } from './internalObject'
transition
if (needTransition) {
if (moveType === MoveType.ENTER) {
- transition!.beforeEnter(el!)
- hostInsert(el!, container, anchor)
- queuePostRenderEffect(() => transition!.enter(el!), parentSuspense)
+ performTransitionEnter(
+ el!,
+ transition,
+ () => hostInsert(el!, container, anchor),
+ parentSuspense,
+ )
} else {
const { leave, delayLeave, afterLeave } = transition!
- const remove = () => hostInsert(el!, container, anchor)
+ const remove = () => {
+ if (vnode.ctx!.isUnmounted) {
+ hostRemove(el!)
+ } else {
+ hostInsert(el!, container, anchor)
+ }
+ }
const performLeave = () => {
leave(el!, () => {
remove()
movedChildren.forEach(c => {
const el = c.el as ElementWithTransition
- const style = el.style
- addTransitionClass(el, moveClass)
- style.transform = style.webkitTransform = style.transitionDuration = ''
- const cb = ((el as any)[moveCbKey] = (e: TransitionEvent) => {
- if (e && e.target !== el) {
- return
- }
- if (!e || /transform$/.test(e.propertyName)) {
- el.removeEventListener('transitionend', cb)
- ;(el as any)[moveCbKey] = null
- removeTransitionClass(el, moveClass)
- }
- })
- el.addEventListener('transitionend', cb)
+ handleMovedChildren(el, moveClass)
})
+ prevChildren = []
})
return () => {
-import { resolveDynamicComponent } from '@vue/runtime-dom'
+import { currentInstance, resolveDynamicComponent } from '@vue/runtime-dom'
- import { DynamicFragment, type VaporFragment } from './block'
+ import { DynamicFragment, type VaporFragment, insert } from './block'
-import { createComponentWithFallback } from './component'
+import { createComponentWithFallback, emptyContext } from './component'
import { renderEffect } from './renderEffect'
import type { RawProps } from './componentProps'
import type { RawSlots } from './componentSlots'
const frag = __DEV__
? new DynamicFragment('dynamic-component')
: new DynamicFragment()
+
renderEffect(() => {
const value = getter()
+ const appContext =
+ (currentInstance && currentInstance.appContext) || emptyContext
frag.update(
() =>
createComponentWithFallback(
import type { DynamicSlot } from './componentSlots'
import { renderEffect } from './renderEffect'
import { VaporVForFlags } from '../../shared/src/vaporFlags'
+import { applyTransitionHooks } from './components/Transition'
+ import { isHydrating, locateHydrationNode } from './dom/hydration'
+ import {
+ insertionAnchor,
+ insertionParent,
+ resetInsertionState,
+ } from './insertionState'
class ForBlock extends VaporFragment {
scope: EffectScope | undefined
getSlot,
} from './componentSlots'
import { hmrReload, hmrRerender } from './hmr'
+import { createElement } from './dom/node'
+ import { isHydrating, locateHydrationNode } from './dom/hydration'
+ import {
+ insertionAnchor,
+ insertionParent,
+ resetInsertionState,
+ } from './insertionState'
export { currentInstance } from '@vue/runtime-dom'
if (
instance.hasFallthrough &&
component.inheritAttrs !== false &&
- instance.block instanceof Element &&
Object.keys(instance.attrs).length
) {
- renderEffect(() =>
- applyFallthroughProps(instance.block as Element, instance.attrs),
- )
+ const el = getRootElement(instance)
+ if (el) {
- renderEffect(() => {
- isApplyingFallthroughProps = true
- setDynamicProps(el, [instance.attrs])
- isApplyingFallthroughProps = false
- })
++ renderEffect(() => applyFallthroughProps(el, instance.attrs))
+ }
}
resetTracking()
rawProps?: LooseRawProps | null,
rawSlots?: LooseRawSlots | null,
isSingleRoot?: boolean,
+ appContext?: GenericAppContext,
): HTMLElement | VaporComponentInstance {
if (!isString(comp)) {
- return createComponent(comp, rawProps, rawSlots, isSingleRoot)
+ return createComponent(comp, rawProps, rawSlots, isSingleRoot, appContext)
}
- const el = document.createElement(comp)
+ const el = createElement(comp)
+ const _insertionParent = insertionParent
+ const _insertionAnchor = insertionAnchor
+ if (isHydrating) {
+ locateHydrationNode()
+ } else {
+ resetInsertionState()
+ }
+
// mark single root
;(el as any).$root = isSingleRoot
- import {
- adoptHydrationNode,
- currentHydrationNode,
- isHydrating,
- } from './hydration'
+import { child, createElement, createTextNode } from './node'
+ import { adoptTemplate, currentHydrationNode, isHydrating } from './hydration'
-import { child, createTextNode } from './node'
let t: HTMLTemplateElement
type RendererInternals,
type ShallowRef,
type Slots,
+ type TransitionHooks,
type VNode,
type VaporInteropInterface,
+ createInternalObject,
createVNode,
currentInstance,
ensureRenderer,
+ isEmitListener,
onScopeDispose,
renderSlot,
+ setTransitionHooks as setVNodeTransitionHooks,
shallowRef,
simpleSetCurrentInstance,
} from '@vue/runtime-dom'