mirror of https://github.com/vuejs/core.git
199 lines
5.4 KiB
TypeScript
199 lines
5.4 KiB
TypeScript
/**
|
|
Runtime helper for applying directives to a vnode. Example usage:
|
|
|
|
const comp = resolveComponent('comp')
|
|
const foo = resolveDirective('foo')
|
|
const bar = resolveDirective('bar')
|
|
|
|
return withDirectives(h(comp), [
|
|
[foo, this.x],
|
|
[bar, this.y]
|
|
])
|
|
*/
|
|
|
|
import type { VNode } from './vnode'
|
|
import { EMPTY_OBJ, isBuiltInDirective, isFunction } from '@vue/shared'
|
|
import { warn } from './warning'
|
|
import {
|
|
type ComponentInternalInstance,
|
|
type Data,
|
|
getComponentPublicInstance,
|
|
} from './component'
|
|
import { currentRenderingInstance } from './componentRenderContext'
|
|
import { ErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
|
|
import type { ComponentPublicInstance } from './componentPublicInstance'
|
|
import { mapCompatDirectiveHook } from './compat/customDirective'
|
|
import { pauseTracking, resetTracking, traverse } from '@vue/reactivity'
|
|
|
|
export interface DirectiveBinding<
|
|
Value = any,
|
|
Modifiers extends string = string,
|
|
Arg extends string = string,
|
|
> {
|
|
instance: ComponentPublicInstance | Record<string, any> | null
|
|
value: Value
|
|
oldValue: Value | null
|
|
arg?: Arg
|
|
modifiers: DirectiveModifiers<Modifiers>
|
|
dir: ObjectDirective<any, Value>
|
|
}
|
|
|
|
export type DirectiveHook<
|
|
HostElement = any,
|
|
Prev = VNode<any, HostElement> | null,
|
|
Value = any,
|
|
Modifiers extends string = string,
|
|
Arg extends string = string,
|
|
> = (
|
|
el: HostElement,
|
|
binding: DirectiveBinding<Value, Modifiers, Arg>,
|
|
vnode: VNode<any, HostElement>,
|
|
prevVNode: Prev,
|
|
) => void
|
|
|
|
export type SSRDirectiveHook<
|
|
Value = any,
|
|
Modifiers extends string = string,
|
|
Arg extends string = string,
|
|
> = (
|
|
binding: DirectiveBinding<Value, Modifiers, Arg>,
|
|
vnode: VNode,
|
|
) => Data | undefined
|
|
|
|
export interface ObjectDirective<
|
|
HostElement = any,
|
|
Value = any,
|
|
Modifiers extends string = string,
|
|
Arg extends string = string,
|
|
> {
|
|
/**
|
|
* @internal without this, ts-expect-error in directives.test-d.ts somehow
|
|
* fails when running tsc, but passes in IDE and when testing against built
|
|
* dts. Could be a TS bug.
|
|
*/
|
|
__mod?: Modifiers
|
|
created?: DirectiveHook<HostElement, null, Value, Modifiers, Arg>
|
|
beforeMount?: DirectiveHook<HostElement, null, Value, Modifiers, Arg>
|
|
mounted?: DirectiveHook<HostElement, null, Value, Modifiers, Arg>
|
|
beforeUpdate?: DirectiveHook<
|
|
HostElement,
|
|
VNode<any, HostElement>,
|
|
Value,
|
|
Modifiers,
|
|
Arg
|
|
>
|
|
updated?: DirectiveHook<
|
|
HostElement,
|
|
VNode<any, HostElement>,
|
|
Value,
|
|
Modifiers,
|
|
Arg
|
|
>
|
|
beforeUnmount?: DirectiveHook<HostElement, null, Value, Modifiers, Arg>
|
|
unmounted?: DirectiveHook<HostElement, null, Value, Modifiers, Arg>
|
|
getSSRProps?: SSRDirectiveHook<Value, Modifiers, Arg>
|
|
deep?: boolean
|
|
}
|
|
|
|
export type FunctionDirective<
|
|
HostElement = any,
|
|
V = any,
|
|
Modifiers extends string = string,
|
|
Arg extends string = string,
|
|
> = DirectiveHook<HostElement, any, V, Modifiers, Arg>
|
|
|
|
export type Directive<
|
|
HostElement = any,
|
|
Value = any,
|
|
Modifiers extends string = string,
|
|
Arg extends string = string,
|
|
> =
|
|
| ObjectDirective<HostElement, Value, Modifiers, Arg>
|
|
| FunctionDirective<HostElement, Value, Modifiers, Arg>
|
|
|
|
export type DirectiveModifiers<K extends string = string> = Record<K, boolean>
|
|
|
|
export function validateDirectiveName(name: string): void {
|
|
if (isBuiltInDirective(name)) {
|
|
warn('Do not use built-in directive ids as custom directive id: ' + name)
|
|
}
|
|
}
|
|
|
|
// Directive, value, argument, modifiers
|
|
export type DirectiveArguments = Array<
|
|
| [Directive | undefined]
|
|
| [Directive | undefined, any]
|
|
| [Directive | undefined, any, string]
|
|
| [Directive | undefined, any, string | undefined, DirectiveModifiers]
|
|
>
|
|
|
|
/**
|
|
* Adds directives to a VNode.
|
|
*/
|
|
export function withDirectives<T extends VNode>(
|
|
vnode: T,
|
|
directives: DirectiveArguments,
|
|
): T {
|
|
if (currentRenderingInstance === null) {
|
|
__DEV__ && warn(`withDirectives can only be used inside render functions.`)
|
|
return vnode
|
|
}
|
|
const instance = getComponentPublicInstance(currentRenderingInstance)
|
|
const bindings: DirectiveBinding[] = vnode.dirs || (vnode.dirs = [])
|
|
for (let i = 0; i < directives.length; i++) {
|
|
let [dir, value, arg, modifiers = EMPTY_OBJ] = directives[i]
|
|
if (dir) {
|
|
if (isFunction(dir)) {
|
|
dir = {
|
|
mounted: dir,
|
|
updated: dir,
|
|
} as ObjectDirective
|
|
}
|
|
if (dir.deep) {
|
|
traverse(value)
|
|
}
|
|
bindings.push({
|
|
dir,
|
|
instance,
|
|
value,
|
|
oldValue: void 0,
|
|
arg,
|
|
modifiers,
|
|
})
|
|
}
|
|
}
|
|
return vnode
|
|
}
|
|
|
|
export function invokeDirectiveHook(
|
|
vnode: VNode,
|
|
prevVNode: VNode | null,
|
|
instance: ComponentInternalInstance | null,
|
|
name: keyof ObjectDirective,
|
|
): void {
|
|
const bindings = vnode.dirs!
|
|
const oldBindings = prevVNode && prevVNode.dirs!
|
|
for (let i = 0; i < bindings.length; i++) {
|
|
const binding = bindings[i]
|
|
if (oldBindings) {
|
|
binding.oldValue = oldBindings[i].value
|
|
}
|
|
let hook = binding.dir[name] as DirectiveHook | DirectiveHook[] | undefined
|
|
if (__COMPAT__ && !hook) {
|
|
hook = mapCompatDirectiveHook(name, binding.dir, instance)
|
|
}
|
|
if (hook) {
|
|
// disable tracking inside all lifecycle hooks
|
|
// since they can potentially be called inside effects.
|
|
pauseTracking()
|
|
callWithAsyncErrorHandling(hook, instance, ErrorCodes.DIRECTIVE_HOOK, [
|
|
vnode.el,
|
|
binding,
|
|
vnode,
|
|
prevVNode,
|
|
])
|
|
resetTracking()
|
|
}
|
|
}
|
|
}
|