From 250127c13ddc16220ad2574b26f90f313209fd87 Mon Sep 17 00:00:00 2001 From: Evan You Date: Sat, 1 Feb 2025 22:21:11 +0800 Subject: [PATCH] wip(vapor): v-model checkbox, radio, select, dynamic --- .../src/generators/component.ts | 4 +- .../src/generators/modelValue.ts | 38 ---- .../src/generators/operation.ts | 3 - .../compiler-vapor/src/generators/vModel.ts | 18 +- packages/compiler-vapor/src/ir/index.ts | 12 -- .../compiler-vapor/src/transforms/vModel.ts | 9 - packages/runtime-dom/src/directives/vModel.ts | 174 +++++++++++------- packages/runtime-dom/src/index.ts | 10 +- .../runtime-vapor/src/directives/vModel.ts | 102 ++++++++-- 9 files changed, 221 insertions(+), 149 deletions(-) delete mode 100644 packages/compiler-vapor/src/generators/modelValue.ts diff --git a/packages/compiler-vapor/src/generators/component.ts b/packages/compiler-vapor/src/generators/component.ts index cb92aa5968..a010b788f8 100644 --- a/packages/compiler-vapor/src/generators/component.ts +++ b/packages/compiler-vapor/src/generators/component.ts @@ -37,8 +37,8 @@ import { } from '@vue/compiler-core' import { genEventHandler } from './event' import { genDirectiveModifiers, genDirectivesForElement } from './directive' -import { genModelHandler } from './modelValue' import { genBlock } from './block' +import { genModelHandler } from './vModel' export function genCreateComponent( operation: CreateComponentIRNode, @@ -223,7 +223,7 @@ function genModelEvent(prop: IRProp, context: CodegenContext): CodeFragment[] { : ['["onUpdate:" + ', ...genExpression(prop.key, context), ']'] const handler = genModelHandler(prop.values[0], context) - return [',', NEWLINE, ...name, ': ', ...handler] + return [',', NEWLINE, ...name, ': () => ', ...handler] } function genModelModifiers( diff --git a/packages/compiler-vapor/src/generators/modelValue.ts b/packages/compiler-vapor/src/generators/modelValue.ts deleted file mode 100644 index a12bab0f89..0000000000 --- a/packages/compiler-vapor/src/generators/modelValue.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { camelize } from '@vue/shared' -import { genExpression } from './expression' -import type { SetModelValueIRNode } from '../ir' -import type { CodegenContext } from '../generate' -import { type CodeFragment, 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) - - return [ - NEWLINE, - ...genCall(helper('delegate'), `n${oper.element}`, name, handler), - ] -} - -export function genModelHandler( - value: SimpleExpressionNode, - context: CodegenContext, -): CodeFragment[] { - const { - options: { isTS }, - } = context - - return [ - `() => ${isTS ? `($event: any)` : `$event`} => (`, - ...genExpression(value, context, '$event'), - ')', - ] -} diff --git a/packages/compiler-vapor/src/generators/operation.ts b/packages/compiler-vapor/src/generators/operation.ts index 5159348fd1..997b2655ec 100644 --- a/packages/compiler-vapor/src/generators/operation.ts +++ b/packages/compiler-vapor/src/generators/operation.ts @@ -5,7 +5,6 @@ import { genSetDynamicEvents, genSetEvent } from './event' import { genFor } from './for' import { genSetHtml } from './html' import { genIf } from './if' -import { genSetModelValue } from './modelValue' import { genDynamicProps, genSetProp } from './prop' import { genDeclareOldRef, genSetTemplateRef } from './templateRef' import { genCreateTextNode, genSetText } from './text' @@ -51,8 +50,6 @@ export function genOperation( return genSetHtml(oper, context) case IRNodeTypes.SET_TEMPLATE_REF: return genSetTemplateRef(oper, context) - case IRNodeTypes.SET_MODEL_VALUE: - return genSetModelValue(oper, context) case IRNodeTypes.CREATE_TEXT_NODE: return genCreateTextNode(oper, context) case IRNodeTypes.INSERT_NODE: diff --git a/packages/compiler-vapor/src/generators/vModel.ts b/packages/compiler-vapor/src/generators/vModel.ts index 8ebd365762..a7082d9146 100644 --- a/packages/compiler-vapor/src/generators/vModel.ts +++ b/packages/compiler-vapor/src/generators/vModel.ts @@ -2,6 +2,7 @@ import type { CodegenContext } from '../generate' import type { DirectiveIRNode } from '../ir' import { type CodeFragment, NEWLINE, genCall } from './utils' import { genExpression } from './expression' +import type { SimpleExpressionNode } from '@vue/compiler-dom' const helperMap = { text: 'applyTextModel', @@ -30,11 +31,7 @@ export function genVModel( // getter [`() => (`, ...genExpression(exp!, context), `)`], // setter - [ - `${context.options.isTS ? `($event: any)` : `$event`} => (`, - ...genExpression(exp!, context, '$event'), - ')', - ], + genModelHandler(exp!, context), // modifiers modifiers.length ? `{ ${modifiers.map(e => e.content + ': true').join(',')} }` @@ -42,3 +39,14 @@ export function genVModel( ), ] } + +export function genModelHandler( + exp: SimpleExpressionNode, + context: CodegenContext, +): CodeFragment[] { + return [ + `${context.options.isTS ? `(_value: any)` : `_value`} => (`, + ...genExpression(exp, context, '_value'), + ')', + ] +} diff --git a/packages/compiler-vapor/src/ir/index.ts b/packages/compiler-vapor/src/ir/index.ts index f51591f374..cf45a60139 100644 --- a/packages/compiler-vapor/src/ir/index.ts +++ b/packages/compiler-vapor/src/ir/index.ts @@ -1,5 +1,4 @@ import type { - BindingTypes, CompoundExpressionNode, DirectiveNode, RootNode, @@ -23,7 +22,6 @@ export enum IRNodeTypes { SET_DYNAMIC_EVENTS, SET_HTML, SET_TEMPLATE_REF, - SET_MODEL_VALUE, INSERT_NODE, PREPEND_NODE, @@ -154,15 +152,6 @@ export interface SetTemplateRefIRNode extends BaseIRNode { effect: boolean } -export interface SetModelValueIRNode extends BaseIRNode { - type: IRNodeTypes.SET_MODEL_VALUE - element: number - key: SimpleExpressionNode - value: SimpleExpressionNode - bindingType?: BindingTypes - isComponent: boolean -} - export interface CreateTextNodeIRNode extends BaseIRNode { type: IRNodeTypes.CREATE_TEXT_NODE id: number @@ -227,7 +216,6 @@ export type OperationNode = | SetDynamicEventsIRNode | SetHtmlIRNode | SetTemplateRefIRNode - | SetModelValueIRNode | CreateTextNodeIRNode | InsertNodeIRNode | PrependNodeIRNode diff --git a/packages/compiler-vapor/src/transforms/vModel.ts b/packages/compiler-vapor/src/transforms/vModel.ts index e616954ebe..e92f1bcebf 100644 --- a/packages/compiler-vapor/src/transforms/vModel.ts +++ b/packages/compiler-vapor/src/transforms/vModel.ts @@ -139,15 +139,6 @@ export const transformVModel: DirectiveTransform = (dir, node, context) => { ) } - // TODO this should no longer be needed - context.registerOperation({ - type: IRNodeTypes.SET_MODEL_VALUE, - element: context.reference(), - key: arg || createSimpleExpression('modelValue', true), - value: exp, - isComponent, - }) - if (modelType) context.registerOperation({ type: IRNodeTypes.DIRECTIVE, diff --git a/packages/runtime-dom/src/directives/vModel.ts b/packages/runtime-dom/src/directives/vModel.ts index 41e33a68af..256b915592 100644 --- a/packages/runtime-dom/src/directives/vModel.ts +++ b/packages/runtime-dom/src/directives/vModel.ts @@ -52,9 +52,9 @@ export const vModelText: ModelDirective< 'trim' | 'number' | 'lazy' > = { created(el, { modifiers: { lazy, trim, number } }, vnode) { + el[assignKey] = getModelAssigner(vnode) vModelTextInit( el, - (el[assignKey] = getModelAssigner(vnode)), trim, number || !!(vnode.props && vnode.props.type === 'number'), lazy, @@ -70,7 +70,7 @@ export const vModelText: ModelDirective< vnode, ) { el[assignKey] = getModelAssigner(vnode) - vModelTextUpdate(el, value, oldValue, trim, number, lazy) + vModelTextUpdate(el, oldValue, value, trim, number, lazy) }, } @@ -79,10 +79,10 @@ export const vModelText: ModelDirective< */ export const vModelTextInit = ( el: HTMLInputElement | HTMLTextAreaElement, - set: (v: any) => void, trim: boolean | undefined, number: boolean | undefined, lazy: boolean | undefined, + set?: (v: any) => void, ): void => { addEventListener(el, lazy ? 'change' : 'input', e => { if ((e.target as any).composing) return @@ -93,7 +93,7 @@ export const vModelTextInit = ( if (number) { domValue = looseToNumber(domValue) } - set(domValue) + ;(set || (el as any)[assignKey])(domValue) }) if (trim) { addEventListener(el, 'change', () => { @@ -116,8 +116,8 @@ export const vModelTextInit = ( */ export const vModelTextUpdate = ( el: HTMLInputElement | HTMLTextAreaElement, - value: any, oldValue: any, + value: any, trim: boolean | undefined, number: boolean | undefined, lazy: boolean | undefined, @@ -152,56 +152,82 @@ export const vModelCheckbox: ModelDirective = { deep: true, created(el, _, vnode) { el[assignKey] = getModelAssigner(vnode) - addEventListener(el, 'change', () => { - const modelValue = (el as any)._modelValue - const elementValue = getValue(el) - const checked = el.checked - const assign = el[assignKey] - if (isArray(modelValue)) { - const index = looseIndexOf(modelValue, elementValue) - const found = index !== -1 - if (checked && !found) { - assign(modelValue.concat(elementValue)) - } else if (!checked && found) { - const filtered = [...modelValue] - filtered.splice(index, 1) - assign(filtered) - } - } else if (isSet(modelValue)) { - const cloned = new Set(modelValue) - if (checked) { - cloned.add(elementValue) - } else { - cloned.delete(elementValue) - } - assign(cloned) - } else { - assign(getCheckboxValue(el, checked)) - } - }) + vModelCheckboxInit(el) }, // set initial checked on mount to wait for true-value/false-value - mounted: setChecked, + mounted(el, binding, vnode) { + vModelCheckboxUpdate( + el, + binding.oldValue, + binding.value, + vnode.props!.value, + ) + }, beforeUpdate(el, binding, vnode) { el[assignKey] = getModelAssigner(vnode) - setChecked(el, binding, vnode) + vModelCheckboxUpdate( + el, + binding.oldValue, + binding.value, + vnode.props!.value, + ) }, } -function setChecked( +/** + * @internal + */ +export const vModelCheckboxInit = ( el: HTMLInputElement, - { value, oldValue }: DirectiveBinding, - vnode: VNode, -) { + set?: (v: any) => void, +): void => { + addEventListener(el, 'change', () => { + const assign = set || (el as any)[assignKey] + const modelValue = (el as any)._modelValue + const elementValue = getValue(el) + const checked = el.checked + if (isArray(modelValue)) { + const index = looseIndexOf(modelValue, elementValue) + const found = index !== -1 + if (checked && !found) { + assign(modelValue.concat(elementValue)) + } else if (!checked && found) { + const filtered = [...modelValue] + filtered.splice(index, 1) + assign(filtered) + } + } else if (isSet(modelValue)) { + const cloned = new Set(modelValue) + if (checked) { + cloned.add(elementValue) + } else { + cloned.delete(elementValue) + } + assign(cloned) + } else { + assign(getCheckboxValue(el, checked)) + } + }) +} + +/** + * @internal + */ +export const vModelCheckboxUpdate = ( + el: HTMLInputElement, + oldValue: any, + value: any, + rawValue: any = getValue(el), +): void => { // store the v-model value on the element so it can be accessed by the // change listener. ;(el as any)._modelValue = value let checked: boolean if (isArray(value)) { - checked = looseIndexOf(value, vnode.props!.value) > -1 + checked = looseIndexOf(value, rawValue) > -1 } else if (isSet(value)) { - checked = value.has(vnode.props!.value) + checked = value.has(rawValue) } else { if (value === oldValue) return checked = looseEqual(value, getCheckboxValue(el, true)) @@ -233,43 +259,57 @@ export const vModelSelect: ModelDirective = { // relies on its children //