2024-01-13 03:25:57 +08:00
|
|
|
import { NOOP, isFunction } from '@vue/shared'
|
2023-12-29 22:05:33 +08:00
|
|
|
import { type ComponentInternalInstance, currentInstance } from './component'
|
2024-01-13 03:25:57 +08:00
|
|
|
import { pauseTracking, resetTracking } from '@vue/reactivity'
|
|
|
|
import { VaporErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
|
|
|
|
import { renderWatch } from './renderWatch'
|
2023-12-07 01:51:57 +08:00
|
|
|
|
2023-12-07 10:46:34 +08:00
|
|
|
export type DirectiveModifiers<M extends string = string> = Record<M, boolean>
|
|
|
|
|
2023-12-11 16:23:39 +08:00
|
|
|
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
|
2023-12-11 16:23:39 +08:00
|
|
|
arg?: string
|
2023-12-07 10:46:34 +08:00
|
|
|
modifiers?: DirectiveModifiers<M>
|
2023-12-03 18:36:01 +08:00
|
|
|
dir: ObjectDirective<any, V>
|
|
|
|
}
|
|
|
|
|
2023-12-07 10:46:34 +08:00
|
|
|
export type DirectiveHook<
|
|
|
|
T = any | null,
|
|
|
|
V = any,
|
|
|
|
M extends string = string,
|
2023-12-11 16:23:39 +08:00
|
|
|
> = (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'
|
2024-01-13 03:25:57 +08:00
|
|
|
| 'beforeUpdate'
|
2023-12-08 17:34:33 +08:00
|
|
|
| 'updated'
|
|
|
|
| 'beforeUnmount'
|
|
|
|
| 'unmounted'
|
2023-12-11 16:23:39 +08:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2023-12-07 10:46:34 +08:00
|
|
|
export type FunctionDirective<
|
|
|
|
T = any,
|
|
|
|
V = any,
|
|
|
|
M extends string = string,
|
2023-12-11 16:23:39 +08:00
|
|
|
> = DirectiveHook<T, V, M>
|
2023-12-07 10:46:34 +08:00
|
|
|
|
2023-12-11 16:23:39 +08:00
|
|
|
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]
|
2023-12-07 01:41:17 +08:00
|
|
|
| [
|
|
|
|
Directive | undefined,
|
2023-12-08 17:34:33 +08:00
|
|
|
value: () => any,
|
2023-12-07 01:41:17 +08:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2023-12-12 15:39:00 +08:00
|
|
|
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 = {
|
2023-12-13 15:01:07 +08:00
|
|
|
mounted: dir,
|
|
|
|
updated: dir,
|
2023-12-03 18:36:01 +08:00
|
|
|
} satisfies ObjectDirective
|
|
|
|
}
|
|
|
|
|
|
|
|
const binding: DirectiveBinding = {
|
|
|
|
dir,
|
2023-12-12 15:39:00 +08:00
|
|
|
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,
|
2023-12-07 01:41:17 +08:00
|
|
|
modifiers,
|
2023-12-03 18:36:01 +08:00
|
|
|
}
|
|
|
|
bindings.push(binding)
|
2023-12-08 17:34:33 +08:00
|
|
|
|
2024-01-13 03:25:57 +08:00
|
|
|
callDirectiveHook(node, binding, instance, 'created')
|
2023-12-12 15:39:00 +08:00
|
|
|
|
2024-01-13 03:25:57 +08:00
|
|
|
// register source
|
|
|
|
if (source) {
|
2024-01-19 17:09:57 +08:00
|
|
|
// callback will be overridden by middleware
|
2024-01-13 03:25:57 +08:00
|
|
|
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) {
|
2024-01-13 03:25:57 +08:00
|
|
|
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,
|
2024-01-13 03:25:57 +08:00
|
|
|
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
|
2024-01-13 03:25:57 +08:00
|
|
|
// 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
|
|
|
}
|