feat: v-show
This commit is contained in:
parent
71bc13575f
commit
5f90f8bef0
|
@ -35,7 +35,7 @@ export function render(_ctx) {
|
||||||
const t0 = _template("<div></div>")
|
const t0 = _template("<div></div>")
|
||||||
const n0 = t0()
|
const n0 = t0()
|
||||||
const { 0: [n1],} = _children(n0)
|
const { 0: [n1],} = _children(n0)
|
||||||
_withDirectives(n1, [[_ctx.vExample, _ctx.msg]])
|
_withDirectives(n1, [[_ctx.vExample, () => _ctx.msg]])
|
||||||
return n0
|
return n0
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -47,7 +47,7 @@ export function render(_ctx) {
|
||||||
const t0 = _template("<div></div>")
|
const t0 = _template("<div></div>")
|
||||||
const n0 = t0()
|
const n0 = t0()
|
||||||
const { 0: [n1],} = _children(n0)
|
const { 0: [n1],} = _children(n0)
|
||||||
_withDirectives(n1, [[_ctx.vExample, _ctx.msg, _ctx.foo]])
|
_withDirectives(n1, [[_ctx.vExample, () => _ctx.msg, _ctx.foo]])
|
||||||
return n0
|
return n0
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -59,7 +59,7 @@ export function render(_ctx) {
|
||||||
const t0 = _template("<div></div>")
|
const t0 = _template("<div></div>")
|
||||||
const n0 = t0()
|
const n0 = t0()
|
||||||
const { 0: [n1],} = _children(n0)
|
const { 0: [n1],} = _children(n0)
|
||||||
_withDirectives(n1, [[_ctx.vExample, _ctx.msg, void 0, { bar: true }]])
|
_withDirectives(n1, [[_ctx.vExample, () => _ctx.msg, void 0, { bar: true }]])
|
||||||
return n0
|
return n0
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -83,7 +83,7 @@ export function render(_ctx) {
|
||||||
const t0 = _template("<div></div>")
|
const t0 = _template("<div></div>")
|
||||||
const n0 = t0()
|
const n0 = t0()
|
||||||
const { 0: [n1],} = _children(n0)
|
const { 0: [n1],} = _children(n0)
|
||||||
_withDirectives(n1, [[_ctx.vExample, _ctx.msg, "foo"]])
|
_withDirectives(n1, [[_ctx.vExample, () => _ctx.msg, "foo"]])
|
||||||
return n0
|
return n0
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -95,7 +95,7 @@ export function render(_ctx) {
|
||||||
const t0 = _template("<div></div>")
|
const t0 = _template("<div></div>")
|
||||||
const n0 = t0()
|
const n0 = t0()
|
||||||
const { 0: [n1],} = _children(n0)
|
const { 0: [n1],} = _children(n0)
|
||||||
_withDirectives(n1, [[_ctx.vExample, _ctx.msg, "foo", { bar: true }]])
|
_withDirectives(n1, [[_ctx.vExample, () => _ctx.msg, "foo", { bar: true }]])
|
||||||
return n0
|
return n0
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -20,6 +20,7 @@ import { transformVHtml } from './transforms/vHtml'
|
||||||
import { transformVText } from './transforms/vText'
|
import { transformVText } from './transforms/vText'
|
||||||
import { transformVBind } from './transforms/vBind'
|
import { transformVBind } from './transforms/vBind'
|
||||||
import { transformVOn } from './transforms/vOn'
|
import { transformVOn } from './transforms/vOn'
|
||||||
|
import { transformVShow } from './transforms/vShow'
|
||||||
import { transformInterpolation } from './transforms/transformInterpolation'
|
import { transformInterpolation } from './transforms/transformInterpolation'
|
||||||
import type { HackOptions } from './ir'
|
import type { HackOptions } from './ir'
|
||||||
|
|
||||||
|
@ -97,6 +98,7 @@ export function getBaseTransformPreset(
|
||||||
on: transformVOn,
|
on: transformVOn,
|
||||||
html: transformVHtml,
|
html: transformVHtml,
|
||||||
text: transformVText,
|
text: transformVText,
|
||||||
|
show: transformVShow,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ import {
|
||||||
IRNodeTypes,
|
IRNodeTypes,
|
||||||
} from './ir'
|
} from './ir'
|
||||||
import { SourceMapGenerator } from 'source-map-js'
|
import { SourceMapGenerator } from 'source-map-js'
|
||||||
import { camelize, isString } from '@vue/shared'
|
import { camelize, isString, makeMap } from '@vue/shared'
|
||||||
import type { Identifier } from '@babel/types'
|
import type { Identifier } from '@babel/types'
|
||||||
|
|
||||||
// remove when stable
|
// remove when stable
|
||||||
|
@ -475,16 +475,20 @@ function genWithDirective(oper: WithDirectiveIRNode, context: CodegenContext) {
|
||||||
// TODO merge directive for the same node
|
// TODO merge directive for the same node
|
||||||
pushWithNewline(`${vaporHelper('withDirectives')}(n${oper.element}, [[`)
|
pushWithNewline(`${vaporHelper('withDirectives')}(n${oper.element}, [[`)
|
||||||
|
|
||||||
// TODO resolve directive
|
if (dir.name === 'show') {
|
||||||
const directiveReference = camelize(`v-${dir.name}`)
|
push(vaporHelper('vShow'))
|
||||||
if (bindingMetadata[directiveReference]) {
|
} else {
|
||||||
const directiveExpression = createSimpleExpression(directiveReference)
|
const directiveReference = camelize(`v-${dir.name}`)
|
||||||
directiveExpression.ast = null
|
// TODO resolve directive
|
||||||
genExpression(directiveExpression, context)
|
if (bindingMetadata[directiveReference]) {
|
||||||
|
const directiveExpression = createSimpleExpression(directiveReference)
|
||||||
|
directiveExpression.ast = null
|
||||||
|
genExpression(directiveExpression, context)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dir.exp) {
|
if (dir.exp) {
|
||||||
push(', ')
|
push(', () => ')
|
||||||
genExpression(dir.exp, context)
|
genExpression(dir.exp, context)
|
||||||
} else if (dir.arg || dir.modifiers.length) {
|
} else if (dir.arg || dir.modifiers.length) {
|
||||||
push(', void 0')
|
push(', void 0')
|
||||||
|
@ -512,6 +516,8 @@ function genArrayExpression(elements: string[]) {
|
||||||
return `[${elements.map((it) => JSON.stringify(it)).join(', ')}]`
|
return `[${elements.map((it) => JSON.stringify(it)).join(', ')}]`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isLiteralWhitelisted = /*#__PURE__*/ makeMap('true,false,null,this')
|
||||||
|
|
||||||
function genExpression(node: IRExpression, context: CodegenContext): void {
|
function genExpression(node: IRExpression, context: CodegenContext): void {
|
||||||
const { push } = context
|
const { push } = context
|
||||||
if (isString(node)) return push(node)
|
if (isString(node)) return push(node)
|
||||||
|
@ -525,7 +531,8 @@ function genExpression(node: IRExpression, context: CodegenContext): void {
|
||||||
!context.prefixIdentifiers ||
|
!context.prefixIdentifiers ||
|
||||||
!node.content.trim() ||
|
!node.content.trim() ||
|
||||||
// there was a parsing error
|
// there was a parsing error
|
||||||
ast === false
|
ast === false ||
|
||||||
|
isLiteralWhitelisted(rawExpr)
|
||||||
) {
|
) {
|
||||||
return push(rawExpr, NewlineType.None, loc)
|
return push(rawExpr, NewlineType.None, loc)
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,7 +74,7 @@ function transformProp(
|
||||||
type: IRNodeTypes.WITH_DIRECTIVE,
|
type: IRNodeTypes.WITH_DIRECTIVE,
|
||||||
element: context.reference(),
|
element: context.reference(),
|
||||||
dir: prop,
|
dir: prop,
|
||||||
loc: loc,
|
loc,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { DOMErrorCodes, createDOMCompilerError } from '@vue/compiler-dom'
|
||||||
|
import type { DirectiveTransform } from '../transform'
|
||||||
|
import { IRNodeTypes } from '../ir'
|
||||||
|
|
||||||
|
export const transformVShow: DirectiveTransform = (dir, node, context) => {
|
||||||
|
const { exp, loc } = dir
|
||||||
|
if (!exp) {
|
||||||
|
context.options.onError(
|
||||||
|
createDOMCompilerError(DOMErrorCodes.X_V_SHOW_NO_EXPRESSION, loc),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
context.registerOperation({
|
||||||
|
type: IRNodeTypes.WITH_DIRECTIVE,
|
||||||
|
element: context.reference(),
|
||||||
|
dir,
|
||||||
|
loc,
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import { EffectScope } from '@vue/reactivity'
|
import { type Ref, EffectScope, ref } from '@vue/reactivity'
|
||||||
import { Block } from './render'
|
import type { Block } from './render'
|
||||||
import { DirectiveBinding } from './directives'
|
import type { DirectiveBinding } from './directive'
|
||||||
import type { Data } from '@vue/shared'
|
import type { Data } from '@vue/shared'
|
||||||
|
|
||||||
export type SetupFn = (props: any, ctx: any) => Block | Data
|
export type SetupFn = (props: any, ctx: any) => Block | Data
|
||||||
|
@ -19,7 +19,8 @@ export interface ComponentInternalInstance {
|
||||||
scope: EffectScope
|
scope: EffectScope
|
||||||
|
|
||||||
component: FunctionalComponent | ObjectComponent
|
component: FunctionalComponent | ObjectComponent
|
||||||
isMounted: boolean
|
get isMounted(): boolean
|
||||||
|
isMountedRef: Ref<boolean>
|
||||||
|
|
||||||
/** directives */
|
/** directives */
|
||||||
dirs: Map<Node, DirectiveBinding[]>
|
dirs: Map<Node, DirectiveBinding[]>
|
||||||
|
@ -44,6 +45,7 @@ let uid = 0
|
||||||
export const createComponentInstance = (
|
export const createComponentInstance = (
|
||||||
component: ObjectComponent | FunctionalComponent,
|
component: ObjectComponent | FunctionalComponent,
|
||||||
): ComponentInternalInstance => {
|
): ComponentInternalInstance => {
|
||||||
|
const isMountedRef = ref(false)
|
||||||
const instance: ComponentInternalInstance = {
|
const instance: ComponentInternalInstance = {
|
||||||
uid: uid++,
|
uid: uid++,
|
||||||
block: null,
|
block: null,
|
||||||
|
@ -51,7 +53,10 @@ export const createComponentInstance = (
|
||||||
scope: new EffectScope(true /* detached */)!,
|
scope: new EffectScope(true /* detached */)!,
|
||||||
|
|
||||||
component,
|
component,
|
||||||
isMounted: false,
|
get isMounted() {
|
||||||
|
return isMountedRef.value
|
||||||
|
},
|
||||||
|
isMountedRef,
|
||||||
|
|
||||||
dirs: new Map(),
|
dirs: new Map(),
|
||||||
// TODO: registory of provides, appContext, lifecycles, ...
|
// TODO: registory of provides, appContext, lifecycles, ...
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { isFunction } from '@vue/shared'
|
import { isFunction } from '@vue/shared'
|
||||||
import { currentInstance, type ComponentInternalInstance } from './component'
|
import { currentInstance, type ComponentInternalInstance } from './component'
|
||||||
|
import { effect } from './scheduler'
|
||||||
|
|
||||||
export type DirectiveModifiers<M extends string = string> = Record<M, boolean>
|
export type DirectiveModifiers<M extends string = string> = Record<M, boolean>
|
||||||
|
|
||||||
|
@ -9,6 +10,7 @@ export interface DirectiveBinding<
|
||||||
M extends string = string,
|
M extends string = string,
|
||||||
> {
|
> {
|
||||||
instance: ComponentInternalInstance | null
|
instance: ComponentInternalInstance | null
|
||||||
|
source?: () => V
|
||||||
value: V
|
value: V
|
||||||
oldValue: V | null
|
oldValue: V | null
|
||||||
arg?: A
|
arg?: A
|
||||||
|
@ -26,23 +28,24 @@ export type DirectiveHook<
|
||||||
// create node -> `created` -> node operation -> `beforeMount` -> node mounted -> `mounted`
|
// create node -> `created` -> node operation -> `beforeMount` -> node mounted -> `mounted`
|
||||||
// effect update -> `beforeUpdate` -> node updated -> `updated`
|
// effect update -> `beforeUpdate` -> node updated -> `updated`
|
||||||
// `beforeUnmount`-> node unmount -> `unmounted`
|
// `beforeUnmount`-> node unmount -> `unmounted`
|
||||||
export interface ObjectDirective<
|
export type DirectiveHookName =
|
||||||
|
| 'created'
|
||||||
|
| 'beforeMount'
|
||||||
|
| 'mounted'
|
||||||
|
// | 'beforeUpdate'
|
||||||
|
| 'updated'
|
||||||
|
| 'beforeUnmount'
|
||||||
|
| 'unmounted'
|
||||||
|
export type ObjectDirective<
|
||||||
T = any,
|
T = any,
|
||||||
V = any,
|
V = any,
|
||||||
A = string,
|
A = string,
|
||||||
M extends string = string,
|
M extends string = string,
|
||||||
> {
|
> = {
|
||||||
created?: DirectiveHook<T, V, A, M>
|
[K in DirectiveHookName]?: DirectiveHook<T, V, A, M> | undefined
|
||||||
beforeMount?: DirectiveHook<T, V, A, M>
|
} & {
|
||||||
mounted?: DirectiveHook<T, V, A, M>
|
deep?: boolean
|
||||||
// beforeUpdate?: DirectiveHook<T, V,A,M>
|
|
||||||
// updated?: DirectiveHook<T, V,A,M>
|
|
||||||
beforeUnmount?: DirectiveHook<T, V, A, M>
|
|
||||||
unmounted?: DirectiveHook<T, V, A, M>
|
|
||||||
// getSSRProps?: SSRDirectiveHook
|
|
||||||
// deep?: boolean
|
|
||||||
}
|
}
|
||||||
export type DirectiveHookName = Exclude<keyof ObjectDirective, 'deep'>
|
|
||||||
|
|
||||||
export type FunctionDirective<
|
export type FunctionDirective<
|
||||||
T = any,
|
T = any,
|
||||||
|
@ -60,11 +63,11 @@ export type Directive<
|
||||||
|
|
||||||
export type DirectiveArguments = Array<
|
export type DirectiveArguments = Array<
|
||||||
| [Directive | undefined]
|
| [Directive | undefined]
|
||||||
| [Directive | undefined, value: any]
|
| [Directive | undefined, () => any]
|
||||||
| [Directive | undefined, value: any, argument: string]
|
| [Directive | undefined, () => any, argument: string]
|
||||||
| [
|
| [
|
||||||
Directive | undefined,
|
Directive | undefined,
|
||||||
value: any,
|
value: () => any,
|
||||||
argument: string,
|
argument: string,
|
||||||
modifiers: DirectiveModifiers,
|
modifiers: DirectiveModifiers,
|
||||||
]
|
]
|
||||||
|
@ -83,7 +86,7 @@ export function withDirectives<T extends Node>(
|
||||||
const bindings = currentInstance.dirs.get(node)!
|
const bindings = currentInstance.dirs.get(node)!
|
||||||
|
|
||||||
for (const directive of directives) {
|
for (const directive of directives) {
|
||||||
let [dir, value, arg, modifiers] = directive
|
let [dir, source, arg, modifiers] = directive
|
||||||
if (!dir) continue
|
if (!dir) continue
|
||||||
if (isFunction(dir)) {
|
if (isFunction(dir)) {
|
||||||
// TODO function directive
|
// TODO function directive
|
||||||
|
@ -95,13 +98,19 @@ export function withDirectives<T extends Node>(
|
||||||
const binding: DirectiveBinding = {
|
const binding: DirectiveBinding = {
|
||||||
dir,
|
dir,
|
||||||
instance: currentInstance,
|
instance: currentInstance,
|
||||||
value,
|
source,
|
||||||
oldValue: void 0,
|
value: null, // set later
|
||||||
|
oldValue: null,
|
||||||
arg,
|
arg,
|
||||||
modifiers,
|
modifiers,
|
||||||
}
|
}
|
||||||
if (dir.created) dir.created(node, binding)
|
|
||||||
bindings.push(binding)
|
bindings.push(binding)
|
||||||
|
|
||||||
|
callDirectiveHook(node, binding, 'created')
|
||||||
|
effect(() => {
|
||||||
|
if (!currentInstance!.isMountedRef.value) return
|
||||||
|
callDirectiveHook(node, binding, 'updated')
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return node
|
return node
|
||||||
|
@ -113,14 +122,28 @@ export function invokeDirectiveHook(
|
||||||
nodes?: IterableIterator<Node>,
|
nodes?: IterableIterator<Node>,
|
||||||
) {
|
) {
|
||||||
if (!instance) return
|
if (!instance) return
|
||||||
if (!nodes) {
|
nodes = nodes || instance.dirs.keys()
|
||||||
nodes = instance.dirs.keys()
|
|
||||||
}
|
|
||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
const directives = instance.dirs.get(node) || []
|
const directives = instance.dirs.get(node) || []
|
||||||
for (const binding of directives) {
|
for (const binding of directives) {
|
||||||
const hook = binding.dir[name]
|
callDirectiveHook(node, binding, name)
|
||||||
hook && hook(node, binding)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function callDirectiveHook(
|
||||||
|
node: Node,
|
||||||
|
binding: DirectiveBinding,
|
||||||
|
name: DirectiveHookName,
|
||||||
|
) {
|
||||||
|
const { dir } = binding
|
||||||
|
const hook = dir[name]
|
||||||
|
if (!hook) return
|
||||||
|
|
||||||
|
const newValue = binding.source ? binding.source() : undefined
|
||||||
|
if (name === 'updated' && binding.value === newValue) return
|
||||||
|
|
||||||
|
binding.oldValue = binding.value
|
||||||
|
binding.value = newValue
|
||||||
|
hook(node, binding)
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
import type { ObjectDirective } from '../directive'
|
||||||
|
|
||||||
|
const vShowMap = new WeakMap<HTMLElement, string>()
|
||||||
|
|
||||||
|
export const vShow: ObjectDirective<HTMLElement> = {
|
||||||
|
beforeMount(node, { source: value }) {
|
||||||
|
vShowMap.set(node, node.style.display === 'none' ? '' : node.style.display)
|
||||||
|
setDisplay(node, value)
|
||||||
|
},
|
||||||
|
|
||||||
|
updated(node, { value, oldValue }) {
|
||||||
|
if (!value === !oldValue) return
|
||||||
|
setDisplay(node, value)
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeUnmount(node, { source: value }) {
|
||||||
|
setDisplay(node, value)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
function setDisplay(el: HTMLElement, value: unknown): void {
|
||||||
|
el.style.display = value ? vShowMap.get(el)! : 'none'
|
||||||
|
}
|
|
@ -41,5 +41,6 @@ export * from './on'
|
||||||
export * from './render'
|
export * from './render'
|
||||||
export * from './template'
|
export * from './template'
|
||||||
export * from './scheduler'
|
export * from './scheduler'
|
||||||
export * from './directives'
|
export * from './directive'
|
||||||
export * from './dom'
|
export * from './dom'
|
||||||
|
export * from './directives/vShow'
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
setCurrentInstance,
|
setCurrentInstance,
|
||||||
unsetCurrentInstance,
|
unsetCurrentInstance,
|
||||||
} from './component'
|
} from './component'
|
||||||
import { invokeDirectiveHook } from './directives'
|
import { invokeDirectiveHook } from './directive'
|
||||||
import { insert, remove } from './dom'
|
import { insert, remove } from './dom'
|
||||||
|
|
||||||
export type Block = Node | Fragment | Block[]
|
export type Block = Node | Fragment | Block[]
|
||||||
|
@ -56,7 +56,7 @@ export function mountComponent(
|
||||||
|
|
||||||
invokeDirectiveHook(instance, 'beforeMount')
|
invokeDirectiveHook(instance, 'beforeMount')
|
||||||
insert(block, instance.container)
|
insert(block, instance.container)
|
||||||
instance.isMounted = true
|
instance.isMountedRef.value = true
|
||||||
invokeDirectiveHook(instance, 'mounted')
|
invokeDirectiveHook(instance, 'mounted')
|
||||||
|
|
||||||
// TODO: lifecycle hooks (mounted, ...)
|
// TODO: lifecycle hooks (mounted, ...)
|
||||||
|
@ -70,7 +70,7 @@ export function unmountComponent(instance: ComponentInternalInstance) {
|
||||||
invokeDirectiveHook(instance, 'beforeUnmount')
|
invokeDirectiveHook(instance, 'beforeUnmount')
|
||||||
scope.stop()
|
scope.stop()
|
||||||
block && remove(block, container)
|
block && remove(block, container)
|
||||||
instance.isMounted = false
|
instance.isMountedRef.value = false
|
||||||
invokeDirectiveHook(instance, 'unmounted')
|
invokeDirectiveHook(instance, 'unmounted')
|
||||||
unsetCurrentInstance()
|
unsetCurrentInstance()
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from '@vue/vapor'
|
||||||
|
|
||||||
|
const visible = ref(true)
|
||||||
|
function handleClick() {
|
||||||
|
visible.value = !visible.value
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<button @click="handleClick">toggle</button>
|
||||||
|
<h1 v-show="visible">hello world</h1>
|
||||||
|
</template>
|
Loading…
Reference in New Issue