From: Evan You Date: Sat, 1 Feb 2025 13:06:40 +0000 (+0800) Subject: wip(vapor): v-model text reuse from runtime-dom X-Git-Tag: v3.6.0-alpha.1~16^2~111 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=5a62266e136727ebc9508ce53284e945544223c9;p=thirdparty%2Fvuejs%2Fcore.git wip(vapor): v-model text reuse from runtime-dom --- diff --git a/packages/compiler-vapor/src/generators/vModel.ts b/packages/compiler-vapor/src/generators/vModel.ts index aab780488c..8ebd365762 100644 --- a/packages/compiler-vapor/src/generators/vModel.ts +++ b/packages/compiler-vapor/src/generators/vModel.ts @@ -1,48 +1,44 @@ 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, + ), ] } diff --git a/packages/runtime-dom/src/directives/vModel.ts b/packages/runtime-dom/src/directives/vModel.ts index 5057e16d47..41e33a68af 100644 --- a/packages/runtime-dom/src/directives/vModel.ts +++ b/packages/runtime-dom/src/directives/vModel.ts @@ -52,34 +52,13 @@ export const vModelText: ModelDirective< '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 }) { @@ -91,30 +70,81 @@ export const vModelText: ModelDirective< 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 = { diff --git a/packages/runtime-dom/src/index.ts b/packages/runtime-dom/src/index.ts index a23d0cb588..2ba88006f9 100644 --- a/packages/runtime-dom/src/index.ts +++ b/packages/runtime-dom/src/index.ts @@ -330,3 +330,7 @@ export { vShowHidden, type VShowElement, } from './directives/vShow' +/** + * @internal + */ +export { vModelTextInit, vModelTextUpdate } from './directives/vModel' diff --git a/packages/runtime-vapor/src/directives/vModel.ts b/packages/runtime-vapor/src/directives/vModel.ts new file mode 100644 index 0000000000..48880f6d0b --- /dev/null +++ b/packages/runtime-vapor/src/directives/vModel.ts @@ -0,0 +1,28 @@ +import { onMounted, vModelTextInit, vModelTextUpdate } from '@vue/runtime-dom' +import { renderEffect } from '../renderEffect' + +type VaporModelDirective = ( + 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) => {} diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts index f69d61444b..b228217efa 100644 --- a/packages/runtime-vapor/src/index.ts +++ b/packages/runtime-vapor/src/index.ts @@ -31,3 +31,10 @@ export { export { createTemplateRefSetter } from './apiTemplateRef' export { createDynamicComponent } from './apiCreateDynamicComponent' export { applyVShow } from './directives/vShow' +export { + applyTextModel, + applyRadioModel, + applyCheckboxModel, + applySelectModel, + applyDynamicModel, +} from './directives/vModel'