@@ -6,16 +6,18 @@ import {
6
6
looseIndexOf ,
7
7
looseToNumber ,
8
8
} from '@vue/shared'
9
- import type {
10
- DirectiveBinding ,
11
- DirectiveHook ,
12
- DirectiveHookName ,
13
- ObjectDirective ,
14
- } from '../directives'
9
+ import type { Directive } from '../directives'
15
10
import { addEventListener } from '../dom/event'
16
11
import { nextTick } from '../scheduler'
17
12
import { warn } from '../warning'
18
13
import { MetadataKind , getMetadata } from '../componentMetadata'
14
+ import {
15
+ onBeforeMount ,
16
+ onBeforeUnmount ,
17
+ onBeforeUpdate ,
18
+ onMounted ,
19
+ } from '../apiLifecycle'
20
+ import { renderEffect } from '../renderEffect'
19
21
20
22
type AssignerFn = ( value : any ) => void
21
23
function getModelAssigner ( el : Element ) : AssignerFn {
@@ -41,12 +43,12 @@ const assigningMap = new WeakMap<HTMLElement, boolean>()
41
43
42
44
// We are exporting the v-model runtime directly as vnode hooks so that it can
43
45
// be tree-shaken in case v-model is never used.
44
- export const vModelText : ObjectDirective <
46
+ export const vModelText : Directive <
45
47
HTMLInputElement | HTMLTextAreaElement ,
46
48
any ,
47
49
'lazy' | 'trim' | 'number'
48
- > = {
49
- beforeMount ( el , { modifiers : { lazy , trim , number } = { } } ) {
50
+ > = ( { value : el } , { source , modifiers : { lazy , trim , number } = { } } ) => {
51
+ onBeforeMount ( ( ) => {
50
52
const assigner = getModelAssigner ( el )
51
53
assignFnMap . set ( el , assigner )
52
54
@@ -78,12 +80,15 @@ export const vModelText: ObjectDirective<
78
80
// fires "change" instead of "input" on autocomplete.
79
81
addEventListener ( el , 'change' , onCompositionEnd )
80
82
}
81
- } ,
82
- // set value on mounted so it's after min/max for type="range"
83
- mounted ( el , { value } ) {
83
+ } )
84
+
85
+ onMounted ( ( ) => {
86
+ const value = source ( )
84
87
el . value = value == null ? '' : value
85
- } ,
86
- beforeUpdate ( el , { value, modifiers : { lazy, trim, number } = { } } ) {
88
+ } )
89
+
90
+ renderEffect ( ( ) => {
91
+ const value = source ( )
87
92
assignFnMap . set ( el , getModelAssigner ( el ) )
88
93
89
94
// avoid clearing unresolved text. #2302
@@ -108,29 +113,34 @@ export const vModelText: ObjectDirective<
108
113
}
109
114
110
115
el . value = newValue
111
- } ,
116
+ } )
112
117
}
113
118
114
- export const vModelRadio : ObjectDirective < HTMLInputElement > = {
115
- beforeMount ( el , { value } ) {
116
- el . checked = looseEqual ( value , getValue ( el ) )
119
+ export const vModelRadio : Directive < HTMLInputElement > = (
120
+ { value : el } ,
121
+ { source } ,
122
+ ) => {
123
+ onBeforeMount ( ( ) => {
124
+ el . checked = looseEqual ( source ( ) , getValue ( el ) )
117
125
assignFnMap . set ( el , getModelAssigner ( el ) )
118
126
addEventListener ( el , 'change' , ( ) => {
119
127
assignFnMap . get ( el ) ! ( getValue ( el ) )
120
128
} )
121
- } ,
122
- beforeUpdate ( el , { value, oldValue } ) {
129
+ } )
130
+
131
+ renderEffect ( ( ) => {
132
+ const value = source ( )
123
133
assignFnMap . set ( el , getModelAssigner ( el ) )
124
- if ( value !== oldValue ) {
125
- el . checked = looseEqual ( value , getValue ( el ) )
126
- }
127
- } ,
134
+ el . checked = looseEqual ( value , getValue ( el ) )
135
+ } )
128
136
}
129
137
130
- export const vModelSelect : ObjectDirective < HTMLSelectElement , any , 'number' > = {
131
- // <select multiple> value need to be deep traversed
132
- deep : true ,
133
- beforeMount ( el , { value, modifiers : { number = false } = { } } ) {
138
+ export const vModelSelect : Directive < HTMLSelectElement , any , 'number' > = (
139
+ { value : el } ,
140
+ { source, modifiers : { number = false } = { } } ,
141
+ ) => {
142
+ onBeforeMount ( ( ) => {
143
+ const value = source ( )
134
144
const isSetModel = isSet ( value )
135
145
addEventListener ( el , 'change' , ( ) => {
136
146
const selectedVal = Array . prototype . filter
@@ -153,15 +163,17 @@ export const vModelSelect: ObjectDirective<HTMLSelectElement, any, 'number'> = {
153
163
} )
154
164
assignFnMap . set ( el , getModelAssigner ( el ) )
155
165
setSelected ( el , value , number )
156
- } ,
157
- beforeUpdate ( el ) {
166
+ } )
167
+
168
+ onBeforeUnmount ( ( ) => {
158
169
assignFnMap . set ( el , getModelAssigner ( el ) )
159
- } ,
160
- updated ( el , { value, modifiers : { number = false } = { } } ) {
170
+ } )
171
+
172
+ renderEffect ( ( ) => {
161
173
if ( ! assigningMap . get ( el ) ) {
162
- setSelected ( el , value , number )
174
+ setSelected ( el , source ( ) , number )
163
175
}
164
- } ,
176
+ } )
165
177
}
166
178
167
179
function setSelected ( el : HTMLSelectElement , value : any , number : boolean ) {
@@ -223,27 +235,15 @@ function getCheckboxValue(el: HTMLInputElement, checked: boolean) {
223
235
return checked
224
236
}
225
237
226
- const setChecked : DirectiveHook < HTMLInputElement > = (
227
- el ,
228
- { value , oldValue } ,
238
+ export const vModelCheckbox : Directive < HTMLInputElement > = (
239
+ { value : el } ,
240
+ { source } ,
229
241
) => {
230
- if ( isArray ( value ) ) {
231
- el . checked = looseIndexOf ( value , getValue ( el ) ) > - 1
232
- } else if ( isSet ( value ) ) {
233
- el . checked = value . has ( getValue ( el ) )
234
- } else if ( value !== oldValue ) {
235
- el . checked = looseEqual ( value , getCheckboxValue ( el , true ) )
236
- }
237
- }
238
-
239
- export const vModelCheckbox : ObjectDirective < HTMLInputElement > = {
240
- // #4096 array checkboxes need to be deep traversed
241
- deep : true ,
242
- beforeMount ( el , binding ) {
242
+ onBeforeMount ( ( ) => {
243
243
assignFnMap . set ( el , getModelAssigner ( el ) )
244
244
245
245
addEventListener ( el , 'change' , ( ) => {
246
- const modelValue = binding . value
246
+ const modelValue = source ( )
247
247
const elementValue = getValue ( el )
248
248
const checked = el . checked
249
249
const assigner = assignFnMap . get ( el ) !
@@ -269,36 +269,38 @@ export const vModelCheckbox: ObjectDirective<HTMLInputElement> = {
269
269
assigner ( getCheckboxValue ( el , checked ) )
270
270
}
271
271
} )
272
- } ,
273
- // set initial checked on mount to wait for true-value/false-value
274
- mounted : setChecked ,
275
- beforeUpdate ( el , binding ) {
272
+ } )
273
+
274
+ onMounted ( ( ) => {
275
+ setChecked ( )
276
+ } )
277
+
278
+ onBeforeUpdate ( ( ) => {
276
279
assignFnMap . set ( el , getModelAssigner ( el ) )
277
- setChecked ( el , binding )
278
- } ,
280
+ setChecked ( )
281
+ } )
282
+
283
+ function setChecked ( ) {
284
+ const value = source ( )
285
+ if ( isArray ( value ) ) {
286
+ el . checked = looseIndexOf ( value , getValue ( el ) ) > - 1
287
+ } else if ( isSet ( value ) ) {
288
+ el . checked = value . has ( getValue ( el ) )
289
+ } else {
290
+ el . checked = looseEqual ( value , getCheckboxValue ( el , true ) )
291
+ }
292
+ }
279
293
}
280
294
281
- export const vModelDynamic : ObjectDirective <
295
+ export const vModelDynamic : Directive <
282
296
HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
283
- > = {
284
- beforeMount ( el , binding ) {
285
- callModelHook ( el , binding , 'beforeMount' )
286
- } ,
287
- mounted ( el , binding ) {
288
- callModelHook ( el , binding , 'mounted' )
289
- } ,
290
- beforeUpdate ( el , binding ) {
291
- callModelHook ( el , binding , 'beforeUpdate' )
292
- } ,
293
- updated ( el , binding ) {
294
- callModelHook ( el , binding , 'updated' )
295
- } ,
297
+ > = ( elRef , binding ) => {
298
+ const type = elRef . value . getAttribute ( 'type' )
299
+ const modelToUse = resolveDynamicModel ( elRef . value . tagName , type )
300
+ modelToUse ( elRef , binding )
296
301
}
297
302
298
- function resolveDynamicModel (
299
- tagName : string ,
300
- type : string | null ,
301
- ) : ObjectDirective {
303
+ function resolveDynamicModel ( tagName : string , type : string | null ) : Directive {
302
304
switch ( tagName ) {
303
305
case 'SELECT' :
304
306
return vModelSelect
@@ -315,14 +317,3 @@ function resolveDynamicModel(
315
317
}
316
318
}
317
319
}
318
-
319
- function callModelHook (
320
- el : HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement ,
321
- binding : DirectiveBinding ,
322
- hook : DirectiveHookName ,
323
- ) {
324
- const type = el . getAttribute ( 'type' )
325
- const modelToUse = resolveDynamicModel ( el . tagName , type )
326
- const fn = modelToUse [ hook ]
327
- fn && fn ( el , binding )
328
- }
0 commit comments