/** * Context-based confirmation service. Provides an async `confirm()` function * that any component can call without managing its own modal state. * * ## Setup * Mount `` once in the root `+layout.svelte` — it sets up the context * automatically. Then call `getConfirmService()` from any descendant component. * * ## Usage in event handlers * ```typescript * import { getConfirmService } from '$lib/services/confirm.svelte.js'; * const { confirm } = getConfirmService(); * * async function handleDelete() { * const ok = await confirm({ title: m.confirm_delete_title(), destructive: true }); * if (ok) doDelete(); * } * ``` * * ## Usage with use:enhance * ```svelte *
{ * const ok = await confirm({ title: m.confirm_delete_title(), destructive: true }); * if (!ok) cancel(); * }}> * ``` */ import { getContext, setContext } from 'svelte'; import { browser } from '$app/environment'; export const CONFIRM_KEY = Symbol('confirm'); export interface ConfirmOptions { title: string; body?: string; /** Defaults to m.btn_confirm() ("Bestätigen") */ confirmLabel?: string; /** Defaults to m.btn_cancel() ("Abbrechen") */ cancelLabel?: string; /** Uses danger color for confirm button. Defaults to false. */ destructive?: boolean; /** Close when clicking outside the dialog. Defaults to !destructive. */ closeOnBackdrop?: boolean; } export interface ConfirmService { confirm(opts: ConfirmOptions): Promise; /** Read by ConfirmDialog to render the current dialog. Internal use only. */ readonly options: ConfirmOptions | null; /** Called by ConfirmDialog when the user makes a choice. Internal use only. */ settle(value: boolean): void; } export function createConfirmService(): ConfirmService { let resolveRef: ((value: boolean) => void) | null = $state(null); let options: ConfirmOptions | null = $state(null); return { confirm(opts: ConfirmOptions): Promise { if (!browser) return Promise.resolve(false); // Concurrent call while dialog is already open — reject immediately. if (resolveRef !== null) return Promise.resolve(false); options = opts; return new Promise((r) => { resolveRef = r; }); }, get options() { return options; }, settle(value: boolean): void { options = null; const r = resolveRef; resolveRef = null; r?.(value); } }; } export function provideConfirmService(): ConfirmService { const service = createConfirmService(); setContext(CONFIRM_KEY, service); return service; } export function getConfirmService(): ConfirmService { // Outside component init, getContext either returns undefined or throws a Svelte error. // Either way, map it to our descriptive developer error. let service: ConfirmService | undefined; try { service = getContext(CONFIRM_KEY); } catch { throw new Error('ConfirmService not found — mount in +layout.svelte'); } if (!service) throw new Error('ConfirmService not found — mount in +layout.svelte'); return service; }