// Composables import { useToggleScope } from '@/composables/toggleScope' // Utilities import { computed, ref, toRaw, watch } from 'vue' import { getCurrentInstance, toKebabCase } from '@/utils' // Types import type { Ref } from 'vue' type InnerVal = T extends any[] ? Readonly : T // Composables export function useProxiedModel< Props extends object & { [key in Prop as `onUpdate:${Prop}`]: ((val: any) => void) | undefined }, Prop extends Extract, Inner = Props[Prop] >( props: Props, prop: Prop, defaultValue?: Props[Prop], transformIn: (value?: Props[Prop]) => Inner = (v: any) => v, transformOut: (value: Inner) => Props[Prop] = (v: any) => v ) { const vm = getCurrentInstance('useProxiedModel') const internal = ref( props[prop] !== undefined ? props[prop] : defaultValue ) as Ref const kebabProp = toKebabCase(prop) const checkKebab = kebabProp !== prop const isControlled = checkKebab ? computed(() => { void props[prop] return !!( (vm.vnode.props?.hasOwnProperty(prop) || vm.vnode.props?.hasOwnProperty(kebabProp)) && (vm.vnode.props?.hasOwnProperty(`onUpdate:${prop}`) || vm.vnode.props?.hasOwnProperty(`onUpdate:${kebabProp}`)) ) }) : computed(() => { void props[prop] return !!( vm.vnode.props?.hasOwnProperty(prop) && vm.vnode.props?.hasOwnProperty(`onUpdate:${prop}`) ) }) useToggleScope( () => !isControlled.value, () => { watch( () => props[prop], (val) => { internal.value = val } ) } ) const model = computed({ get(): any { const externalValue = props[prop] return transformIn(isControlled.value ? externalValue : internal.value) }, set(internalValue) { const newValue = transformOut(internalValue) const value = toRaw(isControlled.value ? props[prop] : internal.value) if (value === newValue || transformIn(value) === internalValue) { return } internal.value = newValue vm?.emit(`update:${prop}`, newValue) }, }) as any as Ref> & { readonly externalValue: Props[Prop] } Object.defineProperty(model, 'externalValue', { get: () => (isControlled.value ? props[prop] : internal.value), }) return model }