diff --git a/packages/@core/ui-kit/popup-ui/src/drawer/__tests__/drawer-api.test.ts b/packages/@core/ui-kit/popup-ui/src/drawer/__tests__/drawer-api.test.ts index 8420bb9c..b1b9636a 100644 --- a/packages/@core/ui-kit/popup-ui/src/drawer/__tests__/drawer-api.test.ts +++ b/packages/@core/ui-kit/popup-ui/src/drawer/__tests__/drawer-api.test.ts @@ -81,6 +81,16 @@ describe('drawerApi', () => { expect(drawerApi.getData()).toEqual(testData); }); + it('should return undefined before shared data is set', () => { + expect(drawerApi.getData()).toBeUndefined(); + }); + + it('should preserve null shared data', () => { + const nullableDrawerApi = new DrawerApi>(); + nullableDrawerApi.setData(null); + expect(nullableDrawerApi.getData()).toBeNull(); + }); + it('should set state correctly using an object', () => { drawerApi.setState({ title: 'New Title' }); expect(drawerApi.store.state.title).toBe('New Title'); diff --git a/packages/@core/ui-kit/popup-ui/src/drawer/__tests__/drawer-types.test.ts b/packages/@core/ui-kit/popup-ui/src/drawer/__tests__/drawer-types.test.ts new file mode 100644 index 00000000..c0606776 --- /dev/null +++ b/packages/@core/ui-kit/popup-ui/src/drawer/__tests__/drawer-types.test.ts @@ -0,0 +1,59 @@ +import type { ExtendedDrawerApi, InferDrawerData } from '../drawer'; +import type { createVbenDrawer, useVbenDrawer } from '../use-drawer'; +import type TypedDrawer from './fixtures/typed-drawer.vue'; + +import { describe, expectTypeOf, it } from 'vitest'; + +interface TypedDrawerData { + id: number; + mode: 'edit' | 'view'; +} + +type DrawerData = null | TypedDrawerData; + +declare const createDrawer: typeof createVbenDrawer; +declare const useDrawer: typeof useVbenDrawer; + +describe('drawer public data types', () => { + it('infers data from the connected component exposed api', () => { + function assertInferredData(connectedComponent: typeof TypedDrawer) { + const [, drawerApi] = useDrawer({ connectedComponent }); + + expectTypeOf(drawerApi).toEqualTypeOf>(); + expectTypeOf(drawerApi.getData()).toEqualTypeOf(); + expectTypeOf(drawerApi.setData).parameter(0).toEqualTypeOf(); + expectTypeOf(drawerApi.setData(null).open()).toBeVoid(); + // @ts-expect-error invalid payload type + drawerApi.setData({ id: '1', mode: 'edit' }); + } + + expectTypeOf< + InferDrawerData + >().toEqualTypeOf(); + expectTypeOf(assertInferredData).toBeFunction(); + }); + + it('supports explicit and pre-bound data contracts', () => { + function assertExplicitData() { + const [, explicitApi] = useDrawer(); + const useTypedDrawer = createDrawer(); + const [, preBoundApi] = useTypedDrawer(); + + expectTypeOf(explicitApi).toEqualTypeOf>(); + expectTypeOf(preBoundApi).toEqualTypeOf>(); + } + + expectTypeOf(assertExplicitData).toBeFunction(); + }); + + it('falls back to unknown without a data contract', () => { + function assertUnknownData() { + const [, drawerApi] = useDrawer(); + + expectTypeOf(drawerApi).toEqualTypeOf>(); + expectTypeOf(drawerApi.getData()).toBeUnknown(); + } + + expectTypeOf(assertUnknownData).toBeFunction(); + }); +}); diff --git a/packages/@core/ui-kit/popup-ui/src/drawer/__tests__/fixtures/typed-drawer.vue b/packages/@core/ui-kit/popup-ui/src/drawer/__tests__/fixtures/typed-drawer.vue new file mode 100644 index 00000000..6bc2ba62 --- /dev/null +++ b/packages/@core/ui-kit/popup-ui/src/drawer/__tests__/fixtures/typed-drawer.vue @@ -0,0 +1,16 @@ + + + diff --git a/packages/@core/ui-kit/popup-ui/src/drawer/__tests__/use-drawer.test.ts b/packages/@core/ui-kit/popup-ui/src/drawer/__tests__/use-drawer.test.ts index 9e40fa28..64d4f99b 100644 --- a/packages/@core/ui-kit/popup-ui/src/drawer/__tests__/use-drawer.test.ts +++ b/packages/@core/ui-kit/popup-ui/src/drawer/__tests__/use-drawer.test.ts @@ -79,6 +79,9 @@ describe('useVbenDrawer', () => { expect(initialApi).toBeDefined(); if (!initialApi) return; expect(parentApi.store).toBe(initialApi.store); + const initialData = { id: 1 }; + parentApi.setData(initialData); + expect(initialApi.getData()).toBe(initialData); await remountConsumer(consumerKey); const recreatedApi = getCurrentApi(); @@ -88,6 +91,9 @@ describe('useVbenDrawer', () => { expect(recreatedApi).not.toBe(initialApi); expect(recreatedApi.store.state.title).toBe('Parent drawer title'); expect(parentApi.store).toBe(recreatedApi.store); + const recreatedData = { id: 2 }; + parentApi.setData(recreatedData); + expect(recreatedApi.getData()).toBe(recreatedData); parentApi.open(); expect(onOpenChange).toHaveBeenCalledWith(true); diff --git a/packages/@core/ui-kit/popup-ui/src/drawer/drawer-api.ts b/packages/@core/ui-kit/popup-ui/src/drawer/drawer-api.ts index f5d9f336..31c4aaf3 100644 --- a/packages/@core/ui-kit/popup-ui/src/drawer/drawer-api.ts +++ b/packages/@core/ui-kit/popup-ui/src/drawer/drawer-api.ts @@ -3,10 +3,10 @@ import type { DrawerApiOptions, DrawerState } from './drawer'; import { Store } from '@vben-core/shared/store'; import { bindMethods, isFunction } from '@vben-core/shared/utils'; -export class DrawerApi { +export class DrawerApi { // 共享数据 - public sharedData: Record<'payload', any> = { - payload: {}, + public sharedData: Record<'payload', TData | undefined> = { + payload: undefined, }; public store: Store; @@ -97,8 +97,8 @@ export class DrawerApi { } } - getData>() { - return (this.sharedData?.payload ?? {}) as T; + getData(): TData | undefined { + return this.sharedData.payload; } /** @@ -150,7 +150,7 @@ export class DrawerApi { this.store.setState((prev) => ({ ...prev, isOpen: true })); } - setData(payload: T) { + setData(payload: TData) { this.sharedData.payload = payload; return this; } diff --git a/packages/@core/ui-kit/popup-ui/src/drawer/drawer.ts b/packages/@core/ui-kit/popup-ui/src/drawer/drawer.ts index 1b2ba4e3..4124ee31 100644 --- a/packages/@core/ui-kit/popup-ui/src/drawer/drawer.ts +++ b/packages/@core/ui-kit/popup-ui/src/drawer/drawer.ts @@ -130,23 +130,36 @@ export interface DrawerProps { export interface DrawerState extends DrawerProps { /** 弹窗打开状态 */ isOpen?: boolean; - /** - * 共享数据 - */ - sharedData?: Record; } -export type ExtendedDrawerApi = DrawerApi & { +export type ExtendedDrawerApi = DrawerApi & { useStore: >( selector?: (state: NoInfer) => T, ) => Readonly>; }; -export interface DrawerApiOptions extends DrawerState { +type DrawerComponentInstance = + TComponent extends abstract new (...args: any[]) => infer TInstance + ? TInstance + : never; + +export type InferDrawerData = [ + DrawerComponentInstance, +] extends [never] + ? unknown + : DrawerComponentInstance extends { + drawerApi: ExtendedDrawerApi; + } + ? TData + : unknown; + +export interface DrawerApiOptions< + TConnectedComponent extends Component = Component, +> extends DrawerState { /** * 独立的抽屉组件 */ - connectedComponent?: Component; + connectedComponent?: TConnectedComponent; /** * 关闭前的回调,返回 false 可以阻止关闭 * @returns diff --git a/packages/@core/ui-kit/popup-ui/src/drawer/index.ts b/packages/@core/ui-kit/popup-ui/src/drawer/index.ts index 00f87b2c..bf099a08 100644 --- a/packages/@core/ui-kit/popup-ui/src/drawer/index.ts +++ b/packages/@core/ui-kit/popup-ui/src/drawer/index.ts @@ -1,3 +1,7 @@ export type * from './drawer'; export { default as VbenDrawer } from './drawer.vue'; -export { setDefaultDrawerProps, useVbenDrawer } from './use-drawer'; +export { + createVbenDrawer, + setDefaultDrawerProps, + useVbenDrawer, +} from './use-drawer'; diff --git a/packages/@core/ui-kit/popup-ui/src/drawer/use-drawer.ts b/packages/@core/ui-kit/popup-ui/src/drawer/use-drawer.ts index 6e391954..53e80372 100644 --- a/packages/@core/ui-kit/popup-ui/src/drawer/use-drawer.ts +++ b/packages/@core/ui-kit/popup-ui/src/drawer/use-drawer.ts @@ -1,7 +1,10 @@ +import type { Component } from 'vue'; + import type { DrawerApiOptions, DrawerProps, ExtendedDrawerApi, + InferDrawerData, } from './drawer'; import { @@ -24,6 +27,26 @@ import VbenDrawer from './drawer.vue'; const USER_DRAWER_INJECT_KEY = Symbol('VBEN_DRAWER_INJECT'); +declare const DRAWER_DATA_NOT_PROVIDED: unique symbol; + +type DrawerDataNotProvided = { + readonly [DRAWER_DATA_NOT_PROVIDED]: true; +}; + +type ResolvedDrawerData< + TData, + TConnectedComponent extends Component, +> = TData extends DrawerDataNotProvided + ? InferDrawerData + : TData; + +interface DrawerInjectData { + consumed?: boolean; + extendApi?: (api: ExtendedDrawerApi) => void; + options?: DrawerApiOptions; + reCreateDrawer?: () => Promise; +} + const { globalEscapeShortcutKey } = usePreferences(); /** @@ -36,8 +59,11 @@ export function setDefaultDrawerProps(props: Partial) { } export function useVbenDrawer< - TParentDrawerProps extends DrawerProps = DrawerProps, ->(options: DrawerApiOptions = {}) { + TData = DrawerDataNotProvided, + TConnectedComponent extends Component = Component, +>(options: DrawerApiOptions = {}) { + type TResolvedData = ResolvedDrawerData; + // Drawer一般会抽离出来,所以如果有传入 connectedComponent,则表示为外部调用,与内部组件进行连接 // 外部的Drawer通过provide/inject传递api @@ -47,11 +73,11 @@ export function useVbenDrawer< }; const { connectedComponent } = options; if (connectedComponent) { - const extendedApi = shallowReactive({}); + const extendedApi = shallowReactive({}) as ExtendedDrawerApi; const isDrawerReady = ref(true); const Drawer = defineComponent( - (props: TParentDrawerProps, { attrs, slots }) => { - function rebindApi(api: ExtendedDrawerApi) { + (props: DrawerProps, { attrs, slots }) => { + function rebindApi(api: ExtendedDrawerApi) { Object.setPrototypeOf(extendedApi, markRaw(api)); } @@ -65,7 +91,7 @@ export function useVbenDrawer< isDrawerReady.value = true; }, }); - checkProps(extendedApi as ExtendedDrawerApi, { + checkProps(extendedApi, { ...props, ...attrs, ...slots, @@ -84,10 +110,13 @@ export function useVbenDrawer< }, ); - return [Drawer, extendedApi as ExtendedDrawerApi] as const; + return [Drawer, extendedApi] as const; } - const injectData = inject(USER_DRAWER_INJECT_KEY, {}); + const injectData = inject>( + USER_DRAWER_INJECT_KEY, + {}, + ); const isConsumed = injectData.consumed; const effectiveOptions = isConsumed ? {} : injectData.options; if (!isConsumed && injectData.consumed !== undefined) { @@ -122,9 +151,9 @@ export function useVbenDrawer< injectData.reCreateDrawer?.(); } }; - const api = new DrawerApi(mergedOptions); + const api = new DrawerApi(mergedOptions); - const extendedApi: ExtendedDrawerApi = api as never; + const extendedApi = api as ExtendedDrawerApi; extendedApi.useStore = (selector) => { return useSelector(api.store, selector); @@ -145,7 +174,18 @@ export function useVbenDrawer< return [Drawer, extendedApi] as const; } -async function checkProps(api: ExtendedDrawerApi, attrs: Record) { +export function createVbenDrawer() { + return function useTypedVbenDrawer< + TConnectedComponent extends Component = Component, + >(options: DrawerApiOptions = {}) { + return useVbenDrawer(options); + }; +} + +async function checkProps( + api: ExtendedDrawerApi, + attrs: Record, +) { if (!attrs || Object.keys(attrs).length === 0) { return; } diff --git a/packages/@core/ui-kit/popup-ui/src/modal/__tests__/fixtures/typed-modal.vue b/packages/@core/ui-kit/popup-ui/src/modal/__tests__/fixtures/typed-modal.vue new file mode 100644 index 00000000..5adb21fa --- /dev/null +++ b/packages/@core/ui-kit/popup-ui/src/modal/__tests__/fixtures/typed-modal.vue @@ -0,0 +1,16 @@ + + + diff --git a/packages/@core/ui-kit/popup-ui/src/modal/__tests__/modal-api.test.ts b/packages/@core/ui-kit/popup-ui/src/modal/__tests__/modal-api.test.ts index 5067f5fa..dae99a64 100644 --- a/packages/@core/ui-kit/popup-ui/src/modal/__tests__/modal-api.test.ts +++ b/packages/@core/ui-kit/popup-ui/src/modal/__tests__/modal-api.test.ts @@ -82,6 +82,16 @@ describe('modalApi', () => { expect(modalApi.getData()).toEqual(testData); }); + it('should return undefined before shared data is set', () => { + expect(modalApi.getData()).toBeUndefined(); + }); + + it('should preserve null shared data', () => { + const nullableModalApi = new ModalApi>(); + nullableModalApi.setData(null); + expect(nullableModalApi.getData()).toBeNull(); + }); + it('should set state correctly using an object', () => { modalApi.setState({ title: 'New Title' }); expect(modalApi.store.state.title).toBe('New Title'); diff --git a/packages/@core/ui-kit/popup-ui/src/modal/__tests__/modal-types.test.ts b/packages/@core/ui-kit/popup-ui/src/modal/__tests__/modal-types.test.ts new file mode 100644 index 00000000..d91d5393 --- /dev/null +++ b/packages/@core/ui-kit/popup-ui/src/modal/__tests__/modal-types.test.ts @@ -0,0 +1,59 @@ +import type { ExtendedModalApi, InferModalData } from '../modal'; +import type { createVbenModal, useVbenModal } from '../use-modal'; +import type TypedModal from './fixtures/typed-modal.vue'; + +import { describe, expectTypeOf, it } from 'vitest'; + +interface TypedModalData { + id: number; + mode: 'edit' | 'view'; +} + +type ModalData = null | TypedModalData; + +declare const createModal: typeof createVbenModal; +declare const useModal: typeof useVbenModal; + +describe('modal public data types', () => { + it('infers data from the connected component exposed api', () => { + function assertInferredData(connectedComponent: typeof TypedModal) { + const [, modalApi] = useModal({ connectedComponent }); + + expectTypeOf(modalApi).toEqualTypeOf>(); + expectTypeOf(modalApi.getData()).toEqualTypeOf(); + expectTypeOf(modalApi.setData).parameter(0).toEqualTypeOf(); + expectTypeOf(modalApi.setData(null).open()).toBeVoid(); + // @ts-expect-error invalid payload type + modalApi.setData({ id: '1', mode: 'edit' }); + } + + expectTypeOf< + InferModalData + >().toEqualTypeOf(); + expectTypeOf(assertInferredData).toBeFunction(); + }); + + it('supports explicit and pre-bound data contracts', () => { + function assertExplicitData() { + const [, explicitApi] = useModal(); + const useTypedModal = createModal(); + const [, preBoundApi] = useTypedModal(); + + expectTypeOf(explicitApi).toEqualTypeOf>(); + expectTypeOf(preBoundApi).toEqualTypeOf>(); + } + + expectTypeOf(assertExplicitData).toBeFunction(); + }); + + it('falls back to unknown without a data contract', () => { + function assertUnknownData() { + const [, modalApi] = useModal(); + + expectTypeOf(modalApi).toEqualTypeOf>(); + expectTypeOf(modalApi.getData()).toBeUnknown(); + } + + expectTypeOf(assertUnknownData).toBeFunction(); + }); +}); diff --git a/packages/@core/ui-kit/popup-ui/src/modal/__tests__/use-modal.test.ts b/packages/@core/ui-kit/popup-ui/src/modal/__tests__/use-modal.test.ts index 4a60cbe6..3fb726a3 100644 --- a/packages/@core/ui-kit/popup-ui/src/modal/__tests__/use-modal.test.ts +++ b/packages/@core/ui-kit/popup-ui/src/modal/__tests__/use-modal.test.ts @@ -79,6 +79,9 @@ describe('useVbenModal', () => { expect(initialApi).toBeDefined(); if (!initialApi) return; expect(toRaw(parentApi.store)).toBe(initialApi.store); + const initialData = { id: 1 }; + parentApi.setData(initialData); + expect(initialApi.getData()).toBe(initialData); await remountConsumer(consumerKey); const recreatedApi = getCurrentApi(); @@ -88,6 +91,9 @@ describe('useVbenModal', () => { expect(recreatedApi).not.toBe(initialApi); expect(recreatedApi.store.state.title).toBe('Parent modal title'); expect(toRaw(parentApi.store)).toBe(recreatedApi.store); + const recreatedData = { id: 2 }; + parentApi.setData(recreatedData); + expect(recreatedApi.getData()).toBe(recreatedData); parentApi.open(); expect(onOpenChange).toHaveBeenCalledWith(true); diff --git a/packages/@core/ui-kit/popup-ui/src/modal/index.ts b/packages/@core/ui-kit/popup-ui/src/modal/index.ts index 0cdedf2f..cbf3c52f 100644 --- a/packages/@core/ui-kit/popup-ui/src/modal/index.ts +++ b/packages/@core/ui-kit/popup-ui/src/modal/index.ts @@ -1,3 +1,7 @@ export type * from './modal'; export { default as VbenModal } from './modal.vue'; -export { setDefaultModalProps, useVbenModal } from './use-modal'; +export { + createVbenModal, + setDefaultModalProps, + useVbenModal, +} from './use-modal'; diff --git a/packages/@core/ui-kit/popup-ui/src/modal/modal-api.ts b/packages/@core/ui-kit/popup-ui/src/modal/modal-api.ts index 9bfda4e9..560b9ade 100644 --- a/packages/@core/ui-kit/popup-ui/src/modal/modal-api.ts +++ b/packages/@core/ui-kit/popup-ui/src/modal/modal-api.ts @@ -3,10 +3,10 @@ import type { ModalApiOptions, ModalState } from './modal'; import { Store } from '@vben-core/shared/store'; import { bindMethods, isFunction } from '@vben-core/shared/utils'; -export class ModalApi { +export class ModalApi { // 共享数据 - public sharedData: Record<'payload', any> = { - payload: {}, + public sharedData: Record<'payload', TData | undefined> = { + payload: undefined, }; public store: Store; @@ -106,8 +106,8 @@ export class ModalApi { } } - getData>() { - return (this.sharedData?.payload ?? {}) as T; + getData(): TData | undefined { + return this.sharedData.payload; } /** @@ -163,7 +163,7 @@ export class ModalApi { })); } - setData(payload: T) { + setData(payload: TData) { this.sharedData.payload = payload; return this; } diff --git a/packages/@core/ui-kit/popup-ui/src/modal/modal.ts b/packages/@core/ui-kit/popup-ui/src/modal/modal.ts index 5a7a5028..d84dcadd 100644 --- a/packages/@core/ui-kit/popup-ui/src/modal/modal.ts +++ b/packages/@core/ui-kit/popup-ui/src/modal/modal.ts @@ -150,23 +150,36 @@ export interface ModalProps { export interface ModalState extends ModalProps { /** 弹窗打开状态 */ isOpen?: boolean; - /** - * 共享数据 - */ - sharedData?: Record; } -export type ExtendedModalApi = ModalApi & { +export type ExtendedModalApi = ModalApi & { useStore: >( selector?: (state: NoInfer) => T, ) => Readonly>; }; -export interface ModalApiOptions extends ModalState { +type ModalComponentInstance = + TComponent extends abstract new (...args: any[]) => infer TInstance + ? TInstance + : never; + +export type InferModalData = [ + ModalComponentInstance, +] extends [never] + ? unknown + : ModalComponentInstance extends { + modalApi: ExtendedModalApi; + } + ? TData + : unknown; + +export interface ModalApiOptions< + TConnectedComponent extends Component = Component, +> extends ModalState { /** * 独立的弹窗组件 */ - connectedComponent?: Component; + connectedComponent?: TConnectedComponent; /** * 关闭前的回调,返回 false 可以阻止关闭 * @returns diff --git a/packages/@core/ui-kit/popup-ui/src/modal/use-modal.ts b/packages/@core/ui-kit/popup-ui/src/modal/use-modal.ts index 2b818465..45fd5517 100644 --- a/packages/@core/ui-kit/popup-ui/src/modal/use-modal.ts +++ b/packages/@core/ui-kit/popup-ui/src/modal/use-modal.ts @@ -1,4 +1,11 @@ -import type { ExtendedModalApi, ModalApiOptions, ModalProps } from './modal'; +import type { Component } from 'vue'; + +import type { + ExtendedModalApi, + InferModalData, + ModalApiOptions, + ModalProps, +} from './modal'; import { defineComponent, @@ -20,6 +27,26 @@ import VbenModal from './modal.vue'; const USER_MODAL_INJECT_KEY = Symbol('VBEN_MODAL_INJECT'); +declare const MODAL_DATA_NOT_PROVIDED: unique symbol; + +type ModalDataNotProvided = { + readonly [MODAL_DATA_NOT_PROVIDED]: true; +}; + +type ResolvedModalData< + TData, + TConnectedComponent extends Component, +> = TData extends ModalDataNotProvided + ? InferModalData + : TData; + +interface ModalInjectData { + consumed?: boolean; + extendApi?: (api: ExtendedModalApi) => void; + options?: ModalApiOptions; + reCreateModal?: () => Promise; +} + const { globalEscapeShortcutKey } = usePreferences(); /** * 默认配置 @@ -30,9 +57,12 @@ export function setDefaultModalProps(props: Partial) { Object.assign(DEFAULT_MODAL_PROPS, props); } -export function useVbenModal( - options: ModalApiOptions = {}, -) { +export function useVbenModal< + TData = ModalDataNotProvided, + TConnectedComponent extends Component = Component, +>(options: ModalApiOptions = {}) { + type TResolvedData = ResolvedModalData; + // Modal一般会抽离出来,所以如果有传入 connectedComponent,则表示为外部调用,与内部组件进行连接 // 外部的Modal通过provide/inject传递api @@ -42,11 +72,11 @@ export function useVbenModal( }; const { connectedComponent } = options; if (connectedComponent) { - const extendedApi = shallowReactive({}); + const extendedApi = shallowReactive({}) as ExtendedModalApi; const isModalReady = ref(true); const Modal = defineComponent( - (props: TParentModalProps, { attrs, slots }) => { - function rebindApi(api: ExtendedModalApi) { + (props: ModalProps, { attrs, slots }) => { + function rebindApi(api: ExtendedModalApi) { Object.setPrototypeOf(extendedApi, markRaw(api)); } @@ -60,7 +90,7 @@ export function useVbenModal( isModalReady.value = true; }, }); - checkProps(extendedApi as ExtendedModalApi, { + checkProps(extendedApi, { ...props, ...attrs, ...slots, @@ -82,10 +112,13 @@ export function useVbenModal( }, ); - return [Modal, extendedApi as ExtendedModalApi] as const; + return [Modal, extendedApi] as const; } - const injectData = inject(USER_MODAL_INJECT_KEY, {}); + const injectData = inject>( + USER_MODAL_INJECT_KEY, + {}, + ); const isConsumed = injectData.consumed; const effectiveOptions = isConsumed ? {} : injectData.options; if (!isConsumed && injectData.consumed !== undefined) { @@ -121,9 +154,9 @@ export function useVbenModal( } }; - const api = new ModalApi(mergedOptions); + const api = new ModalApi(mergedOptions); - const extendedApi: ExtendedModalApi = api as never; + const extendedApi = api as ExtendedModalApi; extendedApi.useStore = (selector) => { return useSelector(api.store, selector); @@ -153,7 +186,18 @@ export function useVbenModal( return [Modal, extendedApi] as const; } -async function checkProps(api: ExtendedModalApi, attrs: Record) { +export function createVbenModal() { + return function useTypedVbenModal< + TConnectedComponent extends Component = Component, + >(options: ModalApiOptions = {}) { + return useVbenModal(options); + }; +} + +async function checkProps( + api: ExtendedModalApi, + attrs: Record, +) { if (!attrs || Object.keys(attrs).length === 0) { return; }