-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
/
index.ts
164 lines (150 loc) · 4.02 KB
/
index.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
import { isDef } from '@vueuse/shared'
import type { Ref, UnwrapRef, WritableComputedRef } from 'vue-demi'
import { computed, getCurrentInstance, isVue2, nextTick, ref, watch } from 'vue-demi'
import type { CloneFn } from '../useCloned'
import { cloneFnJSON } from '../useCloned'
export interface UseVModelOptions<T, Passive extends boolean = false> {
/**
* When passive is set to `true`, it will use `watch` to sync with props and ref.
* Instead of relying on the `v-model` or `.sync` to work.
*
* @default false
*/
passive?: Passive
/**
* When eventName is set, it's value will be used to overwrite the emit event name.
*
* @default undefined
*/
eventName?: string
/**
* Attempting to check for changes of properties in a deeply nested object or array.
* Apply only when `passive` option is set to `true`
*
* @default false
*/
deep?: boolean
/**
* Defining default value for return ref when no value is passed.
*
* @default undefined
*/
defaultValue?: T
/**
* Clone the props.
* Accepts a custom clone function.
* When setting to `true`, it will use `JSON.parse(JSON.stringify(value))` to clone.
*
* @default false
*/
clone?: boolean | CloneFn<T>
/**
* The hook before triggering the emit event can be used for form validation.
* if false is returned, the emit event will not be triggered.
*
* @default undefined
*/
shouldEmit?: (v: T) => boolean
}
export function useVModel<P extends object, K extends keyof P, Name extends string>(
props: P,
key?: K,
emit?: (name: Name, ...args: any[]) => void,
options?: UseVModelOptions<P[K], false>,
): WritableComputedRef<P[K]>
export function useVModel<P extends object, K extends keyof P, Name extends string>(
props: P,
key?: K,
emit?: (name: Name, ...args: any[]) => void,
options?: UseVModelOptions<P[K], true>,
): Ref<UnwrapRef<P[K]>>
/**
* Shorthand for v-model binding, props + emit -> ref
*
* @see https://vueuse.org/useVModel
* @param props
* @param key (default 'value' in Vue 2 and 'modelValue' in Vue 3)
* @param emit
*/
export function useVModel<P extends object, K extends keyof P, Name extends string, Passive extends boolean>(
props: P,
key?: K,
emit?: (name: Name, ...args: any[]) => void,
options: UseVModelOptions<P[K], Passive> = {},
) {
const {
clone = false,
passive = false,
eventName,
deep = false,
defaultValue,
shouldEmit,
} = options
const vm = getCurrentInstance()
// @ts-expect-error mis-alignment with @vue/composition-api
const _emit = emit || vm?.emit || vm?.$emit?.bind(vm) || vm?.proxy?.$emit?.bind(vm?.proxy)
let event: string | undefined = eventName
if (!key) {
if (isVue2) {
const modelOptions = vm?.proxy?.$options?.model
key = modelOptions?.value || 'value' as K
if (!eventName)
event = modelOptions?.event || 'input'
}
else {
key = 'modelValue' as K
}
}
event = event || `update:${key!.toString()}`
const cloneFn = (val: P[K]) => !clone
? val
: typeof clone === 'function'
? clone(val)
: cloneFnJSON(val)
const getValue = () => isDef(props[key!])
? cloneFn(props[key!])
: defaultValue
const triggerEmit = (value: P[K]) => {
if (shouldEmit) {
if (shouldEmit(value))
_emit(event, value)
}
else {
_emit(event, value)
}
}
if (passive) {
const initialValue = getValue()
const proxy = ref<P[K]>(initialValue!)
let isUpdating = false
watch(
() => props[key!],
(v) => {
if (!isUpdating) {
isUpdating = true
;(proxy as any).value = cloneFn(v) as UnwrapRef<P[K]>
nextTick(() => isUpdating = false)
}
},
)
watch(
proxy,
(v) => {
if (!isUpdating && (v !== props[key!] || deep))
triggerEmit(v as P[K])
},
{ deep },
)
return proxy
}
else {
return computed<P[K]>({
get() {
return getValue()!
},
set(value) {
triggerEmit(value)
},
})
}
}