diff --git a/packages/compiler-core/__tests__/transforms/vModel.spec.ts b/packages/compiler-core/__tests__/transforms/vModel.spec.ts index 82dd4909fd6..10a1952988a 100644 --- a/packages/compiler-core/__tests__/transforms/vModel.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vModel.spec.ts @@ -507,6 +507,24 @@ describe('compiler: transform v-model', () => { ) }) + test('should generate modelModifiers$ for component v-model:model with arguments', () => { + const root = parseWithVModel('', { + prefixIdentifiers: true, + }) + const vnodeCall = (root.children[0] as ComponentNode) + .codegenNode as VNodeCall + expect(vnodeCall.props).toMatchObject({ + properties: [ + { key: { content: `model` } }, + { key: { content: `onUpdate:model` } }, + { + key: { content: 'modelModifiers$' }, + value: { content: `{ trim: true }`, isStatic: false }, + }, + ], + }) + }) + describe('errors', () => { test('missing expression', () => { const onError = vi.fn() diff --git a/packages/compiler-core/src/transforms/vModel.ts b/packages/compiler-core/src/transforms/vModel.ts index 598c1ea4387..40e6a86f10f 100644 --- a/packages/compiler-core/src/transforms/vModel.ts +++ b/packages/compiler-core/src/transforms/vModel.ts @@ -18,7 +18,7 @@ import { } from '../utils' import { IS_REF } from '../runtimeHelpers' import { BindingTypes } from '../options' -import { camelize } from '@vue/shared' +import { camelize, getModifierPropName } from '@vue/shared' export const transformModel: DirectiveTransform = (dir, node, context) => { const { exp, arg } = dir @@ -136,7 +136,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => { .join(`, `) const modifiersKey = arg ? isStaticExp(arg) - ? `${arg.content}Modifiers` + ? getModifierPropName(arg.content) : createCompoundExpression([arg, ' + "Modifiers"']) : `modelModifiers` props.push( diff --git a/packages/compiler-sfc/src/script/defineModel.ts b/packages/compiler-sfc/src/script/defineModel.ts index 05082800284..75c8e2cdb79 100644 --- a/packages/compiler-sfc/src/script/defineModel.ts +++ b/packages/compiler-sfc/src/script/defineModel.ts @@ -3,6 +3,7 @@ import type { ScriptCompileContext } from './context' import { inferRuntimeType } from './resolveType' import { UNKNOWN_TYPE, isCallOf, toRuntimeTypeString } from './utils' import { BindingTypes, unwrapTSNode } from '@vue/compiler-dom' +import { getModifierPropName } from '@vue/shared' export const DEFINE_MODEL = 'defineModel' @@ -167,9 +168,7 @@ export function genModelProps(ctx: ScriptCompileContext) { modelPropsDecl += `\n ${JSON.stringify(name)}: ${decl},` // also generate modifiers prop - const modifierPropName = JSON.stringify( - name === 'modelValue' ? `modelModifiers` : `${name}Modifiers`, - ) + const modifierPropName = JSON.stringify(getModifierPropName(name)) modelPropsDecl += `\n ${modifierPropName}: {},` } return `{${modelPropsDecl}\n }` diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap index 5ef064974c0..77a689796af 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap @@ -81,6 +81,18 @@ export function render(_ctx) { }" `; +exports[`compiler: vModel transform > component > v-model:model with arguments for component should generate modelModifiers$ 1`] = ` +"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue'; + +export function render(_ctx) { + const _component_Comp = _resolveComponent("Comp") + const n0 = _createComponentWithFallback(_component_Comp, { model: () => (_ctx.foo), + "onUpdate:model": () => _value => (_ctx.foo = _value), + modelModifiers$: () => ({ trim: true }) }, null, true) + return n0 +}" +`; + exports[`compiler: vModel transform > modifiers > .lazy 1`] = ` "import { applyTextModel as _applyTextModel, template as _template } from 'vue'; const t0 = _template("", true) diff --git a/packages/compiler-vapor/__tests__/transforms/vModel.spec.ts b/packages/compiler-vapor/__tests__/transforms/vModel.spec.ts index 51eaa9e0230..23b99ec7019 100644 --- a/packages/compiler-vapor/__tests__/transforms/vModel.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/vModel.spec.ts @@ -319,6 +319,28 @@ describe('compiler: vModel transform', () => { }) }) + test('v-model:model with arguments for component should generate modelModifiers$', () => { + const { code, ir } = compileWithVModel( + '', + ) + expect(code).toMatchSnapshot() + expect(code).contain(`modelModifiers$: () => ({ trim: true })`) + expect(ir.block.dynamic.children[0].operation).toMatchObject({ + type: IRNodeTypes.CREATE_COMPONENT_NODE, + tag: 'Comp', + props: [ + [ + { + key: { content: 'model', isStatic: true }, + values: [{ content: 'foo', isStatic: false }], + model: true, + modelModifiers: ['trim'], + }, + ], + ], + }) + }) + test('v-model with dynamic arguments for component should generate modelModifiers ', () => { const { code, ir } = compileWithVModel( '', diff --git a/packages/compiler-vapor/src/generators/component.ts b/packages/compiler-vapor/src/generators/component.ts index 7c232db754b..516bf94b048 100644 --- a/packages/compiler-vapor/src/generators/component.ts +++ b/packages/compiler-vapor/src/generators/component.ts @@ -1,4 +1,4 @@ -import { camelize, extend, isArray } from '@vue/shared' +import { camelize, extend, getModifierPropName, isArray } from '@vue/shared' import type { CodegenContext } from '../generate' import { type CreateComponentIRNode, @@ -240,9 +240,7 @@ function genModelModifiers( if (!modelModifiers || !modelModifiers.length) return [] const modifiersKey = key.isStatic - ? key.content === 'modelValue' - ? [`modelModifiers`] - : [`${key.content}Modifiers`] + ? [getModifierPropName(key.content)] : ['[', ...genExpression(key, context), ' + "Modifiers"]'] const modifiersVal = genDirectiveModifiers(modelModifiers) diff --git a/packages/runtime-core/src/helpers/useModel.ts b/packages/runtime-core/src/helpers/useModel.ts index e85edc6e9a7..f2a8be36a54 100644 --- a/packages/runtime-core/src/helpers/useModel.ts +++ b/packages/runtime-core/src/helpers/useModel.ts @@ -1,5 +1,11 @@ import { type Ref, customRef, ref } from '@vue/reactivity' -import { EMPTY_OBJ, camelize, hasChanged, hyphenate } from '@vue/shared' +import { + EMPTY_OBJ, + camelize, + getModifierPropName, + hasChanged, + hyphenate, +} from '@vue/shared' import type { DefineModelOptions, ModelRef } from '../apiSetupHelpers' import { type ComponentInternalInstance, @@ -145,9 +151,9 @@ export const getModelModifiers = ( modelName: string, getter: (props: Record, key: string) => any, ): Record | undefined => { - return modelName === 'modelValue' || modelName === 'model-value' - ? getter(props, 'modelModifiers') - : getter(props, `${modelName}Modifiers`) || - getter(props, `${camelize(modelName)}Modifiers`) || - getter(props, `${hyphenate(modelName)}Modifiers`) + return ( + getter(props, getModifierPropName(modelName)) || + getter(props, `${camelize(modelName)}Modifiers`) || + getter(props, `${hyphenate(modelName)}Modifiers`) + ) } diff --git a/packages/shared/src/general.ts b/packages/shared/src/general.ts index bf11ba7a293..5a6e7449340 100644 --- a/packages/shared/src/general.ts +++ b/packages/shared/src/general.ts @@ -153,6 +153,18 @@ export const toHandlerKey: ( }, ) +/** + * #13070 When v-model and v-model:model directives are used together, + * they will generate the same modelModifiers prop, + * so a `$` suffix is added to avoid conflicts. + * @private + */ +export const getModifierPropName = (name: string): string => { + return `${ + name === 'modelValue' || name === 'model-value' ? 'model' : name + }Modifiers${name === 'model' ? '$' : ''}` +} + // compare whether a value has changed, accounting for NaN. export const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue)