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)