Skip to content

Commit 5a62266

Browse files
committed
wip(vapor): v-model text reuse from runtime-dom
1 parent aa28e9c commit 5a62266

File tree

5 files changed

+148
-83
lines changed

5 files changed

+148
-83
lines changed
Lines changed: 32 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,44 @@
11
import type { CodegenContext } from '../generate'
22
import type { DirectiveIRNode } from '../ir'
3-
import type { CodeFragment } from './utils'
4-
5-
export function genVModel(
6-
oper: DirectiveIRNode,
7-
context: CodegenContext,
8-
): CodeFragment[] {
9-
return []
10-
}
11-
12-
import { camelize } from '@vue/shared'
3+
import { type CodeFragment, NEWLINE, genCall } from './utils'
134
import { genExpression } from './expression'
14-
import type { SetModelValueIRNode } from '../ir'
15-
import { NEWLINE, genCall } from './utils'
16-
import type { SimpleExpressionNode } from '@vue/compiler-dom'
17-
18-
export function genSetModelValue(
19-
oper: SetModelValueIRNode,
20-
context: CodegenContext,
21-
): CodeFragment[] {
22-
const { helper } = context
23-
const name = oper.key.isStatic
24-
? [JSON.stringify(`update:${camelize(oper.key.content)}`)]
25-
: ['`update:${', ...genExpression(oper.key, context), '}`']
265

27-
const handler = genModelHandler(oper.value, context)
6+
const helperMap = {
7+
text: 'applyTextModel',
8+
radio: 'applyRadioModel',
9+
checkbox: 'applyCheckboxModel',
10+
select: 'applySelectModel',
11+
dynamic: 'applyDynamicModel',
12+
} as const
2813

29-
return [
30-
NEWLINE,
31-
...genCall(helper('delegate'), `n${oper.element}`, name, handler),
32-
]
33-
}
34-
35-
export function genModelHandler(
36-
value: SimpleExpressionNode,
14+
// This is only for built-in v-model on native elements.
15+
export function genVModel(
16+
oper: DirectiveIRNode,
3717
context: CodegenContext,
3818
): CodeFragment[] {
3919
const {
40-
options: { isTS },
41-
} = context
20+
modelType,
21+
element,
22+
dir: { exp, modifiers },
23+
} = oper
4224

4325
return [
44-
`() => ${isTS ? `($event: any)` : `$event`} => (`,
45-
...genExpression(value, context, '$event'),
46-
')',
26+
NEWLINE,
27+
...genCall(
28+
context.helper(helperMap[modelType!]),
29+
`n${element}`,
30+
// getter
31+
[`() => (`, ...genExpression(exp!, context), `)`],
32+
// setter
33+
[
34+
`${context.options.isTS ? `($event: any)` : `$event`} => (`,
35+
...genExpression(exp!, context, '$event'),
36+
')',
37+
],
38+
// modifiers
39+
modifiers.length
40+
? `{ ${modifiers.map(e => e.content + ': true').join(',')} }`
41+
: undefined,
42+
),
4743
]
4844
}

packages/runtime-dom/src/directives/vModel.ts

Lines changed: 77 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -52,34 +52,13 @@ export const vModelText: ModelDirective<
5252
'trim' | 'number' | 'lazy'
5353
> = {
5454
created(el, { modifiers: { lazy, trim, number } }, vnode) {
55-
el[assignKey] = getModelAssigner(vnode)
56-
const castToNumber =
57-
number || (vnode.props && vnode.props.type === 'number')
58-
addEventListener(el, lazy ? 'change' : 'input', e => {
59-
if ((e.target as any).composing) return
60-
let domValue: string | number = el.value
61-
if (trim) {
62-
domValue = domValue.trim()
63-
}
64-
if (castToNumber) {
65-
domValue = looseToNumber(domValue)
66-
}
67-
el[assignKey](domValue)
68-
})
69-
if (trim) {
70-
addEventListener(el, 'change', () => {
71-
el.value = el.value.trim()
72-
})
73-
}
74-
if (!lazy) {
75-
addEventListener(el, 'compositionstart', onCompositionStart)
76-
addEventListener(el, 'compositionend', onCompositionEnd)
77-
// Safari < 10.2 & UIWebView doesn't fire compositionend when
78-
// switching focus before confirming composition choice
79-
// this also fixes the issue where some browsers e.g. iOS Chrome
80-
// fires "change" instead of "input" on autocomplete.
81-
addEventListener(el, 'change', onCompositionEnd)
82-
}
55+
vModelTextInit(
56+
el,
57+
(el[assignKey] = getModelAssigner(vnode)),
58+
trim,
59+
number || !!(vnode.props && vnode.props.type === 'number'),
60+
lazy,
61+
)
8362
},
8463
// set value on mounted so it's after min/max for type="range"
8564
mounted(el, { value }) {
@@ -91,30 +70,81 @@ export const vModelText: ModelDirective<
9170
vnode,
9271
) {
9372
el[assignKey] = getModelAssigner(vnode)
94-
// avoid clearing unresolved text. #2302
95-
if ((el as any).composing) return
96-
const elValue =
97-
(number || el.type === 'number') && !/^0\d/.test(el.value)
98-
? looseToNumber(el.value)
99-
: el.value
100-
const newValue = value == null ? '' : value
73+
vModelTextUpdate(el, value, oldValue, trim, number, lazy)
74+
},
75+
}
10176

102-
if (elValue === newValue) {
103-
return
77+
/**
78+
* @internal
79+
*/
80+
export const vModelTextInit = (
81+
el: HTMLInputElement | HTMLTextAreaElement,
82+
set: (v: any) => void,
83+
trim: boolean | undefined,
84+
number: boolean | undefined,
85+
lazy: boolean | undefined,
86+
): void => {
87+
addEventListener(el, lazy ? 'change' : 'input', e => {
88+
if ((e.target as any).composing) return
89+
let domValue: string | number = el.value
90+
if (trim) {
91+
domValue = domValue.trim()
92+
}
93+
if (number) {
94+
domValue = looseToNumber(domValue)
10495
}
96+
set(domValue)
97+
})
98+
if (trim) {
99+
addEventListener(el, 'change', () => {
100+
el.value = el.value.trim()
101+
})
102+
}
103+
if (!lazy) {
104+
addEventListener(el, 'compositionstart', onCompositionStart)
105+
addEventListener(el, 'compositionend', onCompositionEnd)
106+
// Safari < 10.2 & UIWebView doesn't fire compositionend when
107+
// switching focus before confirming composition choice
108+
// this also fixes the issue where some browsers e.g. iOS Chrome
109+
// fires "change" instead of "input" on autocomplete.
110+
addEventListener(el, 'change', onCompositionEnd)
111+
}
112+
}
105113

106-
if (document.activeElement === el && el.type !== 'range') {
107-
// #8546
108-
if (lazy && value === oldValue) {
109-
return
110-
}
111-
if (trim && el.value.trim() === newValue) {
112-
return
113-
}
114+
/**
115+
* @internal
116+
*/
117+
export const vModelTextUpdate = (
118+
el: HTMLInputElement | HTMLTextAreaElement,
119+
value: any,
120+
oldValue: any,
121+
trim: boolean | undefined,
122+
number: boolean | undefined,
123+
lazy: boolean | undefined,
124+
): void => {
125+
// avoid clearing unresolved text. #2302
126+
if ((el as any).composing) return
127+
const elValue =
128+
(number || el.type === 'number') && !/^0\d/.test(el.value)
129+
? looseToNumber(el.value)
130+
: el.value
131+
const newValue = value == null ? '' : value
132+
133+
if (elValue === newValue) {
134+
return
135+
}
136+
137+
if (document.activeElement === el && el.type !== 'range') {
138+
// #8546
139+
if (lazy && value === oldValue) {
140+
return
114141
}
142+
if (trim && el.value.trim() === newValue) {
143+
return
144+
}
145+
}
115146

116-
el.value = newValue
117-
},
147+
el.value = newValue
118148
}
119149

120150
export const vModelCheckbox: ModelDirective<HTMLInputElement> = {

packages/runtime-dom/src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,3 +330,7 @@ export {
330330
vShowHidden,
331331
type VShowElement,
332332
} from './directives/vShow'
333+
/**
334+
* @internal
335+
*/
336+
export { vModelTextInit, vModelTextUpdate } from './directives/vModel'
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { onMounted, vModelTextInit, vModelTextUpdate } from '@vue/runtime-dom'
2+
import { renderEffect } from '../renderEffect'
3+
4+
type VaporModelDirective<T = Element> = (
5+
el: T,
6+
get: () => any,
7+
set: (v: any) => void,
8+
modifiers?: { number?: true; trim?: true; lazy?: true },
9+
) => void
10+
11+
export const applyTextModel: VaporModelDirective<
12+
HTMLInputElement | HTMLTextAreaElement
13+
> = (el, get, set, { trim, number, lazy } = {}) => {
14+
vModelTextInit(el, set, trim, number, lazy)
15+
onMounted(() => {
16+
let oldValue: any
17+
renderEffect(() => {
18+
const value = get()
19+
vModelTextUpdate(el, value, oldValue, trim, number, lazy)
20+
oldValue = value
21+
})
22+
})
23+
}
24+
25+
export const applyRadioModel: VaporModelDirective = (el, get, set) => {}
26+
export const applyCheckboxModel: VaporModelDirective = (el, get, set) => {}
27+
export const applySelectModel: VaporModelDirective = (el, get, set) => {}
28+
export const applyDynamicModel: VaporModelDirective = (el, get, set) => {}

packages/runtime-vapor/src/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,10 @@ export {
3131
export { createTemplateRefSetter } from './apiTemplateRef'
3232
export { createDynamicComponent } from './apiCreateDynamicComponent'
3333
export { applyVShow } from './directives/vShow'
34+
export {
35+
applyTextModel,
36+
applyRadioModel,
37+
applyCheckboxModel,
38+
applySelectModel,
39+
applyDynamicModel,
40+
} from './directives/vModel'

0 commit comments

Comments
 (0)