@obinexusltd/obix-component-toast 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OBINexus Computing — Nnamdi Michael Okpala
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,44 @@
1
+ # @obinexusltd/obix-component-toast
2
+
3
+ **The `ObixToast` feedback component** — a transient notification with `role="status"`, six screen positions, an auto-dismiss `duration`, and `pause-on-hover`.
4
+
5
+ Split out of `@obinexusltd/obix-component-feedback` as an independent package.
6
+
7
+ ```bash
8
+ npm install @obinexusltd/obix-component-toast
9
+ ```
10
+
11
+ > **Zero dependencies.** Data-Oriented: `createX(config)` returns
12
+ > `{ name, state, actions, render }`. Actions are pure
13
+ > `(state, …args) => newState`; `render(state)` is deterministic, HTML-escaped
14
+ > markup (an empty string while hidden). `renderX(config, overrides?)` renders
15
+ > in one call.
16
+
17
+ ## API
18
+
19
+ ```ts
20
+ import { createToast, renderToast } from "@obinexusltd/obix-component-toast";
21
+
22
+ const el = createToast({ /* see docs/02-usage.md */ });
23
+ el.render(el.state);
24
+ renderToast({ /* config */ }); // one call
25
+ ```
26
+
27
+ **Actions** — `show(state)` · `hide(state)` · `dismiss(state)` · `setMessage(state, message)` · `setDuration(state, ms)`
28
+
29
+ ## Documentation
30
+
31
+ | # | Guide |
32
+ |---|-------|
33
+ | 01 | [Overview](docs/01-overview.md) |
34
+ | 02 | [Usage & API](docs/02-usage.md) |
35
+ | 03 | [Accessibility](docs/03-accessibility.md) |
36
+ | 04 | [State & rendered HTML](docs/04-state-and-html.md) |
37
+
38
+ ## Related
39
+
40
+ Part of the OBIX feedback set: `@obinexusltd/obix-component-``toast` · `alert` · `progress` · `loading`.
41
+
42
+ ## License
43
+
44
+ MIT — OBINexus Computing
@@ -0,0 +1,5 @@
1
+ import type { DOPComponent, ToastConfig, ToastState } from "./types.js";
2
+ export type { Action, DOPComponent, ToastConfig, ToastPosition, ToastState, ToastType, } from "./types.js";
3
+ export declare function createToast(config: ToastConfig): DOPComponent<ToastState>;
4
+ export declare function renderToast(config: ToastConfig, overrides?: Partial<ToastState>): string;
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExE,YAAY,EACV,MAAM,EACN,YAAY,EACZ,WAAW,EACX,aAAa,EACb,UAAU,EACV,SAAS,GACV,MAAM,YAAY,CAAC;AAuBpB,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,YAAY,CAAC,UAAU,CAAC,CAwBzE;AAGD,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,EAAE,SAAS,GAAE,OAAO,CAAC,UAAU,CAAM,GAAG,MAAM,CAG5F"}
package/dist/index.js ADDED
@@ -0,0 +1,45 @@
1
+ function esc(value) {
2
+ return String(value ?? "")
3
+ .replace(/&/g, "&amp;")
4
+ .replace(/</g, "&lt;")
5
+ .replace(/>/g, "&gt;")
6
+ .replace(/"/g, "&quot;");
7
+ }
8
+ function renderToastHtml(state) {
9
+ if (!state.visible)
10
+ return "";
11
+ return (`<div id="${state.toastId}"` +
12
+ ` class="obix-toast obix-toast--${state.type} obix-toast--${state.position}"` +
13
+ ` role="status" aria-live="polite" aria-atomic="true"` +
14
+ ` data-jfix-strategy="box-shadow" data-pause-on-hover="${state.pauseOnHover}">` +
15
+ `<span class="obix-toast__message">${esc(state.message)}</span>` +
16
+ `<button class="obix-toast__dismiss obix-button" type="button" aria-label="Dismiss notification">×</button>` +
17
+ `</div>`);
18
+ }
19
+ export function createToast(config) {
20
+ if (!config || typeof config.message !== "string") {
21
+ throw new TypeError("[obix-component-toast] createToast: `message` is required");
22
+ }
23
+ const state = {
24
+ message: config.message,
25
+ type: config.type ?? "info",
26
+ visible: true,
27
+ duration: config.duration ?? 5000,
28
+ position: config.position ?? "bottom-right",
29
+ toastId: config.id ?? "obix-toast",
30
+ pauseOnHover: config.pauseOnHover ?? true,
31
+ };
32
+ const actions = {
33
+ show: (s) => ({ ...s, visible: true }),
34
+ hide: (s) => ({ ...s, visible: false }),
35
+ dismiss: (s) => ({ ...s, visible: false }),
36
+ setMessage: (s, message) => ({ ...s, message: String(message) }),
37
+ setDuration: (s, duration) => ({ ...s, duration: Number(duration) }),
38
+ };
39
+ return { name: "ObixToast", state, actions, render: renderToastHtml };
40
+ }
41
+ export function renderToast(config, overrides = {}) {
42
+ const toast = createToast(config);
43
+ return toast.render({ ...toast.state, ...overrides });
44
+ }
45
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAwBA,SAAS,GAAG,CAAC,KAAc;IACzB,OAAO,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC7B,CAAC;AAED,SAAS,eAAe,CAAC,KAAiB;IACxC,IAAI,CAAC,KAAK,CAAC,OAAO;QAAE,OAAO,EAAE,CAAC;IAC9B,OAAO,CACL,YAAY,KAAK,CAAC,OAAO,GAAG;QAC5B,kCAAkC,KAAK,CAAC,IAAI,gBAAgB,KAAK,CAAC,QAAQ,GAAG;QAC7E,sDAAsD;QACtD,yDAAyD,KAAK,CAAC,YAAY,IAAI;QAC/E,qCAAqC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS;QAChE,4GAA4G;QAC5G,QAAQ,CACT,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,MAAmB;IAC7C,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QAClD,MAAM,IAAI,SAAS,CAAC,2DAA2D,CAAC,CAAC;IACnF,CAAC;IAED,MAAM,KAAK,GAAe;QACxB,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,MAAM;QAC3B,OAAO,EAAE,IAAI;QACb,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,IAAI;QACjC,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,cAAc;QAC3C,OAAO,EAAE,MAAM,CAAC,EAAE,IAAI,YAAY;QAClC,YAAY,EAAE,MAAM,CAAC,YAAY,IAAI,IAAI;KAC1C,CAAC;IAEF,MAAM,OAAO,GAAG;QACd,IAAI,EAAE,CAAC,CAAa,EAAc,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC9D,IAAI,EAAE,CAAC,CAAa,EAAc,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QAC/D,OAAO,EAAE,CAAC,CAAa,EAAc,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QAClE,UAAU,EAAE,CAAC,CAAa,EAAE,OAAgB,EAAc,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;QACjG,WAAW,EAAE,CAAC,CAAa,EAAE,QAAiB,EAAc,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;KACtG,CAAC;IAEF,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;AACxE,CAAC;AAGD,MAAM,UAAU,WAAW,CAAC,MAAmB,EAAE,YAAiC,EAAE;IAClF,MAAM,KAAK,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;IAClC,OAAO,KAAK,CAAC,MAAM,CAAC,EAAE,GAAG,KAAK,CAAC,KAAK,EAAE,GAAG,SAAS,EAAE,CAAC,CAAC;AACxD,CAAC"}
@@ -0,0 +1,27 @@
1
+ export type Action<S> = (state: S, ...args: any[]) => S;
2
+ export interface DOPComponent<S> {
3
+ name: string;
4
+ state: S;
5
+ actions: Record<string, Action<S>>;
6
+ render: (state: S) => string;
7
+ }
8
+ export type ToastType = "info" | "warning" | "error" | "success";
9
+ export type ToastPosition = "top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right";
10
+ export interface ToastConfig {
11
+ message: string;
12
+ type?: ToastType;
13
+ duration?: number;
14
+ position?: ToastPosition;
15
+ pauseOnHover?: boolean;
16
+ id?: string;
17
+ }
18
+ export interface ToastState {
19
+ message: string;
20
+ type: ToastType;
21
+ visible: boolean;
22
+ duration: number;
23
+ position: ToastPosition;
24
+ toastId: string;
25
+ pauseOnHover: boolean;
26
+ }
27
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;AAExD,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,CAAC,CAAC;IACT,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,CAAC;CAC9B;AAED,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;AAEjE,MAAM,MAAM,aAAa,GACrB,UAAU,GAAG,YAAY,GAAG,WAAW,GACvC,aAAa,GAAG,eAAe,GAAG,cAAc,CAAC;AAErD,MAAM,WAAW,WAAW;IAE1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,SAAS,CAAC;IAEjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,aAAa,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,OAAO,CAAC;CACvB"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,26 @@
1
+ # ObixToast — Overview
2
+
3
+ `@obinexusltd/obix-component-toast` is one feedback component from the OBIX component set, packaged
4
+ on its own. It is **Data-Oriented** and has **zero runtime dependencies**.
5
+
6
+ ## The shape
7
+
8
+ ```ts
9
+ const toast = createToast(config);
10
+ toast.name // "ObixToast"
11
+ toast.state // the full component state (see docs/04)
12
+ toast.actions // pure (state, …args) => state transitions
13
+ toast.render // (state) => string — deterministic, HTML-escaped
14
+ ```
15
+
16
+ - **No classes, no lifecycle, no DOM.** Data plus pure functions. You own
17
+ the timers, event wiring, and where the HTML string goes.
18
+ - **Deterministic render.** `render(state)` returns the same markup for the
19
+ same state, and an **empty string** while the component is hidden.
20
+ - **Pure actions.** `actions.foo(state, …)` returns a *new* state.
21
+
22
+ ## When to use it
23
+
24
+ Use `@obinexusltd/obix-component-toast` when you want just this one piece of feedback UI without
25
+ pulling the whole feedback bundle.
26
+
@@ -0,0 +1,48 @@
1
+ # ObixToast — Usage & API
2
+
3
+ ```ts
4
+ import { createToast, renderToast } from "@obinexusltd/obix-component-toast";
5
+ ```
6
+
7
+ ## `createToast(config)`
8
+
9
+ Returns a `DOPComponent`: `{ name, state, actions, render }`. Throws
10
+ `TypeError` when a required field is missing or the wrong type.
11
+
12
+ | Field | Type | Default | Notes |
13
+ |-------|------|---------|-------|
14
+ | `message` | `string` | **required** | Notification text |
15
+ | `type` | `info \| warning \| error \| success` | `"info"` | Visual variant (`obix-toast--<type>`) |
16
+ | `duration` | `number` | `5000` | Auto-dismiss delay in ms — **you own the timer**; `duration` is carried in state |
17
+ | `position` | `top-left \| top-center \| top-right \| bottom-left \| bottom-center \| bottom-right` | `"bottom-right"` | Anchor class `obix-toast--<position>` |
18
+ | `pauseOnHover` | `boolean` | `true` | Emits `data-pause-on-hover` for your timer logic |
19
+ | `id` | `string` | `"obix-toast"` | DOM id — **set a unique value per toast** |
20
+
21
+ The full `Config` / `State` interfaces are in
22
+ [`src/types.ts`](../src/types.ts), which ships in the package.
23
+
24
+ ## `renderToast(config, overrides?)`
25
+
26
+ One-call render — `createToast(config).render({ ...state, ...overrides })`.
27
+ Pass `{ visible: true }` as an override to render a normally-hidden state for
28
+ snapshots / SSR:
29
+
30
+ ```ts
31
+ renderToast(config, { visible: true });
32
+ ```
33
+
34
+ ## Actions
35
+
36
+ `show(state)` · `hide(state)` · `dismiss(state)` · `setMessage(state, message)` · `setDuration(state, ms)`.
37
+
38
+ Every action is `(state, …args) => newState` and **pure**. Thread state through
39
+ them (directly or via your store) and re-render:
40
+
41
+ ```ts
42
+ const t = createToast({ message: "File saved", type: "success", position: "top-right" });
43
+ mount.innerHTML = t.render(t.state); // visible
44
+ setTimeout(() => { mount.innerHTML = t.render(t.actions.dismiss(t.state)); }, t.state.duration); // "" -> removed
45
+ ```
46
+
47
+ `render(state)` returns `""` whenever the component is not visible — assigning
48
+ it to `innerHTML` cleanly removes the element.
@@ -0,0 +1,17 @@
1
+ # ObixToast — Accessibility
2
+
3
+ The rendered HTML carries its ARIA inline — there is no separate a11y layer.
4
+
5
+ - Live-region semantics are built in: `role` / `aria-live` / `aria-atomic`
6
+ are set from state (e.g. assertive for errors, polite for status).
7
+ - All interpolated text (messages, labels) is HTML-escaped.
8
+ - Dismiss controls are real `<button type="button">` with an `aria-label`.
9
+
10
+ ## Your responsibilities
11
+
12
+ - Insert the rendered string into a container that already exists in the DOM
13
+ so the live region is announced when content changes.
14
+ - Drive timers yourself (e.g. call `dismiss` after `state.duration` ms).
15
+ - Manage focus: for a dismissible alert, return focus to the triggering
16
+ element (`returnFocusId` is carried in state for you to use).
17
+ - Render one instance per container; give toasts a unique `id`.
@@ -0,0 +1,22 @@
1
+ # ObixToast — State & rendered HTML
2
+
3
+ ## State
4
+
5
+ The full state object is defined by the `State` interface in
6
+ [`src/types.ts`](../src/types.ts), built from `config` by
7
+ `createToast`. Plain JSON-serialisable data.
8
+
9
+ ## Rendered HTML
10
+
11
+ `render(state)` returns a single element as a string, or **`""`** when the
12
+ component is not visible. Attribute order is fixed; only state-driven
13
+ attributes/branches change between renders.
14
+
15
+ Class names follow the `obix-*` convention
16
+ (`obix-toast`, `obix-toast--<modifier>`).
17
+ No CSS ships with the package.
18
+
19
+ ## Testing
20
+
21
+ `test/*.test.mjs` (Node’s built-in runner) asserts the rendered string and
22
+ action purity. Run `npm run build && npm test`.
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@obinexusltd/obix-component-toast",
3
+ "version": "0.1.0",
4
+ "description": "OBIX ObixToast — a transient notification with role=\"status\", six screen positions, an auto-dismiss duration, and pause-on-hover. Data-Oriented, zero dependencies.",
5
+ "license": "MIT",
6
+ "author": "OBINexus Computing — Nnamdi Michael Okpala <okpalan@protonmail.com>",
7
+ "type": "module",
8
+ "private": false,
9
+ "sideEffects": false,
10
+ "main": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js"
16
+ },
17
+ "./src": "./src/index.ts",
18
+ "./package.json": "./package.json"
19
+ },
20
+ "files": [
21
+ "src",
22
+ "dist",
23
+ "docs",
24
+ "test",
25
+ "README.md",
26
+ "LICENSE"
27
+ ],
28
+ "directories": {
29
+ "lib": "dist",
30
+ "doc": "docs",
31
+ "test": "test"
32
+ },
33
+ "scripts": {
34
+ "build": "tsc -p tsconfig.json",
35
+ "test": "node --test \"test/*.test.mjs\"",
36
+ "prepublishOnly": "npm run build"
37
+ },
38
+ "keywords": [
39
+ "obix",
40
+ "component",
41
+ "dop",
42
+ "accessibility",
43
+ "toast",
44
+ "notification",
45
+ "snackbar",
46
+ "feedback"
47
+ ],
48
+ "publishConfig": {
49
+ "access": "public"
50
+ },
51
+ "engines": {
52
+ "node": ">=20.11.0"
53
+ },
54
+ "repository": {
55
+ "type": "git",
56
+ "url": "git+https://github.com/obinexusmk2/obix-component-toast.git"
57
+ }
58
+ }
package/src/index.ts ADDED
@@ -0,0 +1,76 @@
1
+ /**
2
+ * @obinexusltd/obix-component-toast
3
+ *
4
+ * The ObixToast feedback component — a transient notification with
5
+ * `role="status"`, six screen positions, an auto-dismiss `duration` (you own the
6
+ * timer), and `pause-on-hover`.
7
+ *
8
+ * Data-Oriented: `createToast(config)` returns `{ name, state, actions, render }`.
9
+ * Actions are pure `(state, …args) => ToastState`; `render(state)` is
10
+ * deterministic, HTML-escaped markup, or `""` while hidden. Zero dependencies.
11
+ *
12
+ * Split out of `@obinexusltd/obix-component-feedback`.
13
+ */
14
+ import type { DOPComponent, ToastConfig, ToastState } from "./types.js";
15
+
16
+ export type {
17
+ Action,
18
+ DOPComponent,
19
+ ToastConfig,
20
+ ToastPosition,
21
+ ToastState,
22
+ ToastType,
23
+ } from "./types.js";
24
+
25
+ function esc(value: unknown): string {
26
+ return String(value ?? "")
27
+ .replace(/&/g, "&amp;")
28
+ .replace(/</g, "&lt;")
29
+ .replace(/>/g, "&gt;")
30
+ .replace(/"/g, "&quot;");
31
+ }
32
+
33
+ function renderToastHtml(state: ToastState): string {
34
+ if (!state.visible) return "";
35
+ return (
36
+ `<div id="${state.toastId}"` +
37
+ ` class="obix-toast obix-toast--${state.type} obix-toast--${state.position}"` +
38
+ ` role="status" aria-live="polite" aria-atomic="true"` +
39
+ ` data-jfix-strategy="box-shadow" data-pause-on-hover="${state.pauseOnHover}">` +
40
+ `<span class="obix-toast__message">${esc(state.message)}</span>` +
41
+ `<button class="obix-toast__dismiss obix-button" type="button" aria-label="Dismiss notification">×</button>` +
42
+ `</div>`
43
+ );
44
+ }
45
+
46
+ export function createToast(config: ToastConfig): DOPComponent<ToastState> {
47
+ if (!config || typeof config.message !== "string") {
48
+ throw new TypeError("[obix-component-toast] createToast: `message` is required");
49
+ }
50
+
51
+ const state: ToastState = {
52
+ message: config.message,
53
+ type: config.type ?? "info",
54
+ visible: true,
55
+ duration: config.duration ?? 5000,
56
+ position: config.position ?? "bottom-right",
57
+ toastId: config.id ?? "obix-toast",
58
+ pauseOnHover: config.pauseOnHover ?? true,
59
+ };
60
+
61
+ const actions = {
62
+ show: (s: ToastState): ToastState => ({ ...s, visible: true }),
63
+ hide: (s: ToastState): ToastState => ({ ...s, visible: false }),
64
+ dismiss: (s: ToastState): ToastState => ({ ...s, visible: false }),
65
+ setMessage: (s: ToastState, message: unknown): ToastState => ({ ...s, message: String(message) }),
66
+ setDuration: (s: ToastState, duration: unknown): ToastState => ({ ...s, duration: Number(duration) }),
67
+ };
68
+
69
+ return { name: "ObixToast", state, actions, render: renderToastHtml };
70
+ }
71
+
72
+ /** Render a toast's HTML in one call, optionally with state overrides. */
73
+ export function renderToast(config: ToastConfig, overrides: Partial<ToastState> = {}): string {
74
+ const toast = createToast(config);
75
+ return toast.render({ ...toast.state, ...overrides });
76
+ }
package/src/types.ts ADDED
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @obinexusltd/obix-component-toast — types.
3
+ * Zero-dependency Data-Oriented component: { name, state, actions, render }.
4
+ */
5
+
6
+ /* eslint-disable @typescript-eslint/no-explicit-any */
7
+
8
+ export type Action<S> = (state: S, ...args: any[]) => S;
9
+
10
+ export interface DOPComponent<S> {
11
+ name: string;
12
+ state: S;
13
+ actions: Record<string, Action<S>>;
14
+ render: (state: S) => string;
15
+ }
16
+
17
+ export type ToastType = "info" | "warning" | "error" | "success";
18
+
19
+ export type ToastPosition =
20
+ | "top-left" | "top-center" | "top-right"
21
+ | "bottom-left" | "bottom-center" | "bottom-right";
22
+
23
+ export interface ToastConfig {
24
+ /** Notification text. Required. */
25
+ message: string;
26
+ type?: ToastType;
27
+ /** Auto-dismiss delay in ms (you own the timer). Default `5000`. */
28
+ duration?: number;
29
+ position?: ToastPosition;
30
+ pauseOnHover?: boolean;
31
+ /** DOM id; a stable default is used when omitted — set a unique one per toast. */
32
+ id?: string;
33
+ }
34
+
35
+ export interface ToastState {
36
+ message: string;
37
+ type: ToastType;
38
+ visible: boolean;
39
+ duration: number;
40
+ position: ToastPosition;
41
+ toastId: string;
42
+ pauseOnHover: boolean;
43
+ }
@@ -0,0 +1,33 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { createToast, renderToast } from "../dist/index.js";
4
+
5
+ test("factory returns a DOP component shape", () => {
6
+ const el = createToast({ message: "Hello & <world>" });
7
+ assert.equal(el.name, "ObixToast");
8
+ assert.equal(typeof el.render, "function");
9
+ assert.equal(typeof el.actions, "object");
10
+ assert.equal(typeof el.render(el.state), "string");
11
+ });
12
+
13
+ test("required config is validated", () => {
14
+ assert.throws(() => createToast(), TypeError);
15
+ assert.throws(() => createToast({}), TypeError);
16
+ });
17
+
18
+ test("render is deterministic", () => {
19
+ assert.equal(renderToast({ message: "Hello & <world>" }), renderToast({ message: "Hello & <world>" }));
20
+ });
21
+
22
+ test("text is escaped", () => {
23
+ const html = renderToast({ message: "a & b <x>" });
24
+ assert.match(html, /a &amp; b &lt;x&gt;/);
25
+ assert.doesNotMatch(html, /<x>/);
26
+ });
27
+
28
+ test("actions are pure — input state is untouched", () => {
29
+ const el = createToast({ message: "Hello & <world>" });
30
+ const before = JSON.stringify(el.state);
31
+ for (const k of Object.keys(el.actions)) el.actions[k](el.state, 1);
32
+ assert.equal(JSON.stringify(el.state), before);
33
+ });