Skip to content

Commit 7acb515

Browse files
committed
fix v-model ordering w/ value bindings
1 parent f5f3e8f commit 7acb515

File tree

8 files changed

+66
-32
lines changed

8 files changed

+66
-32
lines changed

demos/model.html

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
v-scope="{
1010
text: 'hello',
1111
checked: true,
12+
checkToggle: { a: 1 },
13+
trueValue: { a: 1 },
14+
falseValue: { a: 2 },
1215
arr: ['one'],
1316
radioSelected: 'two',
1417
selected: 'two'
@@ -30,9 +33,20 @@ <h2>Checkbox</h2>
3033
<h2>Checkbox w/ Array</h2>
3134
<label><input type="checkbox" v-model="arr" value="one" /> one</label>
3235
<label><input type="checkbox" v-model="arr" value="two" /> two</label>
33-
<label><input type="checkbox" v-model="arr" value="three" /> three</label>
36+
<label
37+
><input type="checkbox" v-model="arr" :value="123" /> actual number</label
38+
>
3439
<div>{{ arr }}</div>
3540

41+
<h2>Checkbox w/ true-value / false-value</h2>
42+
<input
43+
type="checkbox"
44+
v-model="checkToggle"
45+
:true-value="trueValue"
46+
:false-value="falseValue"
47+
/>
48+
<div>{{ checkToggle }}</div>
49+
3650
<h2>Radio</h2>
3751
<label><input type="radio" v-model="radioSelected" value="one" /> one</label>
3852
<label><input type="radio" v-model="radioSelected" value="two" /> two</label>

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
},
1515
"scripts": {
1616
"dev": "vite",
17-
"check": "tsc --noEmit",
17+
"type": "tsc --noEmit",
1818
"build": "vite build"
1919
},
2020
"devDependencies": {

src/app.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { markRaw, reactive } from '@vue/reactivity'
1+
import { reactive } from '@vue/reactivity'
22
import { Block } from './block'
33
import { Directive } from './directives'
44
import { createContext } from './walk'
@@ -10,17 +10,13 @@ export function createApp(initialData?: any) {
1010
if (initialData) {
1111
ctx.scope = reactive(initialData)
1212
}
13+
1314
// global internal helpers
14-
ctx.scope.$ = markRaw({
15-
toDisplayString
16-
})
15+
ctx.scope.$s = toDisplayString
16+
1717
let rootBlocks: Block[]
1818

1919
return {
20-
data(key: string, value: any) {
21-
ctx.scope[key] = value
22-
return this
23-
},
2420
directive(name: string, def?: Directive) {
2521
if (def) {
2622
ctx.dirs[name] = def
@@ -29,6 +25,7 @@ export function createApp(initialData?: any) {
2925
return ctx.dirs[name]
3026
}
3127
},
28+
3229
mount(el?: string | Element | null) {
3330
if (typeof el === 'string') {
3431
el = document.querySelector(el)
@@ -38,6 +35,7 @@ export function createApp(initialData?: any) {
3835
return
3936
}
4037
}
38+
4139
el = el || document.documentElement
4240
let roots = el.hasAttribute('v-scope')
4341
? [el]
@@ -46,6 +44,7 @@ export function createApp(initialData?: any) {
4644
if (!roots.length) {
4745
roots = [el]
4846
}
47+
4948
if (
5049
import.meta.env.DEV &&
5150
roots.length === 1 &&
@@ -58,13 +57,15 @@ export function createApp(initialData?: any) {
5857
`with \`v-scope\`.`
5958
)
6059
}
60+
6161
rootBlocks = roots.map((el) => new Block(el, ctx, true))
6262
// remove all v-cloak after mount
6363
;[el, ...el.querySelectorAll(`[v-cloak]`)].forEach((el) =>
6464
el.removeAttribute('v-cloak')
6565
)
6666
return this
6767
},
68+
6869
unmount() {
6970
rootBlocks.forEach((block) => block.teardown())
7071
}

src/directives/bind.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@ export const bind: Directive<Element> = ({
3939
})
4040
}
4141

42-
function setProp(el: Element, arg: string, value: any, prevValue?: any) {
43-
if (arg === 'class') {
42+
function setProp(el: Element, key: string, value: any, prevValue?: any) {
43+
if (key === 'class') {
4444
el.setAttribute('class', normalizeClass(value) || '')
45-
} else if (arg === 'style') {
45+
} else if (key === 'style') {
4646
value = normalizeStyle(value)
4747
const { style } = el as HTMLElement
4848
if (!value) {
@@ -61,18 +61,26 @@ function setProp(el: Element, arg: string, value: any, prevValue?: any) {
6161
}
6262
}
6363
}
64-
} else if (arg in el && !forceAttrRE.test(arg)) {
64+
} else if (key in el && !forceAttrRE.test(key)) {
6565
// @ts-ignore
66-
el[arg] = value
67-
if (arg === 'value') {
66+
el[key] = value
67+
if (key === 'value') {
6868
// @ts-ignore
6969
el._value = value
7070
}
7171
} else {
72-
if (value != null) {
73-
el.setAttribute(arg, value)
72+
// special case for <input v-model type="checkbox"> with
73+
// :true-value & :false-value
74+
// store value as dom properties since non-string values will be
75+
// stringified.
76+
if (key === 'true-value') {
77+
;(el as any)._trueValue = value
78+
} else if (key === 'false-value') {
79+
;(el as any)._falseValue = value
80+
} else if (value != null) {
81+
el.setAttribute(key, value)
7482
} else {
75-
el.removeAttribute(arg)
83+
el.removeAttribute(key)
7684
}
7785
}
7886
}

src/directives/model.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { isArray, looseEqual, looseIndexOf, toNumber } from '@vue/shared'
22
import { Directive } from '.'
3-
import { listen } from './on'
3+
import { listen } from '../utils'
44

55
export const model: Directive<
66
HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
@@ -58,7 +58,7 @@ export const model: Directive<
5858
assign(filtered)
5959
}
6060
} else {
61-
assign(checked)
61+
assign(getCheckboxValue(el as HTMLInputElement, checked))
6262
}
6363
})
6464

src/directives/on.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,6 @@
11
import { Directive } from '.'
22
import { hyphenate } from '@vue/shared'
3-
4-
export const listen = (
5-
el: Element,
6-
event: string,
7-
handler: any,
8-
options?: any
9-
) => {
10-
el.addEventListener(event, handler, options)
11-
}
3+
import { listen } from '../utils'
124

135
// same as vue 2
146
const simplePathRE =

src/utils.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,12 @@ export const checkAttr = (el: Element, name: string): string | null => {
33
if (val != null) el.removeAttribute(name)
44
return val
55
}
6+
7+
export const listen = (
8+
el: Element,
9+
event: string,
10+
handler: any,
11+
options?: any
12+
) => {
13+
el.addEventListener(event, handler, options)
14+
}

src/walk.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,21 @@ export function walk(node: Node, ctx: Context): ChildNode | null | void {
7070
}
7171

7272
// other directives
73+
let deferredModel
7374
for (const { name, value } of [...el.attributes]) {
7475
if (dirRE.test(name) && name !== 'v-cloak') {
75-
processDirective(el, name, value, ctx)
76+
if (name === 'v-model') {
77+
// defer v-model since it relies on :value bindings to be processed
78+
// first
79+
deferredModel = value
80+
} else {
81+
processDirective(el, name, value, ctx)
82+
}
7683
}
7784
}
85+
if (deferredModel) {
86+
processDirective(el, 'v-model', deferredModel, ctx)
87+
}
7888
} else if (type === 3) {
7989
// Text
8090
const data = (node as Text).data
@@ -85,7 +95,7 @@ export function walk(node: Node, ctx: Context): ChildNode | null | void {
8595
while ((match = interpolationRE.exec(data))) {
8696
const leading = data.slice(lastIndex, match.index)
8797
if (leading) segments.push(JSON.stringify(leading))
88-
segments.push(`$.toDisplayString(${match[1]})`)
98+
segments.push(`$s(${match[1]})`)
8999
lastIndex = match.index + match[0].length
90100
}
91101
if (lastIndex < data.length - 1) {

0 commit comments

Comments
 (0)