-
-
Notifications
You must be signed in to change notification settings - Fork 8.5k
/
Copy pathdirectives.ts
198 lines (184 loc) · 5.37 KB
/
directives.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
/**
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()
}
}
}