npm package: @subrotosaha/notification
Description: A lightweight toast notification and confirmation dialog library for any JavaScript framework
Author: Subroto Saha
Latest version: 1.2.0

# @subrotosaha/notification

A lightweight, zero-dependency toast notification & confirm dialog library for vanilla JS, React, Vue, Angular, and Svelte. Features multiple visual variants, six positions, inline action buttons, promise tracking, 15 input types, async hooks, multi-step dialogs, **32 built-in icons**, custom icon registration, icon library integration (Lucide, Iconify, etc.), and full TypeScript support.

---

## Table of Contents

- [Installation](#installation)
- [Setup](#setup)
  - [Vanilla JS](#vanilla-js)
  - [React](#react)
  - [Next.js (App Router)](#nextjs-app-router)
- [Global Config](#global-config)
- [Toast Notifications](#toast-notifications)
  - [Basic Usage](#basic-usage)
  - [Loading Toast](#loading-toast)
  - [Promise Toast](#promise-toast)
  - [Dismiss](#dismiss)
  - [NotifyOptions](#notifyoptions)
  - [Action Button](#action-button)
  - [Positions](#positions)
  - [Toast Variants](#toast-variants)
- [Icons](#icons)
  - [Built-in Icons](#built-in-icons)
  - [Custom Icons](#custom-icons)
  - [Icon Library Integration](#icon-library-integration)
  - [resolveIconHtml()](#resolveiconhtml)
- [Confirm Dialogs](#confirm-dialogs)
  - [fire()](#fire)
  - [Shorthand Methods](#shorthand-methods)
  - [mixin()](#mixin)
  - [queue()](#queue)
  - [Runtime Controls](#runtime-controls)
  - [FireOptions Reference](#fireoptions-reference)
  - [FireResult Object](#fireresult-object)
  - [ConfirmUpdateOptions](#confirmupdateoptions)
  - [Input Types](#input-types)
  - [Dismiss Reasons](#dismiss-reasons)
  - [Confirm Variants](#confirm-variants)
- [TypeScript](#typescript)
- [Changelog](#changelog)

---

## Installation

```bash
npm install @subrotosaha/notification
```

---

## Setup

### Vanilla JS

```js
import notification from "@subrotosaha/notification";
import "@subrotosaha/notification/style.css";
```

### React

```jsx
// main.jsx or App.jsx — import styles once at the root
import "@subrotosaha/notification/style.css";

// In any component
import notification from "@subrotosaha/notification";
```

> **Note:** This library manipulates `document.body` directly — it is client-side only. In Next.js or other SSR environments, call it inside `useEffect` or a browser-only code path.

### Next.js (App Router)

```jsx
"use client";
import notification from "@subrotosaha/notification";
import "@subrotosaha/notification/style.css";

export default function Page() {
  const handleClick = () => {
    notification.notify.success("Saved successfully!", {
      position: "top-right",
    });
  };

  return <button onClick={handleClick}>Save</button>;
}
```

---

## Global Config

Set default options applied to all subsequent toasts and/or confirm dialogs. Call once during app bootstrap — options are **merged**, not replaced.

```js
notification.config({
  toast: {
    position: "bottom-right",
    duration: 4000,
    variant: "filled",
    pauseOnHover: true,
    progressBar: true,
  },
  confirm: {
    confirmButtonText: "Yes",
    cancelButtonText: "No",
  },
});
```

| Field     | Type                         | Description                                     |
| --------- | ---------------------------- | ----------------------------------------------- |
| `toast`   | `Partial<GlobalToastConfig>` | Default `position`, `duration`, `variant`, etc. |
| `confirm` | `Partial<FireOptions>`       | Default confirm dialog options                  |

---

## Toast Notifications

### Basic Usage

```js
notification.notify.success("Profile saved!");
notification.notify.warning("Low disk space.");
notification.notify.error("Upload failed.");
notification.notify.info("Your session expires soon.");
```

Each method returns a numeric **toast ID** (`number | null`).

```js
notification.notify.success("File uploaded", {
  title: "Upload Complete",
  variant: "filled",
  position: "bottom-right",
  duration: 3000,
  progressBar: true,
});
```

### Loading Toast

Displays a persistent spinner toast that **never auto-dismisses**. Call `dismiss(id)` when done.

```js
const id = notification.notify.loading("Uploading…");
await upload();
notification.notify.dismiss(id);
```

### Promise Toast

Tracks an async operation and transitions through `loading → success | error` automatically.

```js
notification.notify.promise(
  fetch("/api/save").then((r) => r.json()),
  {
    loading: "Saving…",
    success: (data) => `Saved ${data.count} records.`,
    error: (err) => `Failed: ${err.message}`,
  }
);
```

The original promise is returned so it can still be awaited:

```js
const data = await notification.notify.promise(fetchUser(id), {
  loading: "Loading user…",
  success: "User loaded!",
  error: "Could not load user.",
});
```

### Dismiss

```js
// Dismiss a single toast by ID
notification.notify.dismiss(id);

// Dismiss all visible toasts
notification.notify.dismissAll();
```

### NotifyOptions

All fields are optional and merge with global defaults from `config()`.

| Option         | Type                               | Default       | Description                                                                 |
| -------------- | ---------------------------------- | ------------- | --------------------------------------------------------------------------- |
| `title`        | `string \| null`                   | `null`        | Optional bold title above the message                                       |
| `variant`      | `'filled' \| 'outlined'`           | `''`          | Visual style                                                                |
| `position`     | `NotifyPosition`                   | `'top-right'` | Screen position                                                             |
| `duration`     | `number`                           | `5000`        | Auto-dismiss delay in ms. `0` = persistent                                  |
| `pauseOnHover` | `boolean`                          | `true`        | Pause the timer while the cursor is over the toast                          |
| `progressBar`  | `boolean`                          | `false`       | Show an animated draining progress bar                                      |
| `action`       | `NotifyActionConfig \| null`       | `null`        | Inline action button (e.g. "Undo")                                          |
| `onClick`      | `() => void \| null`               | `null`        | Called when the toast body is clicked (then dismisses)                      |
| `onClose`      | `(reason: string) => void \| null` | `null`        | Called after the toast is removed from the DOM                              |
| `iconHtml`     | `string \| Element \| null`        | `null`        | Custom HTML string, SVG string, or DOM `Element` replacing the default icon |
| `closable`     | `boolean`                          | `true`        | Show or hide the × close button                                             |

### Action Button

Render an inline action button on the right side of the toast.

```js
notification.notify.warning("Item moved to trash.", {
  action: {
    text: "Undo",
    onClick: () => restoreItem(),
  },
});
```

### Positions

| Value             | Description                         |
| ----------------- | ----------------------------------- |
| `'top-right'`     | Top-right corner _(default)_        |
| `'top-left'`      | Top-left corner                     |
| `'top-center'`    | Top-center, slides down from above  |
| `'bottom-right'`  | Bottom-right corner                 |
| `'bottom-left'`   | Bottom-left corner                  |
| `'bottom-center'` | Bottom-center, slides up from below |

Toasts at the same position stack and reposition automatically when one is dismissed.

### Toast Variants

| Variant      | Description                                          |
| ------------ | ---------------------------------------------------- |
| _(default)_  | Tinted glassmorphism background with coloured border |
| `'filled'`   | Solid saturated background with white text           |
| `'outlined'` | Transparent background with a 1.5 px coloured border |

---

## Confirm Dialogs

### fire()

Full options API — returns `Promise<FireResult<T>>`.

```js
const result = await notification.confirm.fire({
  icon: "warning",
  title: "Delete account?",
  text: "All your data will be permanently removed.",
  confirmButtonText: "Yes, delete it",
  cancelButtonText: "Cancel",
  showDenyButton: true,
  denyButtonText: "Archive instead",
});

if (result.isConfirmed) {
  // confirm button clicked
} else if (result.isDenied) {
  // deny button clicked
} else if (result.isDismissed) {
  console.log("Dismissed by:", result.dismiss); // 'cancel' | 'backdrop' | 'esc' | 'close' | 'timer'
}
```

### Shorthand Methods

Pre-configured `fire()` calls with a specific icon. Accept a `FireOptions` object (minus `icon`).

```js
notification.confirm.success(options?)
notification.confirm.warning(options?)
notification.confirm.error(options?)
notification.confirm.info(options?)
notification.confirm.question(options?)
```

**Example:**

```js
const result = await notification.confirm.warning({
  title: "Delete item?",
  text: "This cannot be undone.",
  confirmButtonText: "Delete",
});

if (result.isConfirmed) {
  await deleteItem();
  notification.notify.success("Deleted.", { variant: "filled" });
}
```

```js
// No cancel button
await notification.confirm.info({
  title: "Heads up",
  text: "Your free trial ends in 3 days.",
  showCancelButton: false,
  confirmButtonText: "Got it",
});
```

### mixin()

Create a **reusable factory** with shared defaults. Per-call options override the mixin defaults.

```js
const DeleteAlert = notification.confirm.mixin({
  icon: "warning",
  confirmButtonText: "Delete",
  cancelButtonText: "Cancel",
  showCancelButton: true,
});

const result = await DeleteAlert.fire({ title: "Delete this record?" });
const result2 = await DeleteAlert.fire({ title: "Delete all records?" });
```

### queue()

Show multiple dialogs in **sequence**. Stops early if any step is dismissed.

```js
const steps = ["1", "2", "3"];

const { results, dismiss, isConfirmed } = await notification.confirm.queue([
  {
    title: "Step 1 — Enter your name",
    input: "text",
    inputLabel: "Name",
    progressSteps: steps,
    currentProgressStep: 0,
  },
  {
    title: "Step 2 — Enter your email",
    input: "email",
    inputLabel: "Email",
    progressSteps: steps,
    currentProgressStep: 1,
  },
  {
    title: "Step 3 — Confirm",
    text: "Review your details and submit.",
    progressSteps: steps,
    currentProgressStep: 2,
  },
]);

if (isConfirmed) {
  console.log(
    "Values:",
    results.map((r) => r.value)
  );
}
```

> **Note:** `queue()` auto-injects `currentProgressStep` and `progressSteps` when not provided, numbering steps `"1"`, `"2"`, `"3"`, etc.

### Runtime Controls

```js
// Check if a dialog is currently visible
notification.confirm.isVisible(); // boolean

// Check if preConfirm is running
notification.confirm.isLoading(); // boolean

// Get the popup DOM element
const popup = notification.confirm.getPopup(); // HTMLElement | null

// Programmatically close with 'close' dismiss reason
notification.confirm.close();

// Update live dialog options while it is open
notification.confirm.update({
  title: "Still processing…",
  confirmButtonText: "Please wait",
  showCancelButton: false,
});
```

---

### FireOptions Reference

| Option                | Type                                                     | Default      | Description                                                         |
| --------------------- | -------------------------------------------------------- | ------------ | ------------------------------------------------------------------- |
| `title`               | `string`                                                 | `''`         | Dialog title (HTML allowed)                                         |
| `text`                | `string`                                                 | `''`         | Plain-text body content                                             |
| `html`                | `string`                                                 | `''`         | HTML body content (overrides `text`)                                |
| `icon`                | `IconType` (built-in name or any registered custom name) | `'question'` | Icon to display — see [Icons](#icons) for all 32 built-in names     |
| `iconHtml`            | `string \| Element \| null`                              | `null`       | Custom HTML string or DOM `Element` that replaces the icon entirely |
| `variant`             | `'filled' \| 'outlined'`                                 | `''`         | Visual style                                                        |
| `showConfirmButton`   | `boolean`                                                | `true`       | Show the Confirm button                                             |
| `showDenyButton`      | `boolean`                                                | `false`      | Show an optional Deny button                                        |
| `showCancelButton`    | `boolean`                                                | `true`       | Show the Cancel button                                              |
| `confirmButtonText`   | `string`                                                 | `'OK'`       | Confirm button label                                                |
| `denyButtonText`      | `string`                                                 | `'No'`       | Deny button label                                                   |
| `cancelButtonText`    | `string`                                                 | `'Cancel'`   | Cancel button label                                                 |
| `reverseButtons`      | `boolean`                                                | `false`      | Swap Confirm and Cancel button order                                |
| `showCloseButton`     | `boolean`                                                | `false`      | Show an × close button in the corner                                |
| `allowOutsideClick`   | `boolean \| () => boolean`                               | `true`       | Close on backdrop click                                             |
| `allowEscapeKey`      | `boolean \| () => boolean`                               | `true`       | Close on Escape key                                                 |
| `footer`              | `string`                                                 | `''`         | HTML/text rendered below the action buttons                         |
| `imageUrl`            | `string`                                                 | `''`         | URL for a custom image inside the dialog                            |
| `imageWidth`          | `number \| string \| null`                               | `null`       | CSS width of the custom image                                       |
| `imageHeight`         | `number \| string \| null`                               | `null`       | CSS height of the custom image                                      |
| `imageAlt`            | `string`                                                 | `''`         | Alt text for the custom image                                       |
| `input`               | `InputType \| null`                                      | `null`       | Render an input field inside the dialog                             |
| `inputLabel`          | `string`                                                 | `''`         | Label text above the input                                          |
| `inputPlaceholder`    | `string`                                                 | `''`         | Input placeholder                                                   |
| `inputValue`          | `string \| number \| boolean`                            | `''`         | Pre-filled input value                                              |
| `inputOptions`        | `Record<string, string> \| Map<string, string>`          | `{}`         | Options for `select` or `radio` inputs                              |
| `inputAttributes`     | `Record<string, string>`                                 | `{}`         | Extra HTML attributes on the input element                          |
| `inputValidator`      | `(value) => string \| null \| Promise<...>`              | `null`       | Return an error string to block confirm, falsy to allow             |
| `timer`               | `number \| null`                                         | `null`       | Auto-close after N milliseconds                                     |
| `timerProgressBar`    | `boolean`                                                | `false`      | Show a draining progress bar when `timer` is set                    |
| `preConfirm`          | `(inputValue) => Promise<T \| false>`                    | `null`       | Async hook on Confirm — return `false` to keep the dialog open      |
| `showLoaderOnConfirm` | `boolean`                                                | `false`      | Show a spinner on the Confirm button while `preConfirm` runs        |
| `preDeny`             | `() => Promise<false \| void>`                           | `null`       | Async hook on Deny — return `false` to abort the denial             |
| `progressSteps`       | `Array<string \| number>`                                | `[]`         | Multi-step progress indicator labels                                |
| `currentProgressStep` | `number \| null`                                         | `null`       | Zero-based index of the active step                                 |
| `willOpen`            | `(popup: HTMLElement) => void`                           | `null`       | Called before the dialog animates in                                |
| `didOpen`             | `(popup: HTMLElement) => void`                           | `null`       | Called after the dialog is fully visible                            |
| `willClose`           | `(popup: HTMLElement) => void`                           | `null`       | Called before the dialog begins closing                             |
| `didClose`            | `() => void`                                             | `null`       | Called after the dialog is removed from the DOM                     |

---

### FireResult Object

| Property      | Type                | Description                                                           |
| ------------- | ------------------- | --------------------------------------------------------------------- |
| `isConfirmed` | `boolean`           | `true` when the Confirm button was clicked                            |
| `isDenied`    | `boolean`           | `true` when the Deny button was clicked                               |
| `isDismissed` | `boolean`           | `true` when the dialog was closed without confirming                  |
| `dismiss`     | `DismissReasonType` | Reason string when `isDismissed` is `true`                            |
| `value`       | `T`                 | Input value, `preConfirm` return value, or `true` for simple confirms |

---

### ConfirmUpdateOptions

Fields that can be changed on a **currently open** dialog via `notification.confirm.update()`.

| Field               | Type      | Description                           |
| ------------------- | --------- | ------------------------------------- |
| `title`             | `string`  | New title HTML                        |
| `text`              | `string`  | New plain-text body                   |
| `html`              | `string`  | New rich HTML body (overrides `text`) |
| `confirmButtonText` | `string`  | New Confirm button label              |
| `cancelButtonText`  | `string`  | New Cancel button label               |
| `showConfirmButton` | `boolean` | Show or hide the Confirm button       |
| `showCancelButton`  | `boolean` | Show or hide the Cancel button        |

---

### Input Types

Pass any of these to the `input` option:

`'text'` · `'email'` · `'password'` · `'number'` · `'tel'` · `'url'` · `'textarea'` · `'select'` · `'checkbox'` · `'radio'` · `'range'` · `'date'` · `'datetime-local'` · `'time'` · `'search'`

**Text input with validation:**

```js
const result = await notification.confirm.fire({
  title: "Enter your email",
  input: "email",
  inputPlaceholder: "you@example.com",
  inputValidator: (value) => {
    if (!value) return "Email is required.";
  },
  confirmButtonText: "Submit",
});

if (result.isConfirmed) console.log("Email:", result.value);
```

**Select input:**

```js
await notification.confirm.fire({
  title: "Choose a country",
  input: "select",
  inputOptions: { us: "United States", ca: "Canada", gb: "United Kingdom" },
  inputPlaceholder: "Select a country",
});
```

**Range input:**

```js
await notification.confirm.fire({
  title: "Select volume",
  input: "range",
  inputAttributes: { min: "0", max: "100", step: "5" },
  inputValue: 50,
});
```

---

### Dismiss Reasons

When `isDismissed` is `true`, `result.dismiss` will be one of:

| Value        | Trigger                           |
| ------------ | --------------------------------- |
| `'backdrop'` | Clicked outside the dialog        |
| `'cancel'`   | Clicked the Cancel button         |
| `'close'`    | Clicked the × close button        |
| `'esc'`      | Pressed the Escape key            |
| `'timer'`    | Auto-closed by the `timer` option |

Use the `DismissReason` constant for type-safe comparisons:

```js
import notification, { DismissReason } from "@subrotosaha/notification";

const result = await notification.confirm.fire({ title: "Continue?" });

if (result.dismiss === DismissReason.timer) console.log("Timed out");
if (result.dismiss === DismissReason.cancel) console.log("User cancelled");

// Also accessible on the notification object:
notification.DismissReason.backdrop;
notification.DismissReason.esc;
```

---

### Confirm Variants

| Variant      | Description                                                   |
| ------------ | ------------------------------------------------------------- |
| _(default)_  | Tinted glassmorphism with accent border per icon type         |
| `'filled'`   | Deep solid saturated background matching the icon colour      |
| `'outlined'` | Glass background with a full-perimeter 1.5 px coloured border |

```js
// Filled
await notification.confirm.fire({
  icon: "error",
  title: "Delete permanently?",
  variant: "filled",
  confirmButtonText: "Delete",
});

// Outlined
await notification.confirm.fire({
  icon: "info",
  title: "Heads up",
  text: "Your free trial ends in 3 days.",
  variant: "outlined",
  showCancelButton: false,
  confirmButtonText: "Got it",
});
```

---

## Advanced Examples

### Timer with progress bar

```js
await notification.confirm.fire({
  icon: "info",
  title: "Session expiring",
  text: "You will be logged out automatically.",
  timer: 5000,
  timerProgressBar: true,
  showCancelButton: false,
  confirmButtonText: "Stay logged in",
});
```

### `preConfirm` — async validation / server action

```js
const result = await notification.confirm.fire({
  title: "Submit form?",
  input: "textarea",
  inputLabel: "Your message",
  inputPlaceholder: "Write something…",
  confirmButtonText: "Send",
  showLoaderOnConfirm: true,
  preConfirm: async (message) => {
    const response = await fetch("/api/messages", {
      method: "POST",
      body: JSON.stringify({ message }),
    });
    if (!response.ok) return false; // keep dialog open
    return await response.json();
  },
});

if (result.isConfirmed) {
  notification.notify.success("Message sent!", { variant: "filled" });
}
```

### `preDeny` — confirm before discarding

```js
await notification.confirm.fire({
  title: "Save changes?",
  showDenyButton: true,
  denyButtonText: "Discard",
  preDeny: async () => {
    const check = await notification.confirm.warning({
      title: "Really discard?",
      confirmButtonText: "Yes, discard",
    });
    if (!check.isConfirmed) return false; // keep original dialog open
  },
});
```

### Lifecycle hooks

```js
await notification.confirm.fire({
  title: "Loading…",
  willOpen: (popup) => console.log("opening", popup),
  didOpen: (popup) => {
    // start animations, fetch data, focus an element…
  },
  willClose: (popup) => console.log("closing"),
  didClose: () => console.log("removed from DOM"),
});
```

### Live `update()` inside `preConfirm`

```js
await notification.confirm.fire({
  title: "Uploading file",
  confirmButtonText: "Upload",
  preConfirm: async () => {
    notification.confirm.update({ confirmButtonText: "Uploading…" });
    await uploadFile();
    notification.confirm.update({ confirmButtonText: "Done!" });
  },
});
```

---

## Icons

### Built-in Icons

32 icons are bundled and ready to use by name. Pass them to the `icon` option in confirm dialogs, or pull their SVG strings from `notification.icons`.

| Category    | Names                                                 |
| ----------- | ----------------------------------------------------- |
| Status      | `success` · `warning` · `error` · `info` · `question` |
| UI Controls | `close` · `spinner` · `loading`                       |
| Actions     | `trash` · `edit` · `copy` · `download` · `upload`     |
| Navigation  | `refresh` · `search` · `link` · `mail` · `bell`       |
| Feedback    | `star` · `heart` · `thumbup` · `thumbdown`            |
| Identity    | `user` · `lock` · `unlock` · `settings` · `pin`       |
| Misc        | `fire` · `shield` · `calendar` · `clock` · `home`     |

```js
await notification.confirm.fire({ icon: "trash", title: "Delete this?" });
await notification.confirm.fire({ icon: "shield", title: "Access restricted" });
await notification.confirm.fire({
  icon: "calendar",
  title: "Schedule a meeting",
});
```

### Custom Icons

Register your own icons with `registerIcon()`. They work anywhere the `icon` option is accepted and can override built-ins.

```js
import notification, { registerIcon } from "@subrotosaha/notification";

// Plain SVG string
registerIcon("bitcoin", '<svg viewBox="0 0 320 512">…</svg>');

// Also available on the notification object
notification.registerIcon("bitcoin", '<svg viewBox="0 0 320 512">…</svg>');

// Use by name in confirm dialogs
await notification.confirm.fire({
  icon: "bitcoin",
  title: "Pay with Bitcoin?",
});

// Or grab the SVG string directly
notification.notify.success("Payment received", {
  iconHtml: notification.icons["bitcoin"],
});
```

Access the full live registry at any time:

```js
console.log(notification.icons); // Record<string, string> — built-ins + your custom icons
```

### Icon Library Integration

`iconHtml` accepts a raw HTML/SVG **string** or a DOM **`Element`** — so icon libraries that return nodes work directly with no extra conversion step.

#### Lucide (vanilla JS)

```js
import { createElement, Zap } from "lucide";

// Pass an Element directly
notification.notify.success("Charged!", {
  iconHtml: createElement(Zap),
});

// Or register once and use by name
notification.registerIcon("zap", createElement(Zap));
await notification.confirm.fire({ icon: "zap", title: "Quick action?" });
```

#### Lucide React

```jsx
import { renderToStaticMarkup } from "react-dom/server";
import { Zap } from "lucide-react";

notification.notify.success("Charged!", {
  iconHtml: renderToStaticMarkup(<Zap size={20} />),
});
```

#### Iconify

```js
import { loadIcon, iconToSVG, iconToHTML } from "@iconify/utils";

const data = await loadIcon("mdi:flash");
notification.registerIcon("flash", iconToHTML(iconToSVG(data)));

await notification.confirm.fire({ icon: "flash", title: "Go!" });
```

#### Iconify web component (DOM element)

```js
const el = document.createElement("iconify-icon");
el.setAttribute("icon", "mdi:flash");
notification.notify.info("Tip", { iconHtml: el });
```

#### Emoji or `<img>`

```js
notification.notify.success("Deployed!", { iconHtml: "🚀" });
notification.notify.warning("Check this", {
  iconHtml: '<img src="/logo.svg" width="20" alt="logo">',
});
```

### resolveIconHtml()

A utility that normalises any accepted icon value into a plain HTML string suitable for `innerHTML`. Useful when consuming the library alongside your own rendering logic.

```js
import { resolveIconHtml } from "@subrotosaha/notification";

resolveIconHtml(null); // → ''
resolveIconHtml("…svg string…"); // → '…svg string…'
resolveIconHtml(domElement); // → element.outerHTML
```

---

## TypeScript

Full type definitions are included. No `@types` package needed.

```ts
import notification, {
  FireOptions,
  FireResult,
  NotifyOptions,
  NotifyPosition,
  NotifyVariant,
  PromiseMessages,
  ConfirmUpdateOptions,
  DismissReason,
  DismissReasonType,
  MixinInstance,
  QueueResult,
  InputType,
  IconType,
  GlobalConfig,
  registerIcon,
  resolveIconHtml,
} from "@subrotosaha/notification";

// Typed confirm with preConfirm return value
const options: FireOptions<{ id: number; name: string }> = {
  title: "Confirm action",
  input: "text",
  inputLabel: "Username",
  confirmButtonText: "Submit",
  showLoaderOnConfirm: true,
  preConfirm: async (username) => {
    const res = await fetch(`/api/users/${username}`);
    if (!res.ok) throw new Error("User not found");
    return res.json();
  },
};

const result: FireResult<{ id: number; name: string }> =
  await notification.confirm.fire(options);

if (result.isConfirmed) {
  console.log(result.value?.name); // fully typed
}

// Typed toast
const notifyOptions: NotifyOptions = {
  variant: "filled",
  position: "bottom-right",
  title: "Done",
  duration: 3000,
  progressBar: true,
  action: { text: "Undo", onClick: () => undo() },
};
notification.notify.success("Saved!", notifyOptions);

// Typed promise toast
notification.notify.promise<{ count: number }>(
  fetch("/api/save").then((r) => r.json()),
  {
    loading: "Saving…",
    success: (data) => `Saved ${data.count} items`,
    error: (err) => `Error: ${(err as Error).message}`,
  } satisfies PromiseMessages<{ count: number }>
);

// Global config with type checking
notification.config({
  toast: { position: "top-right", variant: "filled", duration: 5000 },
  confirm: { confirmButtonText: "Yes", cancelButtonText: "No" },
} satisfies GlobalConfig);

// Dismiss reasons
if (result.dismiss === DismissReason.timer) {
  console.log("Timed out");
}
```

---

## Changelog

#### Toast

- `notify.success()`, `notify.warning()`, `notify.error()`, `notify.info()`
- `notify.loading()` — persistent spinner toast, manual dismiss required
- `notify.dismiss(id)` / `notify.dismissAll()`
- `notify.promise(promise, messages, options?)` — auto loading → success/error tracking
- `NotifyOptions`: `title`, `variant`, `position`, `duration`, `pauseOnHover`, `progressBar`, `action`, `onClick`, `onClose`, `iconHtml`, `closable`
- Inline action button support (`action.text` + `action.onClick`)
- Variants: `default`, `filled`, `outlined`
- Positions: `top-right`, `top-left`, `top-center`, `bottom-right`, `bottom-left`, `bottom-center`
- Toasts stack and reposition automatically per position bucket

#### Confirm

- `confirm.fire(options)` — full options API
- `confirm.success/warning/error/info/question(options)` — icon-preset shorthand methods
- `confirm.mixin(defaults)` — reusable pre-configured dialog factory
- `confirm.queue(steps)` — sequential multi-step dialogs with auto progress steps
- `confirm.isVisible()`, `confirm.isLoading()`, `confirm.getPopup()`, `confirm.close()`
- `confirm.update(options)` — live-update title, text, and button labels on an open dialog
- 15 input types: `inputValidator`, `inputOptions`, `inputAttributes`
- `preConfirm` + `showLoaderOnConfirm` — async confirm with loading state
- `preDeny` — async deny hook, abort with `return false`
- `timer` + `timerProgressBar` — auto-close with draining progress bar
- Multi-step dialogs: `progressSteps` + `currentProgressStep`
- Lifecycle hooks: `willOpen`, `didOpen`, `willClose`, `didClose`
- `DismissReason` constant for type-safe dismiss comparisons
- Variants: `default`, `filled`, `outlined`

#### Icons

- 32 built-in SVG icons across 7 categories: status, UI controls, actions, navigation, feedback, identity, misc
- `registerIcon(name, svg: string | Element)` — register custom icons by name; accepts a raw SVG string or any DOM `Element` (e.g. from Lucide, Iconify)
- `resolveIconHtml(icon)` — normalises `string | Element | null` to an HTML string for `innerHTML`
- `notification.icons` — live `Record<string, string>` registry of all built-ins + custom icons
- `iconHtml` option accepts `string | Element | null` in both `NotifyOptions` and `FireOptions`
- `IconType` includes all built-in names with full autocomplete plus `(string & {})` for custom names

#### Build & TypeScript

- Full TypeScript source — zero `any` types, strict mode
- ESM-only output (`dist/`) — no CommonJS bundle
- Individual compiled files, no bundle merging: `confirm.js`, `toast.js`, `icons.js`, `types.js`, `index.js`
- Individual `.d.ts` declarations per module
- No `@types` package required