vue3-core/packages/runtime-vapor/src/directive.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

147 lines
3.7 KiB
TypeScript
Raw Normal View History

import { NOOP, isFunction } from '@vue/shared'
2023-12-29 22:05:33 +08:00
import { type ComponentInternalInstance, currentInstance } from './component'
import { pauseTracking, resetTracking } from '@vue/reactivity'
import { VaporErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
import { renderWatch } from './renderWatch'
2023-12-07 01:51:57 +08:00
export type DirectiveModifiers<M extends string = string> = Record<M, boolean>
export interface DirectiveBinding<V = any, M extends string = string> {
2024-01-21 02:16:30 +08:00
instance: ComponentInternalInstance
2023-12-08 17:34:33 +08:00
source?: () => V
2023-12-03 18:36:01 +08:00
value: V
oldValue: V | null
arg?: string
modifiers?: DirectiveModifiers<M>
2023-12-03 18:36:01 +08:00
dir: ObjectDirective<any, V>
}
export type DirectiveHook<
T = any | null,
V = any,
M extends string = string,
> = (node: T, binding: DirectiveBinding<V, M>) => void
2023-12-03 18:36:01 +08:00
// create node -> `created` -> node operation -> `beforeMount` -> node mounted -> `mounted`
// effect update -> `beforeUpdate` -> node updated -> `updated`
// `beforeUnmount`-> node unmount -> `unmounted`
2023-12-08 17:34:33 +08:00
export type DirectiveHookName =
| 'created'
| 'beforeMount'
| 'mounted'
| 'beforeUpdate'
2023-12-08 17:34:33 +08:00
| 'updated'
| 'beforeUnmount'
| 'unmounted'
export type ObjectDirective<T = any, V = any, M extends string = string> = {
[K in DirectiveHookName]?: DirectiveHook<T, V, M> | undefined
2023-12-08 17:34:33 +08:00
} & {
deep?: boolean
2023-12-03 18:36:01 +08:00
}
export type FunctionDirective<
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>
2023-12-03 18:36:01 +08:00
export type DirectiveArguments = Array<
| [Directive | undefined]
2023-12-08 17:34:33 +08:00
| [Directive | undefined, () => any]
| [Directive | undefined, () => any, argument: string]
| [
Directive | undefined,
2023-12-08 17:34:33 +08:00
value: () => any,
argument: string,
modifiers: DirectiveModifiers,
]
2023-12-03 18:36:01 +08:00
>
export function withDirectives<T extends Node>(
node: T,
directives: DirectiveArguments,
): T {
if (!currentInstance) {
// TODO warning
return node
}
const instance = currentInstance
if (!instance.dirs.has(node)) instance.dirs.set(node, [])
const bindings = instance.dirs.get(node)!
2023-12-03 18:36:01 +08:00
for (const directive of directives) {
2023-12-08 17:34:33 +08:00
let [dir, source, arg, modifiers] = directive
2023-12-03 18:36:01 +08:00
if (!dir) continue
if (isFunction(dir)) {
dir = {
mounted: dir,
updated: dir,
2023-12-03 18:36:01 +08:00
} satisfies ObjectDirective
}
const binding: DirectiveBinding = {
dir,
instance,
2023-12-08 17:34:33 +08:00
source,
value: null, // set later
oldValue: null,
2023-12-03 18:36:01 +08:00
arg,
modifiers,
2023-12-03 18:36:01 +08:00
}
bindings.push(binding)
2023-12-08 17:34:33 +08:00
callDirectiveHook(node, binding, instance, 'created')
// register source
if (source) {
2024-01-19 17:09:57 +08:00
// callback will be overridden by middleware
renderWatch(source, NOOP)
}
2023-12-03 18:36:01 +08:00
}
return node
}
2023-12-04 16:08:15 +08:00
export function invokeDirectiveHook(
instance: ComponentInternalInstance | null,
name: DirectiveHookName,
nodes?: IterableIterator<Node>,
) {
if (!instance) return
2023-12-08 17:34:33 +08:00
nodes = nodes || instance.dirs.keys()
2023-12-04 16:08:15 +08:00
for (const node of nodes) {
const directives = instance.dirs.get(node) || []
for (const binding of directives) {
callDirectiveHook(node, binding, instance, name)
2023-12-04 16:08:15 +08:00
}
}
}
2023-12-08 17:34:33 +08:00
function callDirectiveHook(
node: Node,
binding: DirectiveBinding,
instance: ComponentInternalInstance | null,
2023-12-08 17:34:33 +08:00
name: DirectiveHookName,
) {
const { dir } = binding
const hook = dir[name]
if (!hook) return
const newValue = binding.source ? binding.source() : undefined
binding.value = newValue
// disable tracking inside all lifecycle hooks
// since they can potentially be called inside effects.
pauseTracking()
callWithAsyncErrorHandling(hook, instance, VaporErrorCodes.DIRECTIVE_HOOK, [
node,
binding,
])
resetTracking()
if (name !== 'beforeUpdate') binding.oldValue = binding.value
2023-12-08 17:34:33 +08:00
}