expect(beforeUnmount).toHaveBeenCalled()
expect(unmounted).toHaveBeenCalled()
})
+
+ it('should work with a function directive', async () => {
+ const count = ref(0)
+
+ function assertBindings(binding: DirectiveBinding) {
+ expect(binding.value).toBe(count.value)
+ expect(binding.arg).toBe('foo')
+ expect(binding.instance).toBe(_instance && _instance.renderProxy)
+ expect(binding.modifiers && binding.modifiers.ok).toBe(true)
+ }
+
+ const fn = jest.fn(((el, binding, vnode, prevVNode) => {
+ expect(el.tag).toBe('div')
+ expect(el.parentNode).toBe(root)
+
+ assertBindings(binding)
+
+ expect(vnode).toBe(_vnode)
+ expect(prevVNode).toBe(_prevVnode)
+ }) as DirectiveHook)
+
+ let _instance: ComponentInternalInstance | null = null
+ let _vnode: VNode | null = null
+ let _prevVnode: VNode | null = null
+ const Comp = {
+ setup() {
+ _instance = currentInstance
+ },
+ render() {
+ _prevVnode = _vnode
+ _vnode = applyDirectives(h('div', count.value), [
+ [
+ fn,
+ // value
+ count.value,
+ // argument
+ 'foo',
+ // modifiers
+ { ok: true }
+ ]
+ ])
+ return _vnode
+ }
+ }
+
+ const root = nodeOps.createElement('div')
+ render(h(Comp), root)
+
+ expect(fn).toHaveBeenCalledTimes(1)
+
+ count.value++
+ await nextTick()
+ expect(fn).toHaveBeenCalledTimes(2)
+ })
})
prevVNode: VNode<any, T> | null
) => void
-export interface Directive<T = any> {
+export interface ObjectDirective<T = any> {
beforeMount?: DirectiveHook<T>
mounted?: DirectiveHook<T>
beforeUpdate?: DirectiveHook<T>
unmounted?: DirectiveHook<T>
}
+export type FunctionDirective<T = any> = DirectiveHook<T>
+
+export type Directive<T = any> = ObjectDirective<T> | FunctionDirective<T>
+
type DirectiveModifiers = Record<string, boolean>
const valueCache = new WeakMap<Directive, WeakMap<any, any>>()
valueCacheForDir = new WeakMap<VNode, any>()
valueCache.set(directive, valueCacheForDir)
}
+
+ if (isFunction(directive)) {
+ directive = {
+ mounted: directive,
+ updated: directive
+ } as ObjectDirective
+ }
+
for (const key in directive) {
- const hook = directive[key as keyof Directive]!
+ const hook = directive[key as keyof ObjectDirective]!
const hookKey = `vnode` + key[0].toUpperCase() + key.slice(1)
const vnodeHook = (vnode: VNode, prevVNode: VNode | null) => {
let oldValue
Directive,
DirectiveBinding,
DirectiveHook,
+ ObjectDirective,
+ FunctionDirective,
DirectiveArguments
} from './directives'
export { SuspenseBoundary } from './suspense'
-import { Directive, VNode, DirectiveBinding, warn } from '@vue/runtime-core'
+import {
+ ObjectDirective,
+ VNode,
+ DirectiveBinding,
+ warn
+} from '@vue/runtime-core'
import { addEventListener } from '../modules/events'
import { isArray, isObject } from '@vue/shared'
// We are exporting the v-model runtime directly as vnode hooks so that it can
// be tree-shaken in case v-model is never used.
-export const vModelText: Directive<HTMLInputElement | HTMLTextAreaElement> = {
+export const vModelText: ObjectDirective<HTMLInputElement | HTMLTextAreaElement> = {
beforeMount(el, { value, modifiers: { lazy, trim, number } }, vnode) {
el.value = value
const assign = getModelAssigner(vnode)
}
}
-export const vModelCheckbox: Directive<HTMLInputElement> = {
+export const vModelCheckbox: ObjectDirective<HTMLInputElement> = {
beforeMount(el, binding, vnode) {
setChecked(el, binding, vnode)
const assign = getModelAssigner(vnode)
: !!value
}
-export const vModelRadio: Directive<HTMLInputElement> = {
+export const vModelRadio: ObjectDirective<HTMLInputElement> = {
beforeMount(el, { value }, vnode) {
el.checked = looseEqual(value, vnode.props!.value)
const assign = getModelAssigner(vnode)
}
}
-export const vModelSelect: Directive<HTMLSelectElement> = {
+export const vModelSelect: ObjectDirective<HTMLSelectElement> = {
// use mounted & updated because <select> relies on its children <option>s.
mounted(el, { value }, vnode) {
setSelected(el, value)
return '_value' in el ? (el as any)._value : el.value
}
-export const vModelDynamic: Directive<
+export const vModelDynamic: ObjectDirective<
HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
> = {
beforeMount(el, binding, vnode) {
binding: DirectiveBinding,
vnode: VNode,
prevVNode: VNode | null,
- hook: keyof Directive
+ hook: keyof ObjectDirective
) {
- let modelToUse: Directive
+ let modelToUse: ObjectDirective
switch (el.tagName) {
case 'SELECT':
modelToUse = vModelSelect