Skip to content

Commit bf2d2b2

Browse files
authored
fix(vapor): v-model and v-model:model co-usage (#13070)
1 parent c1f2289 commit bf2d2b2

File tree

9 files changed

+83
-16
lines changed

9 files changed

+83
-16
lines changed

packages/compiler-core/__tests__/transforms/vModel.spec.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,24 @@ describe('compiler: transform v-model', () => {
507507
)
508508
})
509509

510+
test('should generate modelModifiers$ for component v-model:model with arguments', () => {
511+
const root = parseWithVModel('<Comp v-model:model.trim="foo" />', {
512+
prefixIdentifiers: true,
513+
})
514+
const vnodeCall = (root.children[0] as ComponentNode)
515+
.codegenNode as VNodeCall
516+
expect(vnodeCall.props).toMatchObject({
517+
properties: [
518+
{ key: { content: `model` } },
519+
{ key: { content: `onUpdate:model` } },
520+
{
521+
key: { content: 'modelModifiers$' },
522+
value: { content: `{ trim: true }`, isStatic: false },
523+
},
524+
],
525+
})
526+
})
527+
510528
describe('errors', () => {
511529
test('missing expression', () => {
512530
const onError = vi.fn()

packages/compiler-core/src/transforms/vModel.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
} from '../utils'
1919
import { IS_REF } from '../runtimeHelpers'
2020
import { BindingTypes } from '../options'
21-
import { camelize } from '@vue/shared'
21+
import { camelize, getModifierPropName } from '@vue/shared'
2222

2323
export const transformModel: DirectiveTransform = (dir, node, context) => {
2424
const { exp, arg } = dir
@@ -136,7 +136,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
136136
.join(`, `)
137137
const modifiersKey = arg
138138
? isStaticExp(arg)
139-
? `${arg.content}Modifiers`
139+
? getModifierPropName(arg.content)
140140
: createCompoundExpression([arg, ' + "Modifiers"'])
141141
: `modelModifiers`
142142
props.push(

packages/compiler-sfc/src/script/defineModel.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { ScriptCompileContext } from './context'
33
import { inferRuntimeType } from './resolveType'
44
import { UNKNOWN_TYPE, isCallOf, toRuntimeTypeString } from './utils'
55
import { BindingTypes, unwrapTSNode } from '@vue/compiler-dom'
6+
import { getModifierPropName } from '@vue/shared'
67

78
export const DEFINE_MODEL = 'defineModel'
89

@@ -167,9 +168,7 @@ export function genModelProps(ctx: ScriptCompileContext): string | undefined {
167168
modelPropsDecl += `\n ${JSON.stringify(name)}: ${decl},`
168169

169170
// also generate modifiers prop
170-
const modifierPropName = JSON.stringify(
171-
name === 'modelValue' ? `modelModifiers` : `${name}Modifiers`,
172-
)
171+
const modifierPropName = JSON.stringify(getModifierPropName(name))
173172
modelPropsDecl += `\n ${modifierPropName}: {},`
174173
}
175174
return `{${modelPropsDecl}\n }`

packages/compiler-vapor/__tests__/transforms/__snapshots__/transformText.spec.ts.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const t0 = _template("<div>2 foo1 1 1 1</div>", true)
1717
1818
export function render(_ctx) {
1919
const n1 = t0()
20-
const n0 = _child(n1)
20+
const n0 = _child(n1, 0)
2121
return n1
2222
}"
2323
`;

packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,18 @@ export function render(_ctx) {
8181
}"
8282
`;
8383

84+
exports[`compiler: vModel transform > component > v-model:model with arguments for component should generate modelModifiers$ 1`] = `
85+
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
86+
87+
export function render(_ctx) {
88+
const _component_Comp = _resolveComponent("Comp")
89+
const n0 = _createComponentWithFallback(_component_Comp, { model: () => (_ctx.foo),
90+
"onUpdate:model": () => _value => (_ctx.foo = _value),
91+
modelModifiers$: () => ({ trim: true }) }, null, true)
92+
return n0
93+
}"
94+
`;
95+
8496
exports[`compiler: vModel transform > modifiers > .lazy 1`] = `
8597
"import { applyTextModel as _applyTextModel, template as _template } from 'vue';
8698
const t0 = _template("<input>", true)

packages/compiler-vapor/__tests__/transforms/vModel.spec.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,28 @@ describe('compiler: vModel transform', () => {
319319
})
320320
})
321321

322+
test('v-model:model with arguments for component should generate modelModifiers$', () => {
323+
const { code, ir } = compileWithVModel(
324+
'<Comp v-model:model.trim="foo" />',
325+
)
326+
expect(code).toMatchSnapshot()
327+
expect(code).contain(`modelModifiers$: () => ({ trim: true })`)
328+
expect(ir.block.dynamic.children[0].operation).toMatchObject({
329+
type: IRNodeTypes.CREATE_COMPONENT_NODE,
330+
tag: 'Comp',
331+
props: [
332+
[
333+
{
334+
key: { content: 'model', isStatic: true },
335+
values: [{ content: 'foo', isStatic: false }],
336+
model: true,
337+
modelModifiers: ['trim'],
338+
},
339+
],
340+
],
341+
})
342+
})
343+
322344
test('v-model with dynamic arguments for component should generate modelModifiers ', () => {
323345
const { code, ir } = compileWithVModel(
324346
'<Comp v-model:[foo].trim="foo" v-model:[bar].number="bar" />',

packages/compiler-vapor/src/generators/component.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { camelize, extend, isArray } from '@vue/shared'
1+
import { camelize, extend, getModifierPropName, isArray } from '@vue/shared'
22
import type { CodegenContext } from '../generate'
33
import {
44
type CreateComponentIRNode,
@@ -257,9 +257,7 @@ function genModelModifiers(
257257
if (!modelModifiers || !modelModifiers.length) return []
258258

259259
const modifiersKey = key.isStatic
260-
? key.content === 'modelValue'
261-
? [`modelModifiers`]
262-
: [`${key.content}Modifiers`]
260+
? [getModifierPropName(key.content)]
263261
: ['[', ...genExpression(key, context), ' + "Modifiers"]']
264262

265263
const modifiersVal = genDirectiveModifiers(modelModifiers)

packages/runtime-core/src/helpers/useModel.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { type Ref, customRef, ref } from '@vue/reactivity'
2-
import { EMPTY_OBJ, camelize, hasChanged, hyphenate } from '@vue/shared'
2+
import {
3+
EMPTY_OBJ,
4+
camelize,
5+
getModifierPropName,
6+
hasChanged,
7+
hyphenate,
8+
} from '@vue/shared'
39
import type { DefineModelOptions, ModelRef } from '../apiSetupHelpers'
410
import {
511
type ComponentInternalInstance,
@@ -145,9 +151,9 @@ export const getModelModifiers = (
145151
modelName: string,
146152
getter: (props: Record<string, any>, key: string) => any,
147153
): Record<string, boolean> | undefined => {
148-
return modelName === 'modelValue' || modelName === 'model-value'
149-
? getter(props, 'modelModifiers')
150-
: getter(props, `${modelName}Modifiers`) ||
151-
getter(props, `${camelize(modelName)}Modifiers`) ||
152-
getter(props, `${hyphenate(modelName)}Modifiers`)
154+
return (
155+
getter(props, getModifierPropName(modelName)) ||
156+
getter(props, `${camelize(modelName)}Modifiers`) ||
157+
getter(props, `${hyphenate(modelName)}Modifiers`)
158+
)
153159
}

packages/shared/src/general.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,18 @@ export const toHandlerKey: <T extends string>(
153153
},
154154
)
155155

156+
/**
157+
* #13070 When v-model and v-model:model directives are used together,
158+
* they will generate the same modelModifiers prop,
159+
* so a `$` suffix is added to avoid conflicts.
160+
* @private
161+
*/
162+
export const getModifierPropName = (name: string): string => {
163+
return `${
164+
name === 'modelValue' || name === 'model-value' ? 'model' : name
165+
}Modifiers${name === 'model' ? '$' : ''}`
166+
}
167+
156168
// compare whether a value has changed, accounting for NaN.
157169
export const hasChanged = (value: any, oldValue: any): boolean =>
158170
!Object.is(value, oldValue)

0 commit comments

Comments
 (0)