import type { CodegenContext } from '../generate'
import type { DirectiveIRNode } from '../ir'
-import type { CodeFragment } from './utils'
-
-export function genVModel(
- oper: DirectiveIRNode,
- context: CodegenContext,
-): CodeFragment[] {
- return []
-}
-
-import { camelize } from '@vue/shared'
+import { type CodeFragment, NEWLINE, genCall } from './utils'
import { genExpression } from './expression'
-import type { SetModelValueIRNode } from '../ir'
-import { NEWLINE, genCall } from './utils'
-import type { SimpleExpressionNode } from '@vue/compiler-dom'
-
-export function genSetModelValue(
- oper: SetModelValueIRNode,
- context: CodegenContext,
-): CodeFragment[] {
- const { helper } = context
- const name = oper.key.isStatic
- ? [JSON.stringify(`update:${camelize(oper.key.content)}`)]
- : ['`update:${', ...genExpression(oper.key, context), '}`']
- const handler = genModelHandler(oper.value, context)
+const helperMap = {
+ text: 'applyTextModel',
+ radio: 'applyRadioModel',
+ checkbox: 'applyCheckboxModel',
+ select: 'applySelectModel',
+ dynamic: 'applyDynamicModel',
+} as const
- return [
- NEWLINE,
- ...genCall(helper('delegate'), `n${oper.element}`, name, handler),
- ]
-}
-
-export function genModelHandler(
- value: SimpleExpressionNode,
+// This is only for built-in v-model on native elements.
+export function genVModel(
+ oper: DirectiveIRNode,
context: CodegenContext,
): CodeFragment[] {
const {
- options: { isTS },
- } = context
+ modelType,
+ element,
+ dir: { exp, modifiers },
+ } = oper
return [
- `() => ${isTS ? `($event: any)` : `$event`} => (`,
- ...genExpression(value, context, '$event'),
- ')',
+ NEWLINE,
+ ...genCall(
+ context.helper(helperMap[modelType!]),
+ `n${element}`,
+ // getter
+ [`() => (`, ...genExpression(exp!, context), `)`],
+ // setter
+ [
+ `${context.options.isTS ? `($event: any)` : `$event`} => (`,
+ ...genExpression(exp!, context, '$event'),
+ ')',
+ ],
+ // modifiers
+ modifiers.length
+ ? `{ ${modifiers.map(e => e.content + ': true').join(',')} }`
+ : undefined,
+ ),
]
}
'trim' | 'number' | 'lazy'
> = {
created(el, { modifiers: { lazy, trim, number } }, vnode) {
- el[assignKey] = getModelAssigner(vnode)
- const castToNumber =
- number || (vnode.props && vnode.props.type === 'number')
- addEventListener(el, lazy ? 'change' : 'input', e => {
- if ((e.target as any).composing) return
- let domValue: string | number = el.value
- if (trim) {
- domValue = domValue.trim()
- }
- if (castToNumber) {
- domValue = looseToNumber(domValue)
- }
- el[assignKey](domValue)
- })
- if (trim) {
- addEventListener(el, 'change', () => {
- el.value = el.value.trim()
- })
- }
- if (!lazy) {
- addEventListener(el, 'compositionstart', onCompositionStart)
- addEventListener(el, 'compositionend', onCompositionEnd)
- // Safari < 10.2 & UIWebView doesn't fire compositionend when
- // switching focus before confirming composition choice
- // this also fixes the issue where some browsers e.g. iOS Chrome
- // fires "change" instead of "input" on autocomplete.
- addEventListener(el, 'change', onCompositionEnd)
- }
+ vModelTextInit(
+ el,
+ (el[assignKey] = getModelAssigner(vnode)),
+ trim,
+ number || !!(vnode.props && vnode.props.type === 'number'),
+ lazy,
+ )
},
// set value on mounted so it's after min/max for type="range"
mounted(el, { value }) {
vnode,
) {
el[assignKey] = getModelAssigner(vnode)
- // avoid clearing unresolved text. #2302
- if ((el as any).composing) return
- const elValue =
- (number || el.type === 'number') && !/^0\d/.test(el.value)
- ? looseToNumber(el.value)
- : el.value
- const newValue = value == null ? '' : value
+ vModelTextUpdate(el, value, oldValue, trim, number, lazy)
+ },
+}
- if (elValue === newValue) {
- return
+/**
+ * @internal
+ */
+export const vModelTextInit = (
+ el: HTMLInputElement | HTMLTextAreaElement,
+ set: (v: any) => void,
+ trim: boolean | undefined,
+ number: boolean | undefined,
+ lazy: boolean | undefined,
+): void => {
+ addEventListener(el, lazy ? 'change' : 'input', e => {
+ if ((e.target as any).composing) return
+ let domValue: string | number = el.value
+ if (trim) {
+ domValue = domValue.trim()
+ }
+ if (number) {
+ domValue = looseToNumber(domValue)
}
+ set(domValue)
+ })
+ if (trim) {
+ addEventListener(el, 'change', () => {
+ el.value = el.value.trim()
+ })
+ }
+ if (!lazy) {
+ addEventListener(el, 'compositionstart', onCompositionStart)
+ addEventListener(el, 'compositionend', onCompositionEnd)
+ // Safari < 10.2 & UIWebView doesn't fire compositionend when
+ // switching focus before confirming composition choice
+ // this also fixes the issue where some browsers e.g. iOS Chrome
+ // fires "change" instead of "input" on autocomplete.
+ addEventListener(el, 'change', onCompositionEnd)
+ }
+}
- if (document.activeElement === el && el.type !== 'range') {
- // #8546
- if (lazy && value === oldValue) {
- return
- }
- if (trim && el.value.trim() === newValue) {
- return
- }
+/**
+ * @internal
+ */
+export const vModelTextUpdate = (
+ el: HTMLInputElement | HTMLTextAreaElement,
+ value: any,
+ oldValue: any,
+ trim: boolean | undefined,
+ number: boolean | undefined,
+ lazy: boolean | undefined,
+): void => {
+ // avoid clearing unresolved text. #2302
+ if ((el as any).composing) return
+ const elValue =
+ (number || el.type === 'number') && !/^0\d/.test(el.value)
+ ? looseToNumber(el.value)
+ : el.value
+ const newValue = value == null ? '' : value
+
+ if (elValue === newValue) {
+ return
+ }
+
+ if (document.activeElement === el && el.type !== 'range') {
+ // #8546
+ if (lazy && value === oldValue) {
+ return
}
+ if (trim && el.value.trim() === newValue) {
+ return
+ }
+ }
- el.value = newValue
- },
+ el.value = newValue
}
export const vModelCheckbox: ModelDirective<HTMLInputElement> = {
--- /dev/null
+import { onMounted, vModelTextInit, vModelTextUpdate } from '@vue/runtime-dom'
+import { renderEffect } from '../renderEffect'
+
+type VaporModelDirective<T = Element> = (
+ el: T,
+ get: () => any,
+ set: (v: any) => void,
+ modifiers?: { number?: true; trim?: true; lazy?: true },
+) => void
+
+export const applyTextModel: VaporModelDirective<
+ HTMLInputElement | HTMLTextAreaElement
+> = (el, get, set, { trim, number, lazy } = {}) => {
+ vModelTextInit(el, set, trim, number, lazy)
+ onMounted(() => {
+ let oldValue: any
+ renderEffect(() => {
+ const value = get()
+ vModelTextUpdate(el, value, oldValue, trim, number, lazy)
+ oldValue = value
+ })
+ })
+}
+
+export const applyRadioModel: VaporModelDirective = (el, get, set) => {}
+export const applyCheckboxModel: VaporModelDirective = (el, get, set) => {}
+export const applySelectModel: VaporModelDirective = (el, get, set) => {}
+export const applyDynamicModel: VaporModelDirective = (el, get, set) => {}