import type { App, InjectionKey, Plugin } from 'vue' import type { DialogApi, DialogFormOptions, DialogFormResult, DialogConfirmOptions, DialogOpenOptions, DialogRef, DialogAction } from './dialogTypes' import { createApp, defineComponent, h, inject, reactive } from 'vue' import Dialog from './Dialog.vue' import DialogConfirm from './DialogConfirm.vue' import DialogForm from './DialogForm.vue' export const DialogSymbol: InjectionKey = Symbol('dialog') // Global singleton instance as fallback let globalDialogApi: DialogApi | null = null export function useDialog(): DialogApi { const dialogApi = inject(DialogSymbol, null) if (!dialogApi) { if (globalDialogApi) { return globalDialogApi } throw new Error('Dialog API not provided. Make sure DialogPlugin is installed via app.use(DialogPlugin)') } return dialogApi } interface DialogStackItem { id: number type: 'form' | 'confirm' | 'open' options: any resolve: (value: any) => void open: boolean } export const DialogPlugin: Plugin = { install: (app: App) => { let dialogId = 0 const dialogStack = reactive([]) const api: DialogApi = { form: >(options: DialogFormOptions): Promise> => { return new Promise((resolve) => { const id = ++dialogId dialogStack.push({ id, type: 'form', options, resolve, open: true }) }) }, confirm: (options: string | DialogConfirmOptions): Promise => { return new Promise((resolve) => { const id = ++dialogId const normalizedOptions: DialogConfirmOptions = typeof options === 'string' ? { message: options } : options dialogStack.push({ id, type: 'confirm', options: normalizedOptions, resolve, open: true }) }) }, open: (options: DialogOpenOptions): DialogRef => { const id = ++dialogId const item: DialogStackItem = { id, type: 'open', options, resolve: () => {}, open: true } dialogStack.push(item) return { close: () => { const idx = dialogStack.findIndex(d => d.id === id) if (idx !== -1) { dialogStack[idx].open = false // Remove after animation setTimeout(() => { const removeIdx = dialogStack.findIndex(d => d.id === id) if (removeIdx !== -1) { dialogStack.splice(removeIdx, 1) } }, 200) } } } } } // Set global singleton if (!globalDialogApi) { globalDialogApi = api } app.provide(DialogSymbol, api) // Dialog container component const DialogContainerComponent = defineComponent({ name: 'DialogContainer', setup() { function handleFormResolve(item: DialogStackItem, result: { action: DialogAction, data: any }) { item.resolve(result) item.open = false // Remove after animation setTimeout(() => { const idx = dialogStack.findIndex(d => d.id === item.id) if (idx !== -1) { dialogStack.splice(idx, 1) } }, 200) } function handleConfirmResolve(item: DialogStackItem, confirmed: boolean) { item.resolve(confirmed) item.open = false setTimeout(() => { const idx = dialogStack.findIndex(d => d.id === item.id) if (idx !== -1) { dialogStack.splice(idx, 1) } }, 200) } function handleOpenUpdate(item: DialogStackItem, open: boolean) { if (!open) { item.open = false setTimeout(() => { const idx = dialogStack.findIndex(d => d.id === item.id) if (idx !== -1) { dialogStack.splice(idx, 1) } }, 200) } } return { dialogStack, handleFormResolve, handleConfirmResolve, handleOpenUpdate } }, render() { return this.dialogStack.map((item) => { if (item.type === 'form') { return h(DialogForm, { 'key': item.id, 'open': item.open, ...item.options, 'onUpdate:open': (open: boolean) => { if (!open) { this.handleFormResolve(item, { action: 'cancel', data: item.options.modelValue || {} }) } }, 'onResolve': (result: { action: DialogAction, data: any }) => { this.handleFormResolve(item, result) } }) } if (item.type === 'confirm') { return h(DialogConfirm, { 'key': item.id, 'open': item.open, ...item.options, 'onUpdate:open': (open: boolean) => { if (!open) { this.handleConfirmResolve(item, false) } }, 'onResolve': (confirmed: boolean) => { this.handleConfirmResolve(item, confirmed) } }) } if (item.type === 'open') { const opts = item.options as DialogOpenOptions return h(Dialog, { 'key': item.id, 'open': item.open, 'title': opts.title, 'width': opts.width, 'position': opts.position, 'dismissable': opts.dismissable, 'class': opts.class, 'onUpdate:open': (open: boolean) => { this.handleOpenUpdate(item, open) } }, { default: () => h( typeof opts.component === 'string' ? opts.component : opts.component, opts.props || {} ) }) } return null }) } }) // Register component app.component('DialogContainer', DialogContainerComponent) // Auto-mount to body if (typeof document !== 'undefined' && typeof window !== 'undefined') { const mountDialogContainer = () => { const existingContainer = document.getElementById('bagelink-dialog-root') if (!existingContainer) { const container = document.createElement('div') container.id = 'bagelink-dialog-root' document.body.appendChild(container) const dialogApp = createApp(DialogContainerComponent) // Inherit the main app's context (directives, components, provides, etc.) Object.assign(dialogApp._context, app._context) dialogApp.mount(container) } } if (document.body) { mountDialogContainer() } app.mixin({ mounted() { if (this.$root === this) { mountDialogContainer() } } }) } } }