import type { App, InjectionKey, Plugin } from 'vue' import type { PluginOptions as ToastOptions } from 'vue-toastification' import { inject } from 'vue' import * as VueToastification from 'vue-toastification' import CustomToast from '../components/Toast.vue' import 'vue-toastification/dist/index.css' import '../styles/toast-overrides.css' const Toast = (VueToastification as any).default || VueToastification const { POSITION } = VueToastification as any const useVueToast = (VueToastification as any).useToast export interface ToastApi { success: (message: string, options?: any) => void error: (message: string, options?: any) => void info: (message: string, options?: any) => void warning: (message: string, options?: any) => void show: (message: string, options?: any) => void clear: () => void } export const ToastSymbol: InjectionKey = Symbol('toast') // Global singleton instance as fallback (created on first plugin install) let globalToastApi: ToastApi | null = null export function useToast(): ToastApi { const toastApi = inject(ToastSymbol, null) if (!toastApi) { // Fallback to global singleton if injection fails if (globalToastApi) { return globalToastApi } throw new Error('Toast API not provided. Make sure ToastPlugin is installed via app.use(ToastPlugin)') } return toastApi } export type BagelToastOptions = Partial function getToastContainer(): HTMLElement | undefined { if (typeof document === 'undefined') return undefined let el = document.getElementById('bgl-toast-container') if (!el) { el = document.createElement('div') el.setAttribute('popover', 'manual') el.setAttribute('id', 'bgl-toast-container') document.body.appendChild(el) } // Re-stack on top of any open dialogs try { (el as any).hidePopover() } catch {} try { (el as any).showPopover() } catch {} return el } export const ToastPlugin: Plugin = { install: (app: App, options: BagelToastOptions = {}) => { const container = getToastContainer() const defaultOptions: ToastOptions = { position: POSITION.TOP_RIGHT, timeout: 5000, closeOnClick: true, pauseOnFocusLoss: true, pauseOnHover: true, draggable: true, draggablePercent: 0.6, showCloseButtonOnHover: false, hideProgressBar: true, closeButton: false, icon: false, rtl: false, transition: 'Vue-Toastification__fade', maxToasts: 5, newestOnTop: true, toastClassName: 'custom-toast-wrapper', bodyClassName: 'custom-toast-body', ...(container ? { container } : {}), ...options, } // Install vue-toastification app.use(Toast, defaultOptions) function restackToastContainer() { const el = typeof document !== 'undefined' ? document.getElementById('bgl-toast-container') : null if (el) { try { (el as any).hidePopover() } catch {} try { (el as any).showPopover() } catch {} } } // Create the API wrapper using lazy evaluation const api: ToastApi = { success: (message: string, opts?: any) => { restackToastContainer() const toast = useVueToast() return toast({ component: CustomToast, props: { message, type: 'success', }, }, opts) }, error: (message: string, opts?: any) => { restackToastContainer() const toast = useVueToast() return toast({ component: CustomToast, props: { message, type: 'error', }, }, opts) }, info: (message: string, opts?: any) => { restackToastContainer() const toast = useVueToast() return toast({ component: CustomToast, props: { message, type: 'info', }, }, opts) }, warning: (message: string, opts?: any) => { restackToastContainer() const toast = useVueToast() return toast({ component: CustomToast, props: { message, type: 'warning', }, }, opts) }, show: (message: string, opts?: any) => { restackToastContainer() const toast = useVueToast() return toast({ component: CustomToast, props: { message, type: 'info', }, }, opts) }, clear: () => { const toast = useVueToast() toast.clear() }, } // Set global singleton on first install if (!globalToastApi) { globalToastApi = api } app.provide(ToastSymbol, api) }, }