/** 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 } from '@vue/reactivity' import { traverse } from './apiWatch' export interface DirectiveBinding< Value = any, Modifiers extends string = string, Arg extends string = string, > { instance: ComponentPublicInstance | Record | null value: Value oldValue: Value | null arg?: Arg modifiers: DirectiveModifiers dir: ObjectDirective } export type DirectiveHook< HostElement = any, Prev = VNode | null, Value = any, Modifiers extends string = string, Arg extends string = string, > = ( el: HostElement, binding: DirectiveBinding, vnode: VNode, prevVNode: Prev, ) => void export type SSRDirectiveHook< Value = any, Modifiers extends string = string, Arg extends string = string, > = ( binding: DirectiveBinding, 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 beforeMount?: DirectiveHook mounted?: DirectiveHook beforeUpdate?: DirectiveHook< HostElement, VNode, Value, Modifiers, Arg > updated?: DirectiveHook< HostElement, VNode, Value, Modifiers, Arg > beforeUnmount?: DirectiveHook unmounted?: DirectiveHook getSSRProps?: SSRDirectiveHook deep?: boolean } export type FunctionDirective< HostElement = any, V = any, Modifiers extends string = string, Arg extends string = string, > = DirectiveHook export type Directive< HostElement = any, Value = any, Modifiers extends string = string, Arg extends string = string, > = | ObjectDirective | FunctionDirective export type DirectiveModifiers = Record export function validateDirectiveName(name: string) { 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( 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, ) { 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() } } }