/** * @deijose/nix-ionic / overlays.ts * * Reactive overlay controllers for Nix.js following the `create*` pattern * (`createStore`, `createRouter`, `createForm` → `createToast`, `createAlert`, etc.). * * Each controller provides: * - `presented: Signal` — reactive presentation state * - `result: Signal` — dismiss event detail * - `present(opts)` — creates + presents the overlay * - `dismiss(data?, role?)` — dismisses the active overlay * - `dispose()` — transactional cleanup, dismisses if still presented * * Stale-result protection: only the most recent `present()` call's dismiss * event updates the result signal. Latest-wins: presenting a new overlay * dismisses the previous one. * * @example Function component (preferred) * ```ts * import { signal, html } from "@deijose/nix-js"; * import { createToast, createAlert } from "@deijose/nix-ionic"; * * function SettingsPage() { * // Create overlay controllers — signals close over function scope * const toast = createToast(); * const alert = createAlert(); * * const save = async () => { * // Use withLoading for async tasks * const ok = await withLoading({ message: "Saving..." }, async () => { * await fetch("/api/settings", { method: "POST" }); * }); * toast.present({ message: "Saved!", duration: 1500 }); * }; * * const deleteAccount = () => { * // confirm() returns a Promise * confirm({ * header: "Delete account", * message: "This cannot be undone.", * confirmText: "Delete", * }).then((yes) => { * if (yes) alert.present({ header: "Done", message: "Account deleted" }); * }); * }; * * return html` * * Save * Delete * * `; * } * ``` * * @example Class component with lifecycle cleanup * ```ts * import { NixComponent, html, signal } from "@deijose/nix-js"; * import { createLoading, IonPage } from "@deijose/nix-ionic"; * * class ProfilePage extends IonPage { * private loading = createLoading(); * private data = signal(null); * * override async onMount() { * // withLoading auto-dismisses on settle or error * const result = await withLoading( * { message: "Loading profile..." }, * () => fetch("/api/profile").then(r => r.json()), * ); * this.data.value = result; * } * * override onUnmount() { * // Dispose all overlay controllers to prevent leaks * this.loading.dispose(); * } * * override render() { * return html` * *

${() => JSON.stringify(this.data.value)}

*
* `; * } * } * ``` * * @example Reactive UI driven by overlay signals * ```ts * import { signal, html } from "@deijose/nix-js"; * import { createModal } from "@deijose/nix-ionic"; * * function ProductList() { * const modal = createModal(); * const selected = signal(null); * * const openDetail = (id: string) => { * selected.value = id; * modal.present({ component: "product-detail" }); * }; * * return html` * * * openDetail("1")}>Product 1 * * * * ${() => modal.result.value * ? html`

Modal closed with role: ${modal.result.value.role}

` * : null} * * * modal.presented.value} * @click=${() => openDetail("2")} * >Product 2 *
* `; * } * ``` */ import { type Signal, type NixTemplate, type NixComponent } from "@deijose/nix-js"; import { toastController, alertController, loadingController, actionSheetController, popoverController, modalController, pickerController } from "@ionic/core"; export interface OverlayHandle { /** True while the overlay is presented (visible). */ readonly presented: Signal; /** The dismiss event detail (role, data) — null until dismissed. */ readonly result: Signal; /** Present the overlay with the given options. */ present(options: Record): Promise; /** Dismiss the active overlay with optional data and role. */ dismiss(data?: unknown, role?: string): Promise; /** Cleanup: dismisses any active overlay. Safe to call multiple times. */ dispose(): void; } /** * Create a reactive toast overlay controller. * Call `dispose()` in `onUnmount()` when used in a class component. */ export declare function createToast(): OverlayHandle; /** * Create a reactive alert overlay controller. * Call `dispose()` in `onUnmount()` when used in a class component. */ export declare function createAlert(): OverlayHandle; /** * Create a reactive loading overlay controller. * Call `dispose()` in `onUnmount()` when used in a class component. */ export declare function createLoading(): OverlayHandle; /** * Create a reactive action-sheet overlay controller. * Call `dispose()` in `onUnmount()` when used in a class component. */ export declare function createActionSheet(): OverlayHandle; /** * Create a reactive popover overlay controller. * Call `dispose()` in `onUnmount()` when used in a class component. */ export declare function createPopover(): OverlayHandle; /** * Create a reactive modal overlay controller. * Call `dispose()` in `onUnmount()` when used in a class component. */ export declare function createModal(): OverlayHandle; /** * Present a toast and return immediately (fire-and-forget). * The toast auto-dismisses after its duration. * * @example * ```ts * import { showToast } from "@deijose/nix-ionic"; * * showToast({ message: "Saved!", duration: 1500 }); * ``` */ export declare function showToast(options: Record): Promise; /** * Present a loading overlay, run an async task, then dismiss. * Returns the task result. If the task throws, the loading is still dismissed. * * @example * ```ts * import { withLoading } from "@deijose/nix-ionic"; * * const data = await withLoading( * { message: "Fetching..." }, * () => fetch("/api/data").then(r => r.json()), * ); * ``` */ export declare function withLoading(options: Record, task: () => Promise): Promise; /** * Present a confirm alert and return true if "OK" was clicked, false otherwise. * * @example * ```ts * import { confirm } from "@deijose/nix-ionic"; * * const yes = await confirm({ * header: "Delete", * message: "Are you sure?", * confirmText: "Delete", * }); * if (yes) deleteItem(); * ``` */ export declare function confirm(options: { header?: string; message?: string; confirmText?: string; cancelText?: string; }): Promise; export { toastController, alertController, loadingController, actionSheetController, popoverController, modalController, pickerController, }; /** * A Nix.js FrameworkDelegate that can mount NixTemplate or NixComponent * instances inside Ionic overlays (modal, popover). * * Ionic's `FrameworkDelegate` interface has two methods: * - `attachViewToDom(container, component, props, cssClasses)` → HTMLElement * - `removeViewFromDom(container, component)` → void * * For Nix.js, "component" is a function that returns a NixTemplate or * NixComponent. The delegate creates a wrapper div, mounts the Nix.js * content into it, appends it to the overlay, and tracks the unmount * handle for cleanup on dismiss. */ export interface NixOverlayDelegate { attachViewToDom(container: HTMLElement, component: () => NixTemplate | NixComponent, propsOrData?: Record, cssClasses?: string[]): Promise; removeViewFromDom(container: HTMLElement, component: unknown): Promise; } /** * Create a Nix.js delegate for use with Ionic modal/popover controllers. * The delegate mounts Nix.js templates inside overlays and cleans up on * removal. * * @example * ```ts * import { createNixDelegate, createModal } from "@deijose/nix-ionic"; * import { html, signal } from "@deijose/nix-js"; * * const delegate = createNixDelegate(); * const modal = createModal(); * * // The delegate is passed via `delegate` option * modal.present({ * component: () => html`

Hello from modal!

`, * delegate, * }); * ``` */ export declare function createNixDelegate(): NixOverlayDelegate; /** * Options for presenting a modal with Nix.js content. * The `component` function returns a NixTemplate or NixComponent that will * be mounted inside the modal via the Nix.js delegate. */ export interface ModalOptions { /** Function returning the Nix.js content to mount inside the modal. */ component: () => NixTemplate | NixComponent; /** Component props/data passed to the delegate. */ componentProps?: Record; /** CSS classes to add to the mounted content wrapper. */ cssClasses?: string[]; /** Modal-specific options. */ backdropDismiss?: boolean; showBackdrop?: boolean; animated?: boolean; canDismiss?: boolean | (() => Promise); /** Custom delegate (if not provided, a default Nix.js delegate is used). */ delegate?: NixOverlayDelegate; [key: string]: unknown; } /** * Options for presenting a popover with Nix.js content. */ export interface PopoverOptions { /** Function returning the Nix.js content to mount inside the popover. */ component: () => NixTemplate | NixComponent; /** Component props/data passed to the delegate. */ componentProps?: Record; /** CSS classes to add to the mounted content wrapper. */ cssClasses?: string[]; /** The element that the popover should be anchored to. */ event?: Event | { target: HTMLElement; }; /** Popover-specific options. */ backdropDismiss?: boolean; showBackdrop?: boolean; animated?: boolean; /** Custom delegate (if not provided, a default Nix.js delegate is used). */ delegate?: NixOverlayDelegate; [key: string]: unknown; } /** * Enhanced modal controller with Nix.js delegate support. * Unlike the basic `createModal()`, this automatically creates and uses * a Nix.js delegate so `component` can be a NixTemplate/NixComponent. * * @example * ```ts * import { createModalController, createNixDelegate } from "@deijose/nix-ionic"; * import { html, signal } from "@deijose/nix-js"; * * const modal = createModalController(); * * modal.present({ * component: () => html` * * Settings * *

Modal content here

* `, * }); * * // React to dismiss * effect(() => { * if (modal.result.value) { * console.log("Modal dismissed:", modal.result.value); * } * }); * ``` */ export declare function createModalController(delegate?: NixOverlayDelegate): OverlayHandle; /** * Enhanced popover controller with Nix.js delegate support. * Automatically creates and uses a Nix.js delegate so `component` can be * a NixTemplate/NixComponent. * * @example * ```ts * import { createPopoverController } from "@deijose/nix-ionic"; * import { html } from "@deijose/nix-js"; * * const popover = createPopoverController(); * * // In an event handler: * popover.present({ * component: () => html`

Popover content

`, * event, // the click event for anchoring * }); * ``` */ export declare function createPopoverController(delegate?: NixOverlayDelegate): OverlayHandle; /** * Options for a picker column. */ export interface PickerColumnOption { text: string; value: string | number; disabled?: boolean; } /** * Options for presenting a picker overlay. */ export interface PickerOptions { columns: Array<{ name: string; options: PickerColumnOption[]; selectedIndex?: number; }>; buttons?: Array<{ text: string; role?: string; handler?: (selected: Record) => void; }>; cssClass?: string; animated?: boolean; backdropDismiss?: boolean; [key: string]: unknown; } /** * Create a reactive picker overlay controller. * Pickers are column-based selection overlays (like iOS date pickers). * * @example * ```ts * import { createPicker } from "@deijose/nix-ionic"; * * const picker = createPicker(); * await picker.present({ * columns: [{ * name: "size", * options: [ * { text: "Small", value: "sm" }, * { text: "Medium", value: "md" }, * { text: "Large", value: "lg" }, * ], * }], * buttons: [ * { text: "Cancel", role: "cancel" }, * { text: "Done", role: "confirm" }, * ], * }); * ``` */ export declare function createPicker(): OverlayHandle;