@@ -6,10 +6,14 @@ import {
6
6
warn
7
7
} from '@vue/runtime-core'
8
8
import { addEventListener } from '../modules/events'
9
- import { isArray , looseEqual , looseIndexOf } from '@vue/shared'
9
+ import { isArray , looseEqual , looseIndexOf , invokeArrayFns } from '@vue/shared'
10
10
11
- const getModelAssigner = ( vnode : VNode ) : ( ( value : any ) => void ) =>
12
- vnode . props ! [ 'onUpdate:modelValue' ]
11
+ type AssignerFn = ( value : any ) => void
12
+
13
+ const getModelAssigner = ( vnode : VNode ) : AssignerFn => {
14
+ const fn = vnode . props ! [ 'onUpdate:modelValue' ]
15
+ return isArray ( fn ) ? value => invokeArrayFns ( fn , value ) : fn
16
+ }
13
17
14
18
function onCompositionStart ( e : Event ) {
15
19
; ( e . target as any ) . composing = true
@@ -34,14 +38,16 @@ function toNumber(val: string): number | string {
34
38
return isNaN ( n ) ? val : n
35
39
}
36
40
41
+ type ModelDirective < T > = ObjectDirective < T & { _assign : AssignerFn } >
42
+
37
43
// We are exporting the v-model runtime directly as vnode hooks so that it can
38
44
// be tree-shaken in case v-model is never used.
39
- export const vModelText : ObjectDirective <
45
+ export const vModelText : ModelDirective <
40
46
HTMLInputElement | HTMLTextAreaElement
41
47
> = {
42
48
beforeMount ( el , { value, modifiers : { lazy, trim, number } } , vnode ) {
43
49
el . value = value
44
- const assign = getModelAssigner ( vnode )
50
+ el . _assign = getModelAssigner ( vnode )
45
51
const castToNumber = number || el . type === 'number'
46
52
addEventListener ( el , lazy ? 'change' : 'input' , ( ) => {
47
53
let domValue : string | number = el . value
@@ -50,7 +56,7 @@ export const vModelText: ObjectDirective<
50
56
} else if ( castToNumber ) {
51
57
domValue = toNumber ( domValue )
52
58
}
53
- assign ( domValue )
59
+ el . _assign ( domValue )
54
60
} )
55
61
if ( trim ) {
56
62
addEventListener ( el , 'change' , ( ) => {
@@ -67,7 +73,8 @@ export const vModelText: ObjectDirective<
67
73
addEventListener ( el , 'change' , onCompositionEnd )
68
74
}
69
75
} ,
70
- beforeUpdate ( el , { value, oldValue, modifiers : { trim, number } } ) {
76
+ beforeUpdate ( el , { value, oldValue, modifiers : { trim, number } } , vnode ) {
77
+ el . _assign = getModelAssigner ( vnode )
71
78
if ( value === oldValue ) {
72
79
return
73
80
}
@@ -83,14 +90,15 @@ export const vModelText: ObjectDirective<
83
90
}
84
91
}
85
92
86
- export const vModelCheckbox : ObjectDirective < HTMLInputElement > = {
93
+ export const vModelCheckbox : ModelDirective < HTMLInputElement > = {
87
94
beforeMount ( el , binding , vnode ) {
88
95
setChecked ( el , binding , vnode )
89
- const assign = getModelAssigner ( vnode )
96
+ el . _assign = getModelAssigner ( vnode )
90
97
addEventListener ( el , 'change' , ( ) => {
91
98
const modelValue = ( el as any ) . _modelValue
92
99
const elementValue = getValue ( el )
93
100
const checked = el . checked
101
+ const assign = el . _assign
94
102
if ( isArray ( modelValue ) ) {
95
103
const index = looseIndexOf ( modelValue , elementValue )
96
104
const found = index !== - 1
@@ -106,7 +114,10 @@ export const vModelCheckbox: ObjectDirective<HTMLInputElement> = {
106
114
}
107
115
} )
108
116
} ,
109
- beforeUpdate : setChecked
117
+ beforeUpdate ( el , binding , vnode ) {
118
+ setChecked ( el , binding , vnode )
119
+ el . _assign = getModelAssigner ( vnode )
120
+ }
110
121
}
111
122
112
123
function setChecked (
@@ -124,33 +135,37 @@ function setChecked(
124
135
}
125
136
}
126
137
127
- export const vModelRadio : ObjectDirective < HTMLInputElement > = {
138
+ export const vModelRadio : ModelDirective < HTMLInputElement > = {
128
139
beforeMount ( el , { value } , vnode ) {
129
140
el . checked = looseEqual ( value , vnode . props ! . value )
130
- const assign = getModelAssigner ( vnode )
141
+ el . _assign = getModelAssigner ( vnode )
131
142
addEventListener ( el , 'change' , ( ) => {
132
- assign ( getValue ( el ) )
143
+ el . _assign ( getValue ( el ) )
133
144
} )
134
145
} ,
135
146
beforeUpdate ( el , { value, oldValue } , vnode ) {
147
+ el . _assign = getModelAssigner ( vnode )
136
148
if ( value !== oldValue ) {
137
149
el . checked = looseEqual ( value , vnode . props ! . value )
138
150
}
139
151
}
140
152
}
141
153
142
- export const vModelSelect : ObjectDirective < HTMLSelectElement > = {
154
+ export const vModelSelect : ModelDirective < HTMLSelectElement > = {
143
155
// use mounted & updated because <select> relies on its children <option>s.
144
156
mounted ( el , { value } , vnode ) {
145
157
setSelected ( el , value )
146
- const assign = getModelAssigner ( vnode )
158
+ el . _assign = getModelAssigner ( vnode )
147
159
addEventListener ( el , 'change' , ( ) => {
148
160
const selectedVal = Array . prototype . filter
149
161
. call ( el . options , ( o : HTMLOptionElement ) => o . selected )
150
162
. map ( getValue )
151
- assign ( el . multiple ? selectedVal : selectedVal [ 0 ] )
163
+ el . _assign ( el . multiple ? selectedVal : selectedVal [ 0 ] )
152
164
} )
153
165
} ,
166
+ beforeUpdate ( el , _binding , vnode ) {
167
+ el . _assign = getModelAssigner ( vnode )
168
+ } ,
154
169
updated ( el , { value } ) {
155
170
setSelected ( el , value )
156
171
}
0 commit comments