Skip to content

Commit dd4f72d

Browse files
committed
fix(compat): allow v-model built in modifiers on component
close vuejs#12652
1 parent 6eb29d3 commit dd4f72d

File tree

3 files changed

+76
-3
lines changed

3 files changed

+76
-3
lines changed

packages/runtime-core/src/componentEmits.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
import type { ComponentTypeEmits } from './apiSetupHelpers'
3333
import { getModelModifiers } from './helpers/useModel'
3434
import type { ComponentPublicInstance } from './componentPublicInstance'
35+
import { DeprecationTypes, isCompatEnabled } from './compat/compatConfig'
3536

3637
export type ObjectEmitsOptions = Record<
3738
string,
@@ -151,10 +152,14 @@ export function emit(
151152
}
152153

153154
let args = rawArgs
154-
const isModelListener = event.startsWith('update:')
155+
const isModelListener =
156+
__COMPAT__ && isCompatEnabled(DeprecationTypes.COMPONENT_V_MODEL, instance)
157+
? compatModelEventPrefix + event in props
158+
: event.startsWith('update:')
155159

156160
// for v-model update:xxx events, apply modifiers on args
157-
const modifiers = isModelListener && getModelModifiers(props, event.slice(7))
161+
const modifiers =
162+
isModelListener && getModelModifiers(instance, props, event.slice(7))
158163
if (modifiers) {
159164
if (modifiers.trim) {
160165
args = rawArgs.map(a => (isString(a) ? a.trim() : a))

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

+11-1
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import { type Ref, customRef, ref } from '@vue/reactivity'
22
import { EMPTY_OBJ, camelize, hasChanged, hyphenate } from '@vue/shared'
33
import type { DefineModelOptions, ModelRef } from '../apiSetupHelpers'
44
import { getCurrentInstance } from '../component'
5+
import type { ComponentInternalInstance } from '../component'
56
import { warn } from '../warning'
67
import type { NormalizedProps } from '../componentProps'
78
import { watchSyncEffect } from '../apiWatch'
9+
import { DeprecationTypes, isCompatEnabled } from '../compat/compatConfig'
810

911
export function useModel<
1012
M extends PropertyKey,
@@ -35,7 +37,7 @@ export function useModel(
3537
}
3638

3739
const hyphenatedName = hyphenate(name)
38-
const modifiers = getModelModifiers(props, camelizedName)
40+
const modifiers = getModelModifiers(i, props, camelizedName)
3941

4042
const res = customRef((track, trigger) => {
4143
let localValue: any
@@ -118,9 +120,17 @@ export function useModel(
118120
}
119121

120122
export const getModelModifiers = (
123+
instance: ComponentInternalInstance,
121124
props: Record<string, any>,
122125
modelName: string,
123126
): Record<string, boolean> | undefined => {
127+
if (
128+
__COMPAT__ &&
129+
isCompatEnabled(DeprecationTypes.COMPONENT_V_MODEL, instance)
130+
) {
131+
return props.modelModifiers
132+
}
133+
124134
return modelName === 'modelValue' || modelName === 'model-value'
125135
? props.modelModifiers
126136
: props[`${modelName}Modifiers`] ||

packages/vue-compat/__tests__/componentVModel.spec.ts

+58
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,62 @@ describe('COMPONENT_V_MODEL', () => {
8282
template: `<input :value="input" @input="$emit('update', $event.target.value)">`,
8383
})
8484
})
85+
86+
async function runTestWithModifier(CustomInput: ComponentOptions) {
87+
const vm = new Vue({
88+
data() {
89+
return {
90+
text: ' foo ',
91+
}
92+
},
93+
components: {
94+
CustomInput,
95+
},
96+
template: `
97+
<div>
98+
<span>{{ text }}</span>
99+
<custom-input v-model.trim="text"></custom-input>
100+
</div>
101+
`,
102+
}).$mount() as any
103+
104+
const input = vm.$el.querySelector('input')
105+
const span = vm.$el.querySelector('span')
106+
107+
expect(input.value).toBe(' foo ')
108+
expect(span.textContent).toBe(' foo ')
109+
110+
expect(
111+
(deprecationData[DeprecationTypes.COMPONENT_V_MODEL].message as Function)(
112+
CustomInput,
113+
),
114+
).toHaveBeenWarned()
115+
116+
input.value = ' bar '
117+
triggerEvent(input, 'input')
118+
await nextTick()
119+
120+
expect(input.value).toBe('bar')
121+
expect(span.textContent).toBe('bar')
122+
}
123+
124+
test('with model modifiers', async () => {
125+
await runTestWithModifier({
126+
name: 'CustomInput',
127+
props: ['value'],
128+
template: `<input :value="value" @input="$emit('input', $event.target.value)">`,
129+
})
130+
})
131+
132+
test('with model modifiers and model option', async () => {
133+
await runTestWithModifier({
134+
name: 'CustomInput',
135+
props: ['foo'],
136+
model: {
137+
prop: 'foo',
138+
event: 'bar',
139+
},
140+
template: `<input :value="foo" @input="$emit('bar', $event.target.value)">`,
141+
})
142+
})
85143
})

0 commit comments

Comments
 (0)