Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(useVModel): add shouldEmit hook #2836

Merged
merged 7 commits into from
Mar 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
36 changes: 36 additions & 0 deletions packages/core/useVModel/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,4 +229,40 @@ describe('useVModel', () => {
child: { age: 2 },
})
})

it('should trigger beforeEmit', async () => {
const emitMock = vitest.fn()
const beforeEmitMock = vitest.fn()
let res = ''
const beforeEmit = (value: string) => {
res = value
beforeEmitMock()
return true
}
const data = useVModel(defaultProps(), undefined, emitMock, { shouldEmit: beforeEmit })
data.value = 'changed'

expect(emitMock).toHaveBeenCalledWith(isVue2 ? 'input' : 'update:modelValue', 'changed')
expect(beforeEmitMock).toHaveBeenCalled()
await nextTick()
expect(res).toBe('changed')
})

it('should not trigger beforeEmit (return false)', async () => {
const emitMock = vitest.fn()
const beforeEmitMock = vitest.fn()
let res = ''
const beforeEmit = (value: string) => {
res = value
beforeEmitMock()
return false
}
const data = useVModel(defaultProps(), undefined, emitMock, { shouldEmit: beforeEmit })
data.value = 'changed'

expect(emitMock).not.toHaveBeenCalled()
expect(beforeEmitMock).toHaveBeenCalled()
await nextTick()
expect(res).toBe('changed')
})
})
22 changes: 20 additions & 2 deletions packages/core/useVModel/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ export interface UseVModelOptions<T> {
* @default false
*/
clone?: boolean | CloneFn<T>
/**
* The hook before triggering the emit event can be used for form validation.
* if false is returned, the emit event will not be triggered.
*
* @default undefined
*/
shouldEmit?: (v: T) => boolean
}

/**
Expand All @@ -61,6 +68,7 @@ export function useVModel<P extends object, K extends keyof P, Name extends stri
eventName,
deep = false,
defaultValue,
shouldEmit,
} = options

const vm = getCurrentInstance()
Expand Down Expand Up @@ -92,6 +100,16 @@ export function useVModel<P extends object, K extends keyof P, Name extends stri
? cloneFn(props[key!])
: defaultValue

const triggerEmit = (value: P[K]) => {
if (shouldEmit) {
if (shouldEmit(value))
_emit(event, value)
}
else {
_emit(event, value)
}
}

if (passive) {
const initialValue = getValue()
const proxy = ref<P[K]>(initialValue!)
Expand All @@ -105,7 +123,7 @@ export function useVModel<P extends object, K extends keyof P, Name extends stri
proxy,
(v) => {
if (v !== props[key!] || deep)
_emit(event, v)
triggerEmit(v as P[K])
},
{ deep },
)
Expand All @@ -118,7 +136,7 @@ export function useVModel<P extends object, K extends keyof P, Name extends stri
return getValue()!
},
set(value) {
_emit(event, value)
triggerEmit(value)
},
})
}
Expand Down