Skip to content

Commit

Permalink
feat: custom directive v2
Browse files Browse the repository at this point in the history
  • Loading branch information
sxzz committed Sep 19, 2024
1 parent 884c190 commit ef8c4f6
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 51 deletions.
96 changes: 63 additions & 33 deletions packages/runtime-vapor/src/directives.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,37 @@
import { isBuiltInDirective } from '@vue/shared'
import { type ComponentInternalInstance, currentInstance } from './component'
import {
type ComponentInternalInstance,
currentInstance,
isVaporComponent,
} from './component'
import { warn } from './warning'
import { normalizeBlock } from './dom/element'
import { getCurrentScope } from '@vue/reactivity'
import { VaporErrorCodes, callWithAsyncErrorHandling } from './errorHandling'

export type DirectiveModifiers<M extends string = string> = Record<M, boolean>

export interface DirectiveBinding<T = any, V = any, M extends string = string> {
instance: ComponentInternalInstance
source?: () => V
value: V
oldValue: V | null
arg?: string
modifiers?: DirectiveModifiers<M>
dir: ObjectDirective<T, V, M>
dir: Directive<T, V, M>
}

export type DirectiveBindingsMap = Map<Node, DirectiveBinding[]>

export type DirectiveHook<
T = any | null,
V = any,
M extends string = string,
> = (node: T, binding: DirectiveBinding<T, V, M>) => void

// create node -> `created` -> node operation -> `beforeMount` -> node mounted -> `mounted`
// effect update -> `beforeUpdate` -> node updated -> `updated`
// `beforeUnmount`-> node unmount -> `unmounted`
export type DirectiveHookName =
| 'created'
| 'beforeMount'
| 'mounted'
| 'beforeUpdate'
| 'updated'
| 'beforeUnmount'
| 'unmounted'
export type ObjectDirective<T = any, V = any, M extends string = string> = {
[K in DirectiveHookName]?: DirectiveHook<T, V, M> | undefined
} & {
/** Watch value deeply */
deep?: boolean | number
}

export type FunctionDirective<
export type Directive<
T = any,
V = any,
M extends string = string,
> = DirectiveHook<T, V, M>

export type Directive<T = any, V = any, M extends string = string> =
| ObjectDirective<T, V, M>
| FunctionDirective<T, V, M>
export type DirectiveHook<
T = any | null,
V = any,
M extends string = string,
> = (node: T, binding: DirectiveBinding<T, V, M>) => void

export function validateDirectiveName(name: string): void {
if (isBuiltInDirective(name)) {
Expand Down Expand Up @@ -77,7 +60,54 @@ export function withDirectives<T extends ComponentInternalInstance | Node>(
return nodeOrComponent
}

// NOOP
let node: Node
if (isVaporComponent(nodeOrComponent)) {
const root = getComponentNode(nodeOrComponent)
if (!root) return nodeOrComponent
node = root
} else {
node = nodeOrComponent
}

const instance = currentInstance!
const parentScope = getCurrentScope()

if (__DEV__ && !parentScope) {
warn(`Directives should be used inside of RenderEffectScope.`)
}

for (const directive of directives) {
let [dir, source, arg, modifiers] = directive
if (!dir) continue

const binding: DirectiveBinding = {
dir,
source,
instance,
arg,
modifiers,
}

callWithAsyncErrorHandling(dir, instance, VaporErrorCodes.DIRECTIVE_HOOK, [
node,
binding,
])
}

return nodeOrComponent
}

function getComponentNode(component: ComponentInternalInstance) {
if (!component.block) return

const nodes = normalizeBlock(component.block)
if (nodes.length !== 1) {
warn(
`Runtime directive used on component with non-element root node. ` +
`The directives will not function as intended.`,
)
return
}

return nodes[0]
}
41 changes: 25 additions & 16 deletions packages/runtime-vapor/src/directives/vShow.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
import type { ObjectDirective } from '../directives'
import { onBeforeMount } from '../apiLifecycle'
import type { Directive } from '../directives'
import { renderEffect } from '../renderEffect'

const vShowMap = new WeakMap<HTMLElement, string>()
export const vShowOriginalDisplay: unique symbol = Symbol('_vod')
export const vShowHidden: unique symbol = Symbol('_vsh')

export const vShow: ObjectDirective<HTMLElement> = {
beforeMount(node, { value }) {
vShowMap.set(node, node.style.display === 'none' ? '' : node.style.display)
setDisplay(node, value)
},
export interface VShowElement extends HTMLElement {
// _vod = vue original display
[vShowOriginalDisplay]: string
[vShowHidden]: boolean
}

export const vShow: Directive<VShowElement> = (node, { source }) => {
function getValue(): boolean {
return source ? source() : false
}

updated(node, { value, oldValue }) {
if (!value === !oldValue) return
setDisplay(node, value)
},
onBeforeMount(() => {
node[vShowOriginalDisplay] =
node.style.display === 'none' ? '' : node.style.display
})

beforeUnmount(node, { value }) {
setDisplay(node, value)
},
renderEffect(() => {
setDisplay(node, getValue())
})
}

function setDisplay(el: HTMLElement, value: unknown): void {
el.style.display = value ? vShowMap.get(el)! : 'none'
function setDisplay(el: VShowElement, value: unknown): void {
el.style.display = value ? el[vShowOriginalDisplay] : 'none'
el[vShowHidden] = !value
}
2 changes: 0 additions & 2 deletions packages/runtime-vapor/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,6 @@ export {
type Directive,
type DirectiveBinding,
type DirectiveHook,
type ObjectDirective,
type FunctionDirective,
type DirectiveArguments,
type DirectiveModifiers,
} from './directives'
Expand Down

0 comments on commit ef8c4f6

Please sign in to comment.