Skip to content

Commit

Permalink
fix(compat): allow v-model built in modifiers on component
Browse files Browse the repository at this point in the history
  • Loading branch information
markrian committed Jan 6, 2025
1 parent 6eb29d3 commit dd4f72d
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 3 deletions.
9 changes: 7 additions & 2 deletions packages/runtime-core/src/componentEmits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
import type { ComponentTypeEmits } from './apiSetupHelpers'
import { getModelModifiers } from './helpers/useModel'
import type { ComponentPublicInstance } from './componentPublicInstance'
import { DeprecationTypes, isCompatEnabled } from './compat/compatConfig'

export type ObjectEmitsOptions = Record<
string,
Expand Down Expand Up @@ -151,10 +152,14 @@ export function emit(
}

let args = rawArgs
const isModelListener = event.startsWith('update:')
const isModelListener =
__COMPAT__ && isCompatEnabled(DeprecationTypes.COMPONENT_V_MODEL, instance)
? compatModelEventPrefix + event in props
: event.startsWith('update:')

// for v-model update:xxx events, apply modifiers on args
const modifiers = isModelListener && getModelModifiers(props, event.slice(7))
const modifiers =
isModelListener && getModelModifiers(instance, props, event.slice(7))
if (modifiers) {
if (modifiers.trim) {
args = rawArgs.map(a => (isString(a) ? a.trim() : a))
Expand Down
12 changes: 11 additions & 1 deletion packages/runtime-core/src/helpers/useModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import { type Ref, customRef, ref } from '@vue/reactivity'
import { EMPTY_OBJ, camelize, hasChanged, hyphenate } from '@vue/shared'
import type { DefineModelOptions, ModelRef } from '../apiSetupHelpers'
import { getCurrentInstance } from '../component'
import type { ComponentInternalInstance } from '../component'
import { warn } from '../warning'
import type { NormalizedProps } from '../componentProps'
import { watchSyncEffect } from '../apiWatch'
import { DeprecationTypes, isCompatEnabled } from '../compat/compatConfig'

export function useModel<
M extends PropertyKey,
Expand Down Expand Up @@ -35,7 +37,7 @@ export function useModel(
}

const hyphenatedName = hyphenate(name)
const modifiers = getModelModifiers(props, camelizedName)
const modifiers = getModelModifiers(i, props, camelizedName)

const res = customRef((track, trigger) => {
let localValue: any
Expand Down Expand Up @@ -118,9 +120,17 @@ export function useModel(
}

export const getModelModifiers = (
instance: ComponentInternalInstance,
props: Record<string, any>,
modelName: string,
): Record<string, boolean> | undefined => {
if (
__COMPAT__ &&
isCompatEnabled(DeprecationTypes.COMPONENT_V_MODEL, instance)
) {
return props.modelModifiers
}

return modelName === 'modelValue' || modelName === 'model-value'
? props.modelModifiers
: props[`${modelName}Modifiers`] ||
Expand Down
58 changes: 58 additions & 0 deletions packages/vue-compat/__tests__/componentVModel.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,62 @@ describe('COMPONENT_V_MODEL', () => {
template: `<input :value="input" @input="$emit('update', $event.target.value)">`,
})
})

async function runTestWithModifier(CustomInput: ComponentOptions) {
const vm = new Vue({
data() {
return {
text: ' foo ',
}
},
components: {
CustomInput,
},
template: `
<div>
<span>{{ text }}</span>
<custom-input v-model.trim="text"></custom-input>
</div>
`,
}).$mount() as any

const input = vm.$el.querySelector('input')
const span = vm.$el.querySelector('span')

expect(input.value).toBe(' foo ')
expect(span.textContent).toBe(' foo ')

expect(
(deprecationData[DeprecationTypes.COMPONENT_V_MODEL].message as Function)(
CustomInput,
),
).toHaveBeenWarned()

input.value = ' bar '
triggerEvent(input, 'input')
await nextTick()

expect(input.value).toBe('bar')
expect(span.textContent).toBe('bar')
}

test('with model modifiers', async () => {
await runTestWithModifier({
name: 'CustomInput',
props: ['value'],
template: `<input :value="value" @input="$emit('input', $event.target.value)">`,
})
})

test('with model modifiers and model option', async () => {
await runTestWithModifier({
name: 'CustomInput',
props: ['foo'],
model: {
prop: 'foo',
event: 'bar',
},
template: `<input :value="foo" @input="$emit('bar', $event.target.value)">`,
})
})
})

0 comments on commit dd4f72d

Please sign in to comment.