import type { Component } from 'vue' import { reactive, markRaw, shallowReactive } from 'vue' import { createSharedComposable } from '@vueuse/core' import type { ComponentProps, ComponentEmit } from 'vue-component-type-helpers' /** * This is a workaround for a design limitation in TypeScript. * * Conditional types only match the last function overload, not a union of all possible * parameter types. This workaround forces TypeScript to properly extract the 'close' * event argument type from component emits with multiple event signatures. * * @see https://github.com/microsoft/TypeScript/issues/32164 */ type CloseEventArgType = T extends { (event: 'close', arg_0: infer Arg, ...args: any[]): void (...args: any[]): void (...args: any[]): void (...args: any[]): void (...args: any[]): void (...args: any[]): void (...args: any[]): void (...args: any[]): void (...args: any[]): void (...args: any[]): void (...args: any[]): void (...args: any[]): void (...args: any[]): void (...args: any[]): void (...args: any[]): void (...args: any[]): void (...args: any[]): void } ? Arg : never export type OverlayOptions> = { defaultOpen?: boolean props?: OverlayAttrs destroyOnClose?: boolean } interface ManagedOverlayOptionsPrivate { component?: T id: symbol isMounted: boolean isOpen: boolean originalProps?: ComponentProps resolvePromise?: (value: any) => void } export type Overlay = OverlayOptions & ManagedOverlayOptionsPrivate type OverlayInstance = Omit, 'component'> & { id: symbol open: (props?: ComponentProps) => OpenedOverlay close: (value?: any) => void patch: (props: Partial>) => void } type OpenedOverlay = Omit, 'open' | 'close' | 'patch' | 'modelValue' | 'resolvePromise'> & { result: Promise>> } function _useOverlay() { const overlays = shallowReactive([]) const create = (component: T, _options?: OverlayOptions>): OverlayInstance => { const { props, defaultOpen, destroyOnClose } = _options || {} const options = reactive({ id: Symbol(import.meta.dev ? 'useOverlay' : ''), isOpen: !!defaultOpen, component: markRaw(component!), isMounted: !!defaultOpen, destroyOnClose: !!destroyOnClose, originalProps: props || {}, props: { ...(props || {}) } }) overlays.push(options) return { ...options, open: (props?: ComponentProps) => open(options.id, props), close: value => close(options.id, value), patch: (props: Partial>) => patch(options.id, props) } } const open = (id: symbol, props?: ComponentProps): OpenedOverlay => { const overlay = getOverlay(id) // If props are provided, update the overlay's props if (props) { patch(overlay.id, props) } else { patch(overlay.id, overlay.originalProps) } overlay.isOpen = true overlay.isMounted = true return { id, isMounted: overlay.isMounted, isOpen: overlay.isOpen, result: new Promise(resolve => overlay.resolvePromise = resolve) } } const close = (id: symbol, value?: any): void => { const overlay = getOverlay(id) overlay.isOpen = false // Resolve the promise if it exists if (overlay.resolvePromise) { overlay.resolvePromise(value) overlay.resolvePromise = undefined } } const closeAll = (): void => { overlays.forEach(overlay => close(overlay.id)) } const unmount = (id: symbol): void => { const overlay = getOverlay(id) overlay.isMounted = false if (overlay.destroyOnClose) { const index = overlays.findIndex(overlay => overlay.id === id) overlays.splice(index, 1) } } const patch = (id: symbol, props: Partial>): void => { const overlay = getOverlay(id) overlay.props = { ...props } } const getOverlay = (id: symbol): Overlay => { const overlay = overlays.find(overlay => overlay.id === id) if (!overlay) { throw new Error('Overlay not found') } return overlay } const isOpen = (id: symbol): boolean => { const overlay = getOverlay(id) return overlay.isOpen } return { overlays, open, close, closeAll, create, patch, unmount, isOpen } } export const useOverlay = /* @__PURE__ */ createSharedComposable(_useOverlay)