import { runPopupSteps, type PopupStepsOptions } from './popup-stepper'; import type Swal from 'sweetalert2'; import type { SweetAlertCustomClass, SweetAlertOptions, SweetAlertResult } from 'sweetalert2'; import { loadPeer } from '../../src/peer-loader'; export type PopupOptions = SweetAlertOptions; export type PopupResult = SweetAlertResult; function withClass( required: string, custom?: string | readonly string[] ): string { return [ required, ...(typeof custom === 'string' ? custom.split(/\s+/) : custom ?? []) ] .filter( (value, index, values) => value && values.indexOf(value) === index ) .join(' '); } /** Materialize-themed SweetAlert2 dialogs. No element initialization is needed. */ export class Popup { /** Fresh defaults so per-dialog customization cannot affect later popups. */ static get defaults(): PopupOptions { return { buttonsStyling: false, heightAuto: false, customClass: { container: 'popup-container', popup: 'popup', confirmButton: 'btn filled', cancelButton: 'btn outlined', denyButton: 'btn tonal' } }; } private static _load(): Promise { // Use the JS-only build: the consumer controls stylesheet order. return loadPeer( { specifier: 'sweetalert2', globalName: 'Swal', feature: 'Popup', cdnHint: '\n' + ' \n' + ' (copy from node_modules/sweetalert2/dist/; load the CSS before materialize.css)' }, () => import('sweetalert2/dist/sweetalert2.js') ); } /** Open a dialog and resolve with SweetAlert2's confirmation/dismissal result. */ static async fire( options: PopupOptions = {} ): Promise>> { const swal = await Popup._load(); const defaults = Popup.defaults; const customClass: SweetAlertCustomClass = { ...defaults.customClass, ...options.customClass, // Keep the scoped theme even when consumers add their own classes. container: withClass( 'popup-container', options.customClass?.container ), popup: withClass( 'popup', options.customClass?.popup ) }; /* * SweetAlertOptions is a discriminated union because `input: "file"` * uses a different inputValidator signature. Object spreading causes * TypeScript to lose that correlation, even though the resulting * object is valid. */ const mergedOptions = { ...defaults, ...options, customClass } as SweetAlertOptions; const result = await swal.fire(mergedOptions); /* * SweetAlert2 declares its own internal Awaited, while modern * TypeScript provides the global Awaited. They are semantically * equivalent here but TypeScript can report them as unrelated. */ return result as PopupResult>; } /** Run async steps in order, with progress, cancellation and failed-step retry. */ static async steps(options: PopupStepsOptions): Promise> { const swal = await Popup._load(); return runPopupSteps(swal, options, dialog => Popup.fire(dialog)); } /** Confirm the current dialog, including its validation and preConfirm flow. */ static async clickConfirm(): Promise { const swal = await Popup._load(); swal.clickConfirm(); } /** Close the current SweetAlert2 dialog, resolving its pending result. */ static async close(): Promise { const swal = await Popup._load(); swal.close(); } }