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