mirror of https://github.com/vuejs/core.git
feat(defineModel): support modifiers and transformers
This commit is contained in:
parent
d7bb32f9a3
commit
a772031ea8
|
@ -6,8 +6,11 @@ exports[`defineModel() > basic usage 1`] = `
|
|||
export default {
|
||||
props: {
|
||||
"modelValue": { required: true },
|
||||
"modelModifiers": {},
|
||||
"count": {},
|
||||
"countModifiers": {},
|
||||
"toString": { type: Function },
|
||||
"toStringModifiers": {},
|
||||
},
|
||||
emits: ["update:modelValue", "update:count", "update:toString"],
|
||||
setup(__props, { expose: __expose }) {
|
||||
|
@ -23,12 +26,58 @@ return { modelValue, c, toString }
|
|||
}"
|
||||
`;
|
||||
|
||||
exports[`defineModel() > get / set transformers 1`] = `
|
||||
"import { useModel as _useModel, defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
props: {
|
||||
"modelValue": {
|
||||
required: true
|
||||
},
|
||||
"modelModifiers": {},
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
const modelValue = _useModel(__props, "modelValue", { get(v) { return v - 1 }, set: (v) => { return v + 1 }, })
|
||||
|
||||
return { modelValue }
|
||||
}
|
||||
|
||||
})"
|
||||
`;
|
||||
|
||||
exports[`defineModel() > get / set transformers 2`] = `
|
||||
"import { useModel as _useModel, defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
props: {
|
||||
"modelValue": {
|
||||
default: 0,
|
||||
required: true,
|
||||
},
|
||||
"modelModifiers": {},
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
const modelValue = _useModel(__props, "modelValue", { get(v) { return v - 1 }, set: (v) => { return v + 1 }, })
|
||||
|
||||
return { modelValue }
|
||||
}
|
||||
|
||||
})"
|
||||
`;
|
||||
|
||||
exports[`defineModel() > w/ array props 1`] = `
|
||||
"import { useModel as _useModel, mergeModels as _mergeModels } from 'vue'
|
||||
|
||||
export default {
|
||||
props: /*#__PURE__*/_mergeModels(['foo', 'bar'], {
|
||||
"count": {},
|
||||
"countModifiers": {},
|
||||
}),
|
||||
emits: ["update:count"],
|
||||
setup(__props, { expose: __expose }) {
|
||||
|
@ -49,6 +98,7 @@ exports[`defineModel() > w/ defineProps and defineEmits 1`] = `
|
|||
export default {
|
||||
props: /*#__PURE__*/_mergeModels({ foo: String }, {
|
||||
"modelValue": { default: 0 },
|
||||
"modelModifiers": {},
|
||||
}),
|
||||
emits: /*#__PURE__*/_mergeModels(['change'], ["update:modelValue"]),
|
||||
setup(__props, { expose: __expose }) {
|
||||
|
@ -64,47 +114,19 @@ return { count }
|
|||
}"
|
||||
`;
|
||||
|
||||
exports[`defineModel() > w/ local flag 1`] = `
|
||||
"import { useModel as _useModel } from 'vue'
|
||||
const local = true
|
||||
|
||||
export default {
|
||||
props: {
|
||||
"modelValue": { local: true, default: 1 },
|
||||
"bar": { [key]: true },
|
||||
"baz": { ...x },
|
||||
"qux": x,
|
||||
"foo2": { local: true, ...x },
|
||||
"hoist": { local },
|
||||
},
|
||||
emits: ["update:modelValue", "update:bar", "update:baz", "update:qux", "update:foo2", "update:hoist"],
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
const foo = _useModel(__props, "modelValue", { local: true })
|
||||
const bar = _useModel(__props, "bar", { [key]: true })
|
||||
const baz = _useModel(__props, "baz", { ...x })
|
||||
const qux = _useModel(__props, "qux", x)
|
||||
|
||||
const foo2 = _useModel(__props, "foo2", { local: true })
|
||||
|
||||
const hoist = _useModel(__props, "hoist", { local })
|
||||
|
||||
return { foo, bar, baz, qux, foo2, local, hoist }
|
||||
}
|
||||
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`defineModel() > w/ types, basic usage 1`] = `
|
||||
"import { useModel as _useModel, defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
props: {
|
||||
"modelValue": { type: [Boolean, String] },
|
||||
"modelModifiers": {},
|
||||
"count": { type: Number },
|
||||
"countModifiers": {},
|
||||
"disabled": { type: Number, ...{ required: false } },
|
||||
"disabledModifiers": {},
|
||||
"any": { type: Boolean, skipCheck: true },
|
||||
"anyModifiers": {},
|
||||
},
|
||||
emits: ["update:modelValue", "update:count", "update:disabled", "update:any"],
|
||||
setup(__props, { expose: __expose }) {
|
||||
|
@ -127,10 +149,15 @@ exports[`defineModel() > w/ types, production mode 1`] = `
|
|||
export default /*#__PURE__*/_defineComponent({
|
||||
props: {
|
||||
"modelValue": { type: Boolean },
|
||||
"modelModifiers": {},
|
||||
"fn": {},
|
||||
"fnModifiers": {},
|
||||
"fnWithDefault": { type: Function, ...{ default: () => null } },
|
||||
"fnWithDefaultModifiers": {},
|
||||
"str": {},
|
||||
"strModifiers": {},
|
||||
"optional": { required: false },
|
||||
"optionalModifiers": {},
|
||||
},
|
||||
emits: ["update:modelValue", "update:fn", "update:fnWithDefault", "update:str", "update:optional"],
|
||||
setup(__props, { expose: __expose }) {
|
||||
|
|
|
@ -69,6 +69,7 @@ describe('defineModel()', () => {
|
|||
assertCode(content)
|
||||
expect(content).toMatch(`props: /*#__PURE__*/_mergeModels(['foo', 'bar'], {
|
||||
"count": {},
|
||||
"countModifiers": {},
|
||||
})`)
|
||||
expect(content).toMatch(`const count = _useModel(__props, "count")`)
|
||||
expect(content).not.toMatch('defineModel')
|
||||
|
@ -79,29 +80,6 @@ describe('defineModel()', () => {
|
|||
})
|
||||
})
|
||||
|
||||
test('w/ local flag', () => {
|
||||
const { content } = compile(
|
||||
`<script setup>
|
||||
const foo = defineModel({ local: true, default: 1 })
|
||||
const bar = defineModel('bar', { [key]: true })
|
||||
const baz = defineModel('baz', { ...x })
|
||||
const qux = defineModel('qux', x)
|
||||
|
||||
const foo2 = defineModel('foo2', { local: true, ...x })
|
||||
|
||||
const local = true
|
||||
const hoist = defineModel('hoist', { local })
|
||||
</script>`,
|
||||
)
|
||||
assertCode(content)
|
||||
expect(content).toMatch(`_useModel(__props, "modelValue", { local: true })`)
|
||||
expect(content).toMatch(`_useModel(__props, "bar", { [key]: true })`)
|
||||
expect(content).toMatch(`_useModel(__props, "baz", { ...x })`)
|
||||
expect(content).toMatch(`_useModel(__props, "qux", x)`)
|
||||
expect(content).toMatch(`_useModel(__props, "foo2", { local: true })`)
|
||||
expect(content).toMatch(`_useModel(__props, "hoist", { local })`)
|
||||
})
|
||||
|
||||
test('w/ types, basic usage', () => {
|
||||
const { content, bindings } = compile(
|
||||
`
|
||||
|
@ -115,6 +93,7 @@ describe('defineModel()', () => {
|
|||
)
|
||||
assertCode(content)
|
||||
expect(content).toMatch('"modelValue": { type: [Boolean, String] }')
|
||||
expect(content).toMatch('"modelModifiers": {}')
|
||||
expect(content).toMatch('"count": { type: Number }')
|
||||
expect(content).toMatch(
|
||||
'"disabled": { type: Number, ...{ required: false } }',
|
||||
|
@ -176,4 +155,43 @@ describe('defineModel()', () => {
|
|||
optional: BindingTypes.SETUP_REF,
|
||||
})
|
||||
})
|
||||
|
||||
test('get / set transformers', () => {
|
||||
const { content } = compile(
|
||||
`
|
||||
<script setup lang="ts">
|
||||
const modelValue = defineModel({
|
||||
get(v) { return v - 1 },
|
||||
set: (v) => { return v + 1 },
|
||||
required: true
|
||||
})
|
||||
</script>
|
||||
`,
|
||||
)
|
||||
assertCode(content)
|
||||
expect(content).toMatch(/"modelValue": {\s+required: true,?\s+}/m)
|
||||
expect(content).toMatch(
|
||||
`_useModel(__props, "modelValue", { get(v) { return v - 1 }, set: (v) => { return v + 1 }, })`,
|
||||
)
|
||||
|
||||
const { content: content2 } = compile(
|
||||
`
|
||||
<script setup lang="ts">
|
||||
const modelValue = defineModel({
|
||||
default: 0,
|
||||
get(v) { return v - 1 },
|
||||
required: true,
|
||||
set: (v) => { return v + 1 },
|
||||
})
|
||||
</script>
|
||||
`,
|
||||
)
|
||||
assertCode(content2)
|
||||
expect(content2).toMatch(
|
||||
/"modelValue": {\s+default: 0,\s+required: true,?\s+}/m,
|
||||
)
|
||||
expect(content2).toMatch(
|
||||
`_useModel(__props, "modelValue", { get(v) { return v - 1 }, set: (v) => { return v + 1 }, })`,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { LVal, Node, ObjectProperty, TSType } from '@babel/types'
|
||||
import type { LVal, Node, TSType } from '@babel/types'
|
||||
import type { ScriptCompileContext } from './context'
|
||||
import { inferRuntimeType } from './resolveType'
|
||||
import {
|
||||
|
@ -45,7 +45,42 @@ export function processDefineModel(
|
|||
ctx.error(`duplicate model name ${JSON.stringify(modelName)}`, node)
|
||||
}
|
||||
|
||||
const optionsString = options && ctx.getString(options)
|
||||
let optionsString = options && ctx.getString(options)
|
||||
let runtimeOptions = ''
|
||||
let transformOptions = ''
|
||||
|
||||
if (options) {
|
||||
if (options.type === 'ObjectExpression') {
|
||||
for (let i = options.properties.length - 1; i >= 0; i--) {
|
||||
const p = options.properties[i]
|
||||
if (p.type === 'SpreadElement' || p.computed) {
|
||||
runtimeOptions = optionsString!
|
||||
break
|
||||
}
|
||||
if (
|
||||
(p.type === 'ObjectProperty' || p.type === 'ObjectMethod') &&
|
||||
((p.key.type === 'Identifier' &&
|
||||
(p.key.name === 'get' || p.key.name === 'set')) ||
|
||||
(p.key.type === 'StringLiteral' &&
|
||||
(p.key.value === 'get' || p.key.value === 'set')))
|
||||
) {
|
||||
transformOptions = ctx.getString(p) + ', ' + transformOptions
|
||||
|
||||
// remove transform option from prop options to avoid duplicates
|
||||
const offset = p.start! - options.start!
|
||||
const next = options.properties[i + 1]
|
||||
const end = (next ? next.start! : options.end! - 1) - options.start!
|
||||
optionsString =
|
||||
optionsString.slice(0, offset) + optionsString.slice(end)
|
||||
}
|
||||
}
|
||||
if (!runtimeOptions && transformOptions) {
|
||||
runtimeOptions = `{ ${transformOptions} }`
|
||||
}
|
||||
} else {
|
||||
runtimeOptions = optionsString!
|
||||
}
|
||||
}
|
||||
|
||||
ctx.modelDecls[modelName] = {
|
||||
type,
|
||||
|
@ -56,31 +91,6 @@ export function processDefineModel(
|
|||
// register binding type
|
||||
ctx.bindingMetadata[modelName] = BindingTypes.PROPS
|
||||
|
||||
let runtimeOptions = ''
|
||||
if (options) {
|
||||
if (options.type === 'ObjectExpression') {
|
||||
const local = options.properties.find(
|
||||
p =>
|
||||
p.type === 'ObjectProperty' &&
|
||||
((p.key.type === 'Identifier' && p.key.name === 'local') ||
|
||||
(p.key.type === 'StringLiteral' && p.key.value === 'local')),
|
||||
) as ObjectProperty
|
||||
|
||||
if (local) {
|
||||
runtimeOptions = `{ ${ctx.getString(local)} }`
|
||||
} else {
|
||||
for (const p of options.properties) {
|
||||
if (p.type === 'SpreadElement' || p.computed) {
|
||||
runtimeOptions = optionsString!
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
runtimeOptions = optionsString!
|
||||
}
|
||||
}
|
||||
|
||||
ctx.s.overwrite(
|
||||
ctx.startOffset! + node.start!,
|
||||
ctx.startOffset! + node.end!,
|
||||
|
@ -133,6 +143,12 @@ export function genModelProps(ctx: ScriptCompileContext) {
|
|||
decl = options || (runtimeType ? `{ ${codegenOptions} }` : '{}')
|
||||
}
|
||||
modelPropsDecl += `\n ${JSON.stringify(name)}: ${decl},`
|
||||
|
||||
// also generate modifiers prop
|
||||
const modifierPropName = JSON.stringify(
|
||||
name === 'modelValue' ? `modelModifiers` : `${name}Modifiers`,
|
||||
)
|
||||
modelPropsDecl += `\n ${modifierPropName}: {},`
|
||||
}
|
||||
return `{${modelPropsDecl}\n }`
|
||||
}
|
||||
|
|
|
@ -314,6 +314,37 @@ describe('defineModel', () => {
|
|||
const inferredRequired = defineModel({ default: 123, required: true })
|
||||
expectType<Ref<number>>(inferredRequired)
|
||||
|
||||
// modifiers
|
||||
const [_, modifiers] = defineModel<string>()
|
||||
expectType<true | undefined>(modifiers.foo)
|
||||
|
||||
// limit supported modifiers
|
||||
const [__, typedModifiers] = defineModel<string, 'trim' | 'capitalize'>()
|
||||
expectType<true | undefined>(typedModifiers.trim)
|
||||
expectType<true | undefined>(typedModifiers.capitalize)
|
||||
// @ts-expect-error
|
||||
typedModifiers.foo
|
||||
|
||||
// transformers with type
|
||||
defineModel<string>({
|
||||
get(val) {
|
||||
return val.toLowerCase()
|
||||
},
|
||||
set(val) {
|
||||
return val.toUpperCase()
|
||||
},
|
||||
})
|
||||
// transformers with runtime type
|
||||
defineModel({
|
||||
type: String,
|
||||
get(val) {
|
||||
return val.toLowerCase()
|
||||
},
|
||||
set(val) {
|
||||
return val.toUpperCase()
|
||||
},
|
||||
})
|
||||
|
||||
// @ts-expect-error type / default mismatch
|
||||
defineModel<string>({ default: 123 })
|
||||
// @ts-expect-error unknown props option
|
||||
|
|
|
@ -513,6 +513,73 @@ describe('SFC <script setup> helpers', () => {
|
|||
expect(slotRender).toBeCalledTimes(2)
|
||||
expect(serializeInner(root)).toBe('<div>bar</div>')
|
||||
})
|
||||
|
||||
test('with modifiers & transformers', async () => {
|
||||
let childMsg: Ref<string>
|
||||
let childModifiers: Record<string, true | undefined>
|
||||
|
||||
const compRender = vi.fn()
|
||||
const Comp = defineComponent({
|
||||
props: ['msg', 'msgModifiers'],
|
||||
emits: ['update:msg'],
|
||||
setup(props) {
|
||||
;[childMsg, childModifiers] = useModel(props, 'msg', {
|
||||
get(val) {
|
||||
return val.toLowerCase()
|
||||
},
|
||||
set(val) {
|
||||
if (childModifiers.upper) {
|
||||
return val.toUpperCase()
|
||||
}
|
||||
},
|
||||
})
|
||||
return () => {
|
||||
compRender()
|
||||
return childMsg.value
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const msg = ref('HI')
|
||||
const Parent = defineComponent({
|
||||
setup() {
|
||||
return () =>
|
||||
h(Comp, {
|
||||
msg: msg.value,
|
||||
msgModifiers: { upper: true },
|
||||
'onUpdate:msg': val => {
|
||||
msg.value = val
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(Parent), root)
|
||||
|
||||
// should be lowered
|
||||
expect(serializeInner(root)).toBe('hi')
|
||||
|
||||
// child update
|
||||
childMsg!.value = 'Hmm'
|
||||
|
||||
await nextTick()
|
||||
expect(childMsg!.value).toBe('hmm')
|
||||
expect(serializeInner(root)).toBe('hmm')
|
||||
// parent should get uppercase value
|
||||
expect(msg.value).toBe('HMM')
|
||||
|
||||
// parent update
|
||||
msg.value = 'Ughh'
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe('ughh')
|
||||
expect(msg.value).toBe('Ughh')
|
||||
|
||||
// child update again
|
||||
childMsg!.value = 'ughh'
|
||||
await nextTick()
|
||||
expect(msg.value).toBe('UGHH')
|
||||
})
|
||||
})
|
||||
|
||||
test('createPropsRestProxy', () => {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import {
|
||||
EMPTY_OBJ,
|
||||
type LooseRequired,
|
||||
type Prettify,
|
||||
type UnionToIntersection,
|
||||
|
@ -218,6 +219,9 @@ export function defineSlots<
|
|||
return null as any
|
||||
}
|
||||
|
||||
export type ModelRef<T, M extends string | number | symbol = string> = Ref<T> &
|
||||
[ModelRef<T, M>, Record<M, true | undefined>]
|
||||
|
||||
/**
|
||||
* Vue `<script setup>` compiler macro for declaring a
|
||||
* two-way binding prop that can be consumed via `v-model` from the parent
|
||||
|
@ -251,25 +255,27 @@ export function defineSlots<
|
|||
* const count = defineModel<number>('count', { default: 0 })
|
||||
* ```
|
||||
*/
|
||||
export function defineModel<T>(
|
||||
options: { required: true } & PropOptions<T>,
|
||||
): Ref<T>
|
||||
export function defineModel<T>(
|
||||
options: { default: any } & PropOptions<T>,
|
||||
): Ref<T>
|
||||
export function defineModel<T>(options?: PropOptions<T>): Ref<T | undefined>
|
||||
export function defineModel<T>(
|
||||
export function defineModel<T, M extends string | number | symbol = string>(
|
||||
options: { required: true } & PropOptions<T> & UseModelOptions<T>,
|
||||
): ModelRef<T, M>
|
||||
export function defineModel<T, M extends string | number | symbol = string>(
|
||||
options: { default: any } & PropOptions<T> & UseModelOptions<T>,
|
||||
): ModelRef<T, M>
|
||||
export function defineModel<T, M extends string | number | symbol = string>(
|
||||
options?: PropOptions<T> & UseModelOptions<T>,
|
||||
): ModelRef<T | undefined, M>
|
||||
export function defineModel<T, M extends string | number | symbol = string>(
|
||||
name: string,
|
||||
options: { required: true } & PropOptions<T>,
|
||||
): Ref<T>
|
||||
export function defineModel<T>(
|
||||
options: { required: true } & PropOptions<T> & UseModelOptions<T>,
|
||||
): ModelRef<T, M>
|
||||
export function defineModel<T, M extends string | number | symbol = string>(
|
||||
name: string,
|
||||
options: { default: any } & PropOptions<T>,
|
||||
): Ref<T>
|
||||
export function defineModel<T>(
|
||||
options: { default: any } & PropOptions<T> & UseModelOptions<T>,
|
||||
): ModelRef<T, M>
|
||||
export function defineModel<T, M extends string | number | symbol = string>(
|
||||
name: string,
|
||||
options?: PropOptions<T>,
|
||||
): Ref<T | undefined>
|
||||
options?: PropOptions<T> & UseModelOptions<T>,
|
||||
): ModelRef<T | undefined, M>
|
||||
export function defineModel(): any {
|
||||
if (__DEV__) {
|
||||
warnRuntimeUsage('defineModel')
|
||||
|
@ -348,11 +354,21 @@ export function useAttrs(): SetupContext['attrs'] {
|
|||
return getContext().attrs
|
||||
}
|
||||
|
||||
export function useModel<T extends Record<string, any>, K extends keyof T>(
|
||||
props: T,
|
||||
name: K,
|
||||
): Ref<T[K]>
|
||||
export function useModel(props: Record<string, any>, name: string): Ref {
|
||||
type UseModelOptions<T = any> = {
|
||||
get?: (v: T) => any
|
||||
set?: (v: T) => any
|
||||
}
|
||||
|
||||
export function useModel<
|
||||
M extends string | number | symbol,
|
||||
T extends Record<string, any>,
|
||||
K extends keyof T,
|
||||
>(props: T, name: K, options?: UseModelOptions<T[K]>): ModelRef<T[K], M>
|
||||
export function useModel(
|
||||
props: Record<string, any>,
|
||||
name: string,
|
||||
options: UseModelOptions = EMPTY_OBJ,
|
||||
): Ref {
|
||||
const i = getCurrentInstance()!
|
||||
if (__DEV__ && !i) {
|
||||
warn(`useModel() called without active instance.`)
|
||||
|
@ -364,7 +380,7 @@ export function useModel(props: Record<string, any>, name: string): Ref {
|
|||
return ref() as any
|
||||
}
|
||||
|
||||
return customRef((track, trigger) => {
|
||||
const res = customRef((track, trigger) => {
|
||||
let localValue: any
|
||||
watchSyncEffect(() => {
|
||||
const propValue = props[name]
|
||||
|
@ -376,7 +392,7 @@ export function useModel(props: Record<string, any>, name: string): Ref {
|
|||
return {
|
||||
get() {
|
||||
track()
|
||||
return localValue
|
||||
return options.get ? options.get(localValue) : localValue
|
||||
},
|
||||
set(value) {
|
||||
const rawProps = i.vnode!.props
|
||||
|
@ -384,10 +400,29 @@ export function useModel(props: Record<string, any>, name: string): Ref {
|
|||
localValue = value
|
||||
trigger()
|
||||
}
|
||||
i.emit(`update:${name}`, value)
|
||||
i.emit(`update:${name}`, options.set ? options.set(value) : value)
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const modifierKey =
|
||||
name === 'modelValue' ? 'modelModifiers' : `${name}Modifiers`
|
||||
|
||||
// @ts-expect-error
|
||||
res[Symbol.iterator] = () => {
|
||||
let i = 0
|
||||
return {
|
||||
next() {
|
||||
if (i < 2) {
|
||||
return { value: i++ ? props[modifierKey] : res, done: false }
|
||||
} else {
|
||||
return { done: true }
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
function getContext(): SetupContext {
|
||||
|
|
|
@ -60,7 +60,7 @@ export { provide, inject, hasInjectionContext } from './apiInject'
|
|||
export { nextTick } from './scheduler'
|
||||
export { defineComponent } from './apiDefineComponent'
|
||||
export { defineAsyncComponent } from './apiAsyncComponent'
|
||||
export { useAttrs, useSlots, type DefineProps } from './apiSetupHelpers'
|
||||
export { useAttrs, useSlots } from './apiSetupHelpers'
|
||||
|
||||
// <script setup> API ----------------------------------------------------------
|
||||
|
||||
|
@ -74,6 +74,8 @@ export {
|
|||
defineModel,
|
||||
withDefaults,
|
||||
useModel,
|
||||
type DefineProps,
|
||||
type ModelRef,
|
||||
} from './apiSetupHelpers'
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue