@tensor_1/react 2.27.0 → 2.28.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/README.md CHANGED
@@ -308,3 +308,26 @@ const toRoute = (p: UixLinkProps) => <NextLink {...p} />;
308
308
  - **`CollapsibleSection`** stays a JS-free `<details>` unless you pass `lazy`, `persistKey` or `openRequest`. `lazy` unmounts a closed body; `lazy="keep"` keeps it after the first open.
309
309
  - **`ConfirmDialog`** focuses the type-to-confirm field, else Cancel when `destructive`. `Popconfirm` is for low-risk, easily undone actions; use `ConfirmDialog` for anything hard to undo.
310
310
  - **`KbdCombo`** renders the non-Apple glyphs on the server and switches to ⌘/⇧/⌥ after mount, so hydration matches. Pass `platform` to pin it.
311
+
312
+ ### Imperative toasts (2.28.0)
313
+
314
+ `toast()` queues a toast from anywhere on the client (a mutation hook, a tRPC error handler); every mounted
315
+ `<Toaster />` renders the queue (UIx HAR-1363; replaces `sonner` in TENSOR and MOTUS).
316
+
317
+ ```tsx
318
+ <Toaster position="bottom-end" limit={3} /> // once, in the app shell
319
+
320
+ toast.success('Saved', { description: 'Change CHG-1042 updated.' });
321
+ toast.error('Could not save', { action: { label: 'Copy details', onClick: copy } }); // stays until dismissed
322
+ toast.warning('Export is taking longer than usual');
323
+ const id = toast.loading('Exporting…'); toast.update(id, { kind: 'success', message: 'Exported' });
324
+ toast.promise(save(), { loading: 'Saving…', success: 'Saved', error: (e) => `Failed: ${message(e)}` });
325
+ toast.undoable('Ticket closed', { onUndo: reopen, onCommit: close }); // TENSOR C2 useUndoableAction
326
+ toast.dismiss(id); // or toast.dismiss() for all
327
+ ```
328
+
329
+ - **Timing:** success, info and default toasts stay 5 s, warnings 8 s; errors and loading toasts stay until dismissed or updated. Pass `duration` (ms, or `null`) to override. Timers pause while the pointer or focus is inside the toaster and while the tab is hidden.
330
+ - **Stacking:** at most `limit` toasts show (default 3). The rest wait their turn. Reusing an `id` replaces a toast in place.
331
+ - **Accessibility:** errors are `role="alert"`. Everything else is announced through the toaster's polite live region, and a loading toast that settles is announced again. Esc closes the focused toast. Tone glyphs are decorative; the text carries the meaning.
332
+ - **Stores:** `toast` writes to a default store that `<Toaster />` reads. `createToastApi(createToastStore())` gives a separate queue (tests, embedded apps); pass its `store` to `<Toaster store>`. The store has no React dependency.
333
+ - `<Toaster>` still renders hand-managed `<Toast>` children first. `Toast` gains `tone="warning"` and an `action` slot.
@@ -1,7 +1,8 @@
1
1
  import * as react from 'react';
2
2
  import { HTMLAttributes, ReactNode } from 'react';
3
+ import { ToastStore } from '../toast-store.js';
3
4
 
4
- type ToastTone = 'success' | 'danger' | 'info';
5
+ type ToastTone = 'success' | 'danger' | 'info' | 'warning';
5
6
  interface ToastProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title'> {
6
7
  /** TENSOR RX-125 (UIX-04): translatable; English default. */
7
8
  dismissLabel?: string;
@@ -9,15 +10,26 @@ interface ToastProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title'> {
9
10
  message?: ReactNode;
10
11
  tone?: ToastTone;
11
12
  icon?: ReactNode;
13
+ /** A button (or link) after the text, e.g. "Undo" or "Copy details" (HAR-1363). */
14
+ action?: ReactNode;
12
15
  onClose?: () => void;
13
16
  leaving?: boolean;
14
17
  }
15
- declare function Toast({ title, message, tone, icon, onClose, leaving, dismissLabel: dismissLabelProp, className, ...props }: ToastProps): react.JSX.Element;
18
+ declare function Toast({ title, message, tone, icon, action, onClose, leaving, dismissLabel: dismissLabelProp, className, ...props }: ToastProps): react.JSX.Element;
16
19
  interface ToasterProps extends HTMLAttributes<HTMLDivElement> {
17
20
  /** TENSOR RX-125 (UIX-04): translatable; English default. */
18
21
  regionLabel?: string;
22
+ /**
23
+ * The queue to render (HAR-1363). Default: the store behind the exported `toast()`.
24
+ * Children still render first, so a hand-managed `<Toast>` keeps working.
25
+ */
26
+ store?: ToastStore;
27
+ /** At most this many store toasts on screen; later ones wait their turn. Default 3. */
28
+ limit?: number;
29
+ /** Corner of the viewport. Default `bottom-end`. */
30
+ position?: 'bottom-end' | 'bottom-start' | 'bottom-center' | 'top-end' | 'top-start' | 'top-center';
19
31
  children?: ReactNode;
20
32
  }
21
- declare function Toaster({ children, regionLabel: regionLabelProp, className, ...props }: ToasterProps): react.JSX.Element;
33
+ declare function Toaster({ children, regionLabel: regionLabelProp, store, limit, position, className, onMouseEnter, onMouseLeave, onFocus, onBlur, onKeyDown, ...props }: ToasterProps): react.JSX.Element;
22
34
 
23
35
  export { Toast, type ToastProps, type ToastTone, Toaster, type ToasterProps };
@@ -1,10 +1,11 @@
1
1
  "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
3
- import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
4
4
  import { cx } from "../cx.js";
5
5
  import { useUixLabels } from "../labels-context.js";
6
+ import { toast as defaultToast } from "../toast-store.js";
6
7
  const ToasterContext = createContext(null);
7
- function Toast({ title, message, tone, icon, onClose, leaving, dismissLabel: dismissLabelProp, className, ...props }) {
8
+ function Toast({ title, message, tone, icon, action, onClose, leaving, dismissLabel: dismissLabelProp, className, ...props }) {
8
9
  const uixLabels = useUixLabels();
9
10
  const dismissLabel = dismissLabelProp ?? uixLabels.toast?.dismiss ?? "Dismiss";
10
11
  const assertive = tone === "danger";
@@ -42,12 +43,106 @@ function Toast({ title, message, tone, icon, onClose, leaving, dismissLabel: dis
42
43
  title && /* @__PURE__ */ jsx("div", { className: "uix-toast__title", children: title }),
43
44
  message && /* @__PURE__ */ jsx("div", { className: "uix-toast__msg", children: message })
44
45
  ] }),
45
- onClose && /* @__PURE__ */ jsx("button", { className: "uix-toast__close", onClick: handleClose, "aria-label": dismissLabel, children: "\xD7" })
46
+ action != null && /* @__PURE__ */ jsx("div", { className: "uix-toast__action-slot", children: action }),
47
+ onClose && /* @__PURE__ */ jsx("button", { type: "button", className: "uix-toast__close", onClick: handleClose, "aria-label": dismissLabel, children: "\xD7" })
46
48
  ]
47
49
  }
48
50
  );
49
51
  }
50
- function Toaster({ children, regionLabel: regionLabelProp, className, ...props }) {
52
+ const KIND_TONE = {
53
+ success: "success",
54
+ error: "danger",
55
+ warning: "warning",
56
+ info: "info",
57
+ loading: "info",
58
+ default: void 0
59
+ };
60
+ const svg = (path) => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: 1.8, strokeLinecap: "round", strokeLinejoin: "round", children: path });
61
+ const KIND_GLYPH = {
62
+ success: svg(/* @__PURE__ */ jsxs(Fragment, { children: [
63
+ /* @__PURE__ */ jsx("circle", { cx: "10", cy: "10", r: "7.25" }),
64
+ /* @__PURE__ */ jsx("path", { d: "M6.75 10.25l2.25 2.25 4.25-4.75" })
65
+ ] })),
66
+ error: svg(/* @__PURE__ */ jsxs(Fragment, { children: [
67
+ /* @__PURE__ */ jsx("circle", { cx: "10", cy: "10", r: "7.25" }),
68
+ /* @__PURE__ */ jsx("path", { d: "M10 6.25v4.5M10 13.5v.25" })
69
+ ] })),
70
+ warning: svg(/* @__PURE__ */ jsxs(Fragment, { children: [
71
+ /* @__PURE__ */ jsx("path", { d: "M10 3.25l7.25 13H2.75z" }),
72
+ /* @__PURE__ */ jsx("path", { d: "M10 8.25v3.5M10 14v.25" })
73
+ ] })),
74
+ info: svg(/* @__PURE__ */ jsxs(Fragment, { children: [
75
+ /* @__PURE__ */ jsx("circle", { cx: "10", cy: "10", r: "7.25" }),
76
+ /* @__PURE__ */ jsx("path", { d: "M10 9.25v4.5M10 6.5v.25" })
77
+ ] })),
78
+ loading: /* @__PURE__ */ jsx("span", { className: "uix-spinner uix-toast__spinner" })
79
+ };
80
+ function StoreToast({ record, store }) {
81
+ const action = record.action && /* @__PURE__ */ jsx(
82
+ "button",
83
+ {
84
+ type: "button",
85
+ className: "uix-toast__action",
86
+ onClick: () => {
87
+ record.action.onClick();
88
+ store.dismiss(record.id, "action");
89
+ },
90
+ children: record.action.label
91
+ }
92
+ );
93
+ return /* @__PURE__ */ jsx(
94
+ Toast,
95
+ {
96
+ "data-toast-id": record.id,
97
+ tone: KIND_TONE[record.kind],
98
+ icon: record.icon ?? KIND_GLYPH[record.kind],
99
+ title: record.message,
100
+ message: record.description,
101
+ action,
102
+ leaving: record.leaving,
103
+ "aria-busy": record.kind === "loading" || void 0,
104
+ onClose: () => store.dismiss(record.id, "close")
105
+ }
106
+ );
107
+ }
108
+ function useToastTimers(store, visible, paused) {
109
+ const timers = useRef(/* @__PURE__ */ new Map());
110
+ useEffect(() => {
111
+ const now = Date.now();
112
+ const map = timers.current;
113
+ const live = new Map(visible.filter((r) => !r.leaving && r.duration != null).map((r) => [r.id, r]));
114
+ for (const [id, timer] of map) {
115
+ const record = live.get(id);
116
+ if (!record || record.version !== timer.version) {
117
+ if (timer.handle != null) clearTimeout(timer.handle);
118
+ map.delete(id);
119
+ }
120
+ }
121
+ for (const record of live.values()) {
122
+ let timer = map.get(record.id);
123
+ if (!timer) {
124
+ timer = { version: record.version, remaining: record.duration, start: now };
125
+ map.set(record.id, timer);
126
+ }
127
+ if (paused) {
128
+ if (timer.handle != null) {
129
+ clearTimeout(timer.handle);
130
+ timer.handle = void 0;
131
+ timer.remaining -= now - timer.start;
132
+ }
133
+ } else if (timer.handle == null) {
134
+ timer.start = now;
135
+ const id = record.id;
136
+ timer.handle = window.setTimeout(() => store.dismiss(id, "timeout"), Math.max(0, timer.remaining));
137
+ }
138
+ }
139
+ });
140
+ useEffect(() => () => {
141
+ for (const timer of timers.current.values()) if (timer.handle != null) clearTimeout(timer.handle);
142
+ timers.current.clear();
143
+ }, []);
144
+ }
145
+ function Toaster({ children, regionLabel: regionLabelProp, store = defaultToast.store, limit = 3, position = "bottom-end", className, onMouseEnter, onMouseLeave, onFocus, onBlur, onKeyDown, ...props }) {
51
146
  const uixLabels = useUixLabels();
52
147
  const regionLabel = regionLabelProp ?? uixLabels.toaster?.region ?? "Notifications";
53
148
  const [announced, setAnnounced] = useState("");
@@ -61,10 +156,59 @@ function Toaster({ children, regionLabel: regionLabelProp, className, ...props }
61
156
  if (clearTimer.current != null) clearTimeout(clearTimer.current);
62
157
  }, []);
63
158
  const ctx = useMemo(() => ({ announce }), [announce]);
64
- return /* @__PURE__ */ jsx(ToasterContext.Provider, { value: ctx, children: /* @__PURE__ */ jsxs("div", { className: cx("uix-toaster", className), role: "region", "aria-label": regionLabel, ...props, children: [
65
- /* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", role: "status", "aria-live": "polite", children: announced }),
66
- children
67
- ] }) });
159
+ const records = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
160
+ const visible = useMemo(() => records.slice(-Math.max(1, limit)), [records, limit]);
161
+ const [hovered, setHovered] = useState(false);
162
+ const [focused, setFocused] = useState(false);
163
+ const [hidden, setHidden] = useState(false);
164
+ useEffect(() => {
165
+ const sync = () => setHidden(document.visibilityState === "hidden");
166
+ sync();
167
+ document.addEventListener("visibilitychange", sync);
168
+ return () => document.removeEventListener("visibilitychange", sync);
169
+ }, []);
170
+ useToastTimers(store, visible, hovered || focused || hidden);
171
+ const handleKeyDown = (event) => {
172
+ onKeyDown?.(event);
173
+ if (event.defaultPrevented || event.key !== "Escape") return;
174
+ const id = event.target.closest("[data-toast-id]")?.dataset.toastId;
175
+ if (!id) return;
176
+ event.preventDefault();
177
+ event.stopPropagation();
178
+ store.dismiss(id, "close");
179
+ };
180
+ return /* @__PURE__ */ jsx(ToasterContext.Provider, { value: ctx, children: /* @__PURE__ */ jsxs(
181
+ "div",
182
+ {
183
+ className: cx("uix-toaster", className),
184
+ role: "region",
185
+ "aria-label": regionLabel,
186
+ "data-position": position === "bottom-end" ? void 0 : position,
187
+ onMouseEnter: (e) => {
188
+ setHovered(true);
189
+ onMouseEnter?.(e);
190
+ },
191
+ onMouseLeave: (e) => {
192
+ setHovered(false);
193
+ onMouseLeave?.(e);
194
+ },
195
+ onFocus: (e) => {
196
+ setFocused(true);
197
+ onFocus?.(e);
198
+ },
199
+ onBlur: (e) => {
200
+ if (!e.currentTarget.contains(e.relatedTarget)) setFocused(false);
201
+ onBlur?.(e);
202
+ },
203
+ onKeyDown: handleKeyDown,
204
+ ...props,
205
+ children: [
206
+ /* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", role: "status", "aria-live": "polite", children: announced }),
207
+ children,
208
+ visible.map((record) => /* @__PURE__ */ jsx(StoreToast, { record, store }, `${record.id}:${record.kind}`))
209
+ ]
210
+ }
211
+ ) });
68
212
  }
69
213
  export {
70
214
  Toast,
package/dist/index.cjs CHANGED
@@ -161,6 +161,7 @@ __export(src_exports, {
161
161
  Steps: () => Steps,
162
162
  SubNavItem: () => SubNavItem,
163
163
  Switch: () => Switch,
164
+ TOAST_LEAVE_MS: () => TOAST_LEAVE_MS,
164
165
  Tab: () => Tab,
165
166
  TabPanel: () => TabPanel,
166
167
  Table: () => Table,
@@ -194,6 +195,8 @@ __export(src_exports, {
194
195
  clusterIdFor: () => clusterIdFor,
195
196
  computePosition: () => computePosition,
196
197
  contrastRatio: () => contrastRatio,
198
+ createToastApi: () => createToastApi,
199
+ createToastStore: () => createToastStore,
197
200
  cx: () => cx,
198
201
  enumerateDateSpan: () => enumerateDateSpan,
199
202
  filterComboboxOptions: () => filterComboboxOptions,
@@ -241,6 +244,7 @@ __export(src_exports, {
241
244
  summarizeDiff: () => summarizeDiff,
242
245
  summarizeRule: () => summarizeRule,
243
246
  toDateKey: () => toDateKey,
247
+ toast: () => toast,
244
248
  toggleId: () => toggleId,
245
249
  togglePage: () => togglePage,
246
250
  toggleSort: () => toggleSort,
@@ -3283,9 +3287,117 @@ function Alert({ tone, title, icon, children, className, ...props }) {
3283
3287
 
3284
3288
  // src/components/Toast.tsx
3285
3289
  var import_react31 = require("react");
3290
+
3291
+ // src/toast-store.ts
3292
+ var DEFAULT_DURATION = {
3293
+ success: 5e3,
3294
+ info: 5e3,
3295
+ default: 5e3,
3296
+ warning: 8e3,
3297
+ error: null,
3298
+ loading: null
3299
+ };
3300
+ var TOAST_LEAVE_MS = 200;
3301
+ var counter = 0;
3302
+ function createToastStore() {
3303
+ let records = [];
3304
+ const listeners = /* @__PURE__ */ new Set();
3305
+ const emit = () => {
3306
+ for (const listener of listeners) listener();
3307
+ };
3308
+ const set = (next) => {
3309
+ records = next;
3310
+ emit();
3311
+ };
3312
+ const show = (kind, message, options = {}) => {
3313
+ const id = options.id ?? `uix-toast-${++counter}`;
3314
+ const record = {
3315
+ id,
3316
+ kind,
3317
+ message,
3318
+ description: options.description,
3319
+ duration: options.duration === void 0 ? DEFAULT_DURATION[kind] : options.duration,
3320
+ action: options.action,
3321
+ icon: options.icon,
3322
+ onDismiss: options.onDismiss,
3323
+ version: 0
3324
+ };
3325
+ const existing = records.find((r) => r.id === id);
3326
+ set(existing ? records.map((r) => r.id === id ? { ...record, version: r.version + 1 } : r) : [...records, record]);
3327
+ return id;
3328
+ };
3329
+ const update = (id, patch) => {
3330
+ if (!records.some((r) => r.id === id)) return;
3331
+ set(records.map((r) => {
3332
+ if (r.id !== id) return r;
3333
+ const kind = patch.kind ?? r.kind;
3334
+ const duration = "duration" in patch ? patch.duration ?? null : patch.kind ? DEFAULT_DURATION[kind] : r.duration;
3335
+ return { ...r, ...patch, kind, duration, leaving: false, version: r.version + 1 };
3336
+ }));
3337
+ };
3338
+ const dismiss = (id, reason = "programmatic") => {
3339
+ const closing = records.filter((r) => (id === void 0 || r.id === id) && !r.leaving);
3340
+ if (closing.length === 0) return;
3341
+ const ids = new Set(closing.map((r) => r.id));
3342
+ set(records.map((r) => ids.has(r.id) ? { ...r, leaving: true } : r));
3343
+ for (const r of closing) r.onDismiss?.(reason);
3344
+ const remove = () => set(records.filter((r) => !(ids.has(r.id) && r.leaving)));
3345
+ if (typeof setTimeout === "function") setTimeout(remove, TOAST_LEAVE_MS);
3346
+ else remove();
3347
+ };
3348
+ return {
3349
+ show,
3350
+ update,
3351
+ dismiss,
3352
+ subscribe: (listener) => {
3353
+ listeners.add(listener);
3354
+ return () => {
3355
+ listeners.delete(listener);
3356
+ };
3357
+ },
3358
+ getSnapshot: () => records
3359
+ };
3360
+ }
3361
+ function createToastApi(store) {
3362
+ const api = ((message, options) => store.show("default", message, options));
3363
+ api.success = (message, options) => store.show("success", message, options);
3364
+ api.error = (message, options) => store.show("error", message, options);
3365
+ api.warning = (message, options) => store.show("warning", message, options);
3366
+ api.info = (message, options) => store.show("info", message, options);
3367
+ api.loading = (message, options) => store.show("loading", message, options);
3368
+ api.dismiss = (id) => store.dismiss(id, "programmatic");
3369
+ api.update = (id, patch) => store.update(id, patch);
3370
+ api.promise = (promise, messages, options) => {
3371
+ const id = store.show("loading", messages.loading, options);
3372
+ promise.then(
3373
+ (value) => store.update(id, { kind: "success", message: typeof messages.success === "function" ? messages.success(value) : messages.success }),
3374
+ (error) => store.update(id, { kind: "error", message: typeof messages.error === "function" ? messages.error(error) : messages.error })
3375
+ );
3376
+ return promise;
3377
+ };
3378
+ api.undoable = (message, { onUndo, onCommit, undoLabel = "Undo", duration = 6e3, ...options }) => {
3379
+ let undone = false;
3380
+ return store.show("default", message, {
3381
+ ...options,
3382
+ duration,
3383
+ action: { label: undoLabel, onClick: () => {
3384
+ undone = true;
3385
+ onUndo();
3386
+ } },
3387
+ onDismiss: () => {
3388
+ if (!undone) onCommit();
3389
+ }
3390
+ });
3391
+ };
3392
+ api.store = store;
3393
+ return api;
3394
+ }
3395
+ var toast = createToastApi(createToastStore());
3396
+
3397
+ // src/components/Toast.tsx
3286
3398
  var import_jsx_runtime38 = require("react/jsx-runtime");
3287
3399
  var ToasterContext = (0, import_react31.createContext)(null);
3288
- function Toast({ title, message, tone, icon, onClose, leaving, dismissLabel: dismissLabelProp, className, ...props }) {
3400
+ function Toast({ title, message, tone, icon, action, onClose, leaving, dismissLabel: dismissLabelProp, className, ...props }) {
3289
3401
  const uixLabels = useUixLabels();
3290
3402
  const dismissLabel = dismissLabelProp ?? uixLabels.toast?.dismiss ?? "Dismiss";
3291
3403
  const assertive = tone === "danger";
@@ -3323,12 +3435,106 @@ function Toast({ title, message, tone, icon, onClose, leaving, dismissLabel: dis
3323
3435
  title && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "uix-toast__title", children: title }),
3324
3436
  message && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "uix-toast__msg", children: message })
3325
3437
  ] }),
3326
- onClose && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("button", { className: "uix-toast__close", onClick: handleClose, "aria-label": dismissLabel, children: "\xD7" })
3438
+ action != null && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "uix-toast__action-slot", children: action }),
3439
+ onClose && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("button", { type: "button", className: "uix-toast__close", onClick: handleClose, "aria-label": dismissLabel, children: "\xD7" })
3327
3440
  ]
3328
3441
  }
3329
3442
  );
3330
3443
  }
3331
- function Toaster({ children, regionLabel: regionLabelProp, className, ...props }) {
3444
+ var KIND_TONE = {
3445
+ success: "success",
3446
+ error: "danger",
3447
+ warning: "warning",
3448
+ info: "info",
3449
+ loading: "info",
3450
+ default: void 0
3451
+ };
3452
+ var svg = (path) => /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("svg", { viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: 1.8, strokeLinecap: "round", strokeLinejoin: "round", children: path });
3453
+ var KIND_GLYPH = {
3454
+ success: svg(/* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(import_jsx_runtime38.Fragment, { children: [
3455
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("circle", { cx: "10", cy: "10", r: "7.25" }),
3456
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("path", { d: "M6.75 10.25l2.25 2.25 4.25-4.75" })
3457
+ ] })),
3458
+ error: svg(/* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(import_jsx_runtime38.Fragment, { children: [
3459
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("circle", { cx: "10", cy: "10", r: "7.25" }),
3460
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("path", { d: "M10 6.25v4.5M10 13.5v.25" })
3461
+ ] })),
3462
+ warning: svg(/* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(import_jsx_runtime38.Fragment, { children: [
3463
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("path", { d: "M10 3.25l7.25 13H2.75z" }),
3464
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("path", { d: "M10 8.25v3.5M10 14v.25" })
3465
+ ] })),
3466
+ info: svg(/* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(import_jsx_runtime38.Fragment, { children: [
3467
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("circle", { cx: "10", cy: "10", r: "7.25" }),
3468
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("path", { d: "M10 9.25v4.5M10 6.5v.25" })
3469
+ ] })),
3470
+ loading: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "uix-spinner uix-toast__spinner" })
3471
+ };
3472
+ function StoreToast({ record, store }) {
3473
+ const action = record.action && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
3474
+ "button",
3475
+ {
3476
+ type: "button",
3477
+ className: "uix-toast__action",
3478
+ onClick: () => {
3479
+ record.action.onClick();
3480
+ store.dismiss(record.id, "action");
3481
+ },
3482
+ children: record.action.label
3483
+ }
3484
+ );
3485
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
3486
+ Toast,
3487
+ {
3488
+ "data-toast-id": record.id,
3489
+ tone: KIND_TONE[record.kind],
3490
+ icon: record.icon ?? KIND_GLYPH[record.kind],
3491
+ title: record.message,
3492
+ message: record.description,
3493
+ action,
3494
+ leaving: record.leaving,
3495
+ "aria-busy": record.kind === "loading" || void 0,
3496
+ onClose: () => store.dismiss(record.id, "close")
3497
+ }
3498
+ );
3499
+ }
3500
+ function useToastTimers(store, visible, paused) {
3501
+ const timers = (0, import_react31.useRef)(/* @__PURE__ */ new Map());
3502
+ (0, import_react31.useEffect)(() => {
3503
+ const now = Date.now();
3504
+ const map = timers.current;
3505
+ const live = new Map(visible.filter((r) => !r.leaving && r.duration != null).map((r) => [r.id, r]));
3506
+ for (const [id, timer] of map) {
3507
+ const record = live.get(id);
3508
+ if (!record || record.version !== timer.version) {
3509
+ if (timer.handle != null) clearTimeout(timer.handle);
3510
+ map.delete(id);
3511
+ }
3512
+ }
3513
+ for (const record of live.values()) {
3514
+ let timer = map.get(record.id);
3515
+ if (!timer) {
3516
+ timer = { version: record.version, remaining: record.duration, start: now };
3517
+ map.set(record.id, timer);
3518
+ }
3519
+ if (paused) {
3520
+ if (timer.handle != null) {
3521
+ clearTimeout(timer.handle);
3522
+ timer.handle = void 0;
3523
+ timer.remaining -= now - timer.start;
3524
+ }
3525
+ } else if (timer.handle == null) {
3526
+ timer.start = now;
3527
+ const id = record.id;
3528
+ timer.handle = window.setTimeout(() => store.dismiss(id, "timeout"), Math.max(0, timer.remaining));
3529
+ }
3530
+ }
3531
+ });
3532
+ (0, import_react31.useEffect)(() => () => {
3533
+ for (const timer of timers.current.values()) if (timer.handle != null) clearTimeout(timer.handle);
3534
+ timers.current.clear();
3535
+ }, []);
3536
+ }
3537
+ function Toaster({ children, regionLabel: regionLabelProp, store = toast.store, limit = 3, position = "bottom-end", className, onMouseEnter, onMouseLeave, onFocus, onBlur, onKeyDown, ...props }) {
3332
3538
  const uixLabels = useUixLabels();
3333
3539
  const regionLabel = regionLabelProp ?? uixLabels.toaster?.region ?? "Notifications";
3334
3540
  const [announced, setAnnounced] = (0, import_react31.useState)("");
@@ -3342,10 +3548,59 @@ function Toaster({ children, regionLabel: regionLabelProp, className, ...props }
3342
3548
  if (clearTimer.current != null) clearTimeout(clearTimer.current);
3343
3549
  }, []);
3344
3550
  const ctx = (0, import_react31.useMemo)(() => ({ announce }), [announce]);
3345
- return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(ToasterContext.Provider, { value: ctx, children: /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: cx("uix-toaster", className), role: "region", "aria-label": regionLabel, ...props, children: [
3346
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "uix-visually-hidden", role: "status", "aria-live": "polite", children: announced }),
3347
- children
3348
- ] }) });
3551
+ const records = (0, import_react31.useSyncExternalStore)(store.subscribe, store.getSnapshot, store.getSnapshot);
3552
+ const visible = (0, import_react31.useMemo)(() => records.slice(-Math.max(1, limit)), [records, limit]);
3553
+ const [hovered, setHovered] = (0, import_react31.useState)(false);
3554
+ const [focused, setFocused] = (0, import_react31.useState)(false);
3555
+ const [hidden, setHidden] = (0, import_react31.useState)(false);
3556
+ (0, import_react31.useEffect)(() => {
3557
+ const sync = () => setHidden(document.visibilityState === "hidden");
3558
+ sync();
3559
+ document.addEventListener("visibilitychange", sync);
3560
+ return () => document.removeEventListener("visibilitychange", sync);
3561
+ }, []);
3562
+ useToastTimers(store, visible, hovered || focused || hidden);
3563
+ const handleKeyDown = (event) => {
3564
+ onKeyDown?.(event);
3565
+ if (event.defaultPrevented || event.key !== "Escape") return;
3566
+ const id = event.target.closest("[data-toast-id]")?.dataset.toastId;
3567
+ if (!id) return;
3568
+ event.preventDefault();
3569
+ event.stopPropagation();
3570
+ store.dismiss(id, "close");
3571
+ };
3572
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(ToasterContext.Provider, { value: ctx, children: /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
3573
+ "div",
3574
+ {
3575
+ className: cx("uix-toaster", className),
3576
+ role: "region",
3577
+ "aria-label": regionLabel,
3578
+ "data-position": position === "bottom-end" ? void 0 : position,
3579
+ onMouseEnter: (e) => {
3580
+ setHovered(true);
3581
+ onMouseEnter?.(e);
3582
+ },
3583
+ onMouseLeave: (e) => {
3584
+ setHovered(false);
3585
+ onMouseLeave?.(e);
3586
+ },
3587
+ onFocus: (e) => {
3588
+ setFocused(true);
3589
+ onFocus?.(e);
3590
+ },
3591
+ onBlur: (e) => {
3592
+ if (!e.currentTarget.contains(e.relatedTarget)) setFocused(false);
3593
+ onBlur?.(e);
3594
+ },
3595
+ onKeyDown: handleKeyDown,
3596
+ ...props,
3597
+ children: [
3598
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "uix-visually-hidden", role: "status", "aria-live": "polite", children: announced }),
3599
+ children,
3600
+ visible.map((record) => /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(StoreToast, { record, store }, `${record.id}:${record.kind}`))
3601
+ ]
3602
+ }
3603
+ ) });
3349
3604
  }
3350
3605
 
3351
3606
  // src/components/Table.tsx
@@ -8752,6 +9007,7 @@ function DiffViewer({
8752
9007
  Steps,
8753
9008
  SubNavItem,
8754
9009
  Switch,
9010
+ TOAST_LEAVE_MS,
8755
9011
  Tab,
8756
9012
  TabPanel,
8757
9013
  Table,
@@ -8785,6 +9041,8 @@ function DiffViewer({
8785
9041
  clusterIdFor,
8786
9042
  computePosition,
8787
9043
  contrastRatio,
9044
+ createToastApi,
9045
+ createToastStore,
8788
9046
  cx,
8789
9047
  enumerateDateSpan,
8790
9048
  filterComboboxOptions,
@@ -8832,6 +9090,7 @@ function DiffViewer({
8832
9090
  summarizeDiff,
8833
9091
  summarizeRule,
8834
9092
  toDateKey,
9093
+ toast,
8835
9094
  toggleId,
8836
9095
  togglePage,
8837
9096
  toggleSort,
package/dist/index.d.cts CHANGED
@@ -1344,7 +1344,102 @@ interface SpinnerProps extends HTMLAttributes<HTMLSpanElement> {
1344
1344
  }
1345
1345
  declare function Spinner({ size, accent, label, className, ...props }: SpinnerProps): react.JSX.Element;
1346
1346
 
1347
- type ToastTone = 'success' | 'danger' | 'info';
1347
+ /**
1348
+ * The imperative toast queue behind `toast()` (HAR-1363; TENSOR A2/C3, MOTUS A3/A4/C-13).
1349
+ * Plain module state, no React: mutation helpers and server-action wrappers can call
1350
+ * `toast.success(...)` from anywhere on the client; every mounted `Toaster` subscribed to
1351
+ * the store renders it. Timing (auto-dismiss, pause on hover/focus/hidden tab) belongs to the
1352
+ * `Toaster`, because it depends on what the user is looking at.
1353
+ */
1354
+ type ToastKind = 'success' | 'error' | 'warning' | 'info' | 'loading' | 'default';
1355
+ interface ToastAction {
1356
+ label: string;
1357
+ /** Runs, then the toast closes (reason `"action"`). */
1358
+ onClick: () => void;
1359
+ }
1360
+ /** Why a toast closed. */
1361
+ type ToastDismissReason = 'timeout' | 'close' | 'action' | 'programmatic';
1362
+ interface ToastOptions {
1363
+ /** Reuse an id to replace a toast in place instead of stacking a duplicate. */
1364
+ id?: string;
1365
+ /** A second, quieter line. */
1366
+ description?: ReactNode;
1367
+ /**
1368
+ * Milliseconds on screen while visible and not paused. `null` keeps it until dismissed.
1369
+ * Defaults: success/info/default 5 s, warning 8 s, error and loading stay.
1370
+ */
1371
+ duration?: number | null;
1372
+ action?: ToastAction;
1373
+ /** Replaces the tone glyph. */
1374
+ icon?: ReactNode;
1375
+ /** Called once when the toast closes, with the reason. */
1376
+ onDismiss?: (reason: ToastDismissReason) => void;
1377
+ }
1378
+ interface ToastRecord {
1379
+ id: string;
1380
+ kind: ToastKind;
1381
+ message: ReactNode;
1382
+ description?: ReactNode;
1383
+ duration: number | null;
1384
+ action?: ToastAction;
1385
+ icon?: ReactNode;
1386
+ /** Set while the leave animation runs; the record is removed after it. */
1387
+ leaving?: boolean;
1388
+ /** Bumped on every update, so a Toaster restarts the toast's timer. */
1389
+ version: number;
1390
+ onDismiss?: (reason: ToastDismissReason) => void;
1391
+ }
1392
+ interface ToastPromiseMessages<T> {
1393
+ loading: ReactNode;
1394
+ success: ReactNode | ((value: T) => ReactNode);
1395
+ error: ReactNode | ((error: unknown) => ReactNode);
1396
+ }
1397
+ interface UndoableToastOptions extends Omit<ToastOptions, 'action' | 'onDismiss'> {
1398
+ /** The user pressed Undo: revert the optimistic change. `onCommit` is not called. */
1399
+ onUndo: () => void;
1400
+ /** The toast closed without Undo (timeout, ×, or a later dismiss): make the change final. */
1401
+ onCommit: () => void;
1402
+ /** Default "Undo". */
1403
+ undoLabel?: string;
1404
+ }
1405
+ interface ToastStore {
1406
+ show: (kind: ToastKind, message: ReactNode, options?: ToastOptions) => string;
1407
+ update: (id: string, patch: Partial<Omit<ToastRecord, 'id' | 'version'>>) => void;
1408
+ dismiss: (id?: string, reason?: ToastDismissReason) => void;
1409
+ subscribe: (listener: () => void) => () => void;
1410
+ getSnapshot: () => readonly ToastRecord[];
1411
+ }
1412
+ /** How long the leave animation gets before the record is removed. */
1413
+ declare const TOAST_LEAVE_MS = 200;
1414
+ /** A separate queue, e.g. per test or per embedded app. Most products use the default `toast`. */
1415
+ declare function createToastStore(): ToastStore;
1416
+ interface ToastApi {
1417
+ (message: ReactNode, options?: ToastOptions): string;
1418
+ success: (message: ReactNode, options?: ToastOptions) => string;
1419
+ error: (message: ReactNode, options?: ToastOptions) => string;
1420
+ warning: (message: ReactNode, options?: ToastOptions) => string;
1421
+ info: (message: ReactNode, options?: ToastOptions) => string;
1422
+ loading: (message: ReactNode, options?: ToastOptions) => string;
1423
+ /** Close one toast, or every toast without an id. */
1424
+ dismiss: (id?: string) => void;
1425
+ /** Change a toast in place (message, kind, description, duration, action). */
1426
+ update: (id: string, patch: Partial<Omit<ToastRecord, 'id' | 'version'>>) => void;
1427
+ /** A loading toast that turns into success or error when the promise settles. Returns the promise. */
1428
+ promise: <T>(promise: Promise<T>, messages: ToastPromiseMessages<T>, options?: ToastOptions) => Promise<T>;
1429
+ /**
1430
+ * An optimistic action with an Undo button (TENSOR C2 `useUndoableAction`): `onCommit` runs
1431
+ * when the toast closes without Undo; `onUndo` runs instead when Undo is pressed.
1432
+ */
1433
+ undoable: (message: ReactNode, options: UndoableToastOptions) => string;
1434
+ /** The store this API writes to; pass it to `<Toaster store>` for a non-default queue. */
1435
+ store: ToastStore;
1436
+ }
1437
+ /** Binds the `toast()` API to a store. */
1438
+ declare function createToastApi(store: ToastStore): ToastApi;
1439
+ /** The default queue; a `<Toaster />` without a `store` prop renders it. */
1440
+ declare const toast: ToastApi;
1441
+
1442
+ type ToastTone = 'success' | 'danger' | 'info' | 'warning';
1348
1443
  interface ToastProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title'> {
1349
1444
  /** TENSOR RX-125 (UIX-04): translatable; English default. */
1350
1445
  dismissLabel?: string;
@@ -1352,16 +1447,27 @@ interface ToastProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title'> {
1352
1447
  message?: ReactNode;
1353
1448
  tone?: ToastTone;
1354
1449
  icon?: ReactNode;
1450
+ /** A button (or link) after the text, e.g. "Undo" or "Copy details" (HAR-1363). */
1451
+ action?: ReactNode;
1355
1452
  onClose?: () => void;
1356
1453
  leaving?: boolean;
1357
1454
  }
1358
- declare function Toast({ title, message, tone, icon, onClose, leaving, dismissLabel: dismissLabelProp, className, ...props }: ToastProps): react.JSX.Element;
1455
+ declare function Toast({ title, message, tone, icon, action, onClose, leaving, dismissLabel: dismissLabelProp, className, ...props }: ToastProps): react.JSX.Element;
1359
1456
  interface ToasterProps extends HTMLAttributes<HTMLDivElement> {
1360
1457
  /** TENSOR RX-125 (UIX-04): translatable; English default. */
1361
1458
  regionLabel?: string;
1459
+ /**
1460
+ * The queue to render (HAR-1363). Default: the store behind the exported `toast()`.
1461
+ * Children still render first, so a hand-managed `<Toast>` keeps working.
1462
+ */
1463
+ store?: ToastStore;
1464
+ /** At most this many store toasts on screen; later ones wait their turn. Default 3. */
1465
+ limit?: number;
1466
+ /** Corner of the viewport. Default `bottom-end`. */
1467
+ position?: 'bottom-end' | 'bottom-start' | 'bottom-center' | 'top-end' | 'top-start' | 'top-center';
1362
1468
  children?: ReactNode;
1363
1469
  }
1364
- declare function Toaster({ children, regionLabel: regionLabelProp, className, ...props }: ToasterProps): react.JSX.Element;
1470
+ declare function Toaster({ children, regionLabel: regionLabelProp, store, limit, position, className, onMouseEnter, onMouseLeave, onFocus, onBlur, onKeyDown, ...props }: ToasterProps): react.JSX.Element;
1365
1471
 
1366
1472
  interface TableWrapProps extends HTMLAttributes<HTMLDivElement> {
1367
1473
  /**
@@ -3366,4 +3472,4 @@ declare function hsvToHex(value: HsvColor): string;
3366
3472
  declare function contrastRatio(background: string, foreground: string): number | undefined;
3367
3473
  declare function meetsContrast(background: string, foreground: string, minimum?: number): boolean;
3368
3474
 
3369
- export { type AccessStateProps, Alert, type AlertProps, type AlertTone, AppShell, type AppShellProps, type AppliedBrandProfileSnapshot, type AsyncOperationState, AsyncOperationStatus, type AsyncOperationStatusProps, Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, type BoundedRelationshipGraph, type BrandProfile, BrandProfileEditor, type BrandProfileEditorLabels, type BrandProfileEditorProps, type BrandProfileLogo, type BrandProfileTypography, BrandProfiles, type BreadcrumbItem, Breadcrumbs, type BreadcrumbsProps, BuilderCanvas, type BuilderCanvasItem, type BuilderCanvasLabels, type BuilderCanvasProps, type BuilderPaletteItem, BulkBar, type BulkBarProps, Button, ButtonGroup, type ButtonGroupProps, ButtonLink, type ButtonLinkProps, type ButtonProps, type ButtonSize, type ButtonVariant, type CalendarDay, type CalendarWeekday, Card, CardLink, type CardLinkProps, type CardProps, CellStrong, CellSub, Checkbox, type CheckboxProps, Chip, ChipGroup, type ChipGroupProps, type ChipProps, CollapsibleSection, type CollapsibleSectionProps, ColorPicker, type ColorPickerLabels, type ColorPickerProps, type ColumnFilter, Combobox, type ComboboxOption, type ComboboxProps, CommandGroup, type CommandGroupProps, CommandItem, type CommandItemProps, CommandPalette, type CommandPaletteProps, Comment, type CommentProps, Comments, type CommentsProps, Composer, ComposerBar, type ComposerBarProps, type ComposerProps, ConfirmDialog, type ConfirmDialogProps, ContentList, ContentListItem, type ContentListItemProps, type ContentListProps, CopyButton, type CopyButtonProps, DEFAULT_BRAND_PROFILE_EDITOR_LABELS, DEFAULT_BUILDER_CANVAS_LABELS, DEFAULT_COLOR_PICKER_LABELS, DEFAULT_DATE_RANGE_PICKER_LABELS, DEFAULT_DIFF_VIEWER_LABELS, DEFAULT_MATCH_REVIEW_LABELS, DEFAULT_PAGINATION_LABELS, DEFAULT_RELATIONSHIP_GRAPH_LABELS, DEFAULT_RULE_BUILDER_LABELS, DEFAULT_SAVE_STATUS_LABELS, DEFAULT_SCHEDULING_CALENDAR_LABELS, DEFAULT_STEP_STATE_LABELS, DateRangePicker, type DateRangePickerLabels, type DateRangePickerProps, type DateRangeValue, DescriptionItem, type DescriptionItemProps, DescriptionList, type DescriptionListItem, type DescriptionListProps, DetailLayout, type DetailLayoutProps, DetailPage, type DetailPageLinkProps, type DetailPageMetric, type DetailPageProps, type DetailPageTab, type DiffEntry, type DiffKind, type DiffResolution, type DiffSummary, DiffViewer, type DiffViewerLabels, type DiffViewerProps, Drawer, type DrawerProps, EmptyState, type EmptyStateProps, ErrorState, type ErrorStateProps, EventRow, type EventRowProps, ExpandToggle, type ExpandToggleProps, FeaturedRundown, FeaturedRundownItem, type FeaturedRundownItemProps, type FeaturedRundownProps, FeaturedStage, type FeaturedStageProps, Field, type FieldProps, type FilterKind, type FilterOp, type FilterOption, FilterPopover, type FilterPopoverProps, FilteredEmptyState, type FlatNode, Flow, FlowNode, type FlowNodeProps, type FlowNodeState, type FlowProps, type FlowVariant, type FoldedText, ForbiddenState, Highlighted, type HighlightedProps, type HsvColor, Inbox, InboxDetail, type InboxDetailProps, InboxItem, type InboxItemProps, InboxList, type InboxListProps, type InboxProps, InfoTip, type InfoTipProps, Input, InputGroup, type InputGroupProps, type InputProps, type JsonPrimitive, type JsonValue, Kanban, KanbanCard, type KanbanCardProps, KanbanColumn, type KanbanColumnProps, type KanbanMoveDirection, type KanbanProps, Kbd, KbdCombo, type KbdComboProps, type KbdPlatform, type KbdProps, Label, type LabelProps, type LayeredColumn, type LayeredEdgeClassification, type LayeredEdgeKind, type LayeredItem, type LayeredLayout, type LayeredLayoutOptions, type LayeredNavigationKey, LicensePositionBar, type LicensePositionBarProps, List, ListItem, type ListItemProps, type ListProps, LoadingState, type LoadingStateProps, Mark, type MarkProps, type MatchBulkResult, MatchReview, type MatchReviewAction, type MatchReviewCandidate, type MatchReviewField, type MatchReviewLabels, type MatchReviewProps, Menu, MenuGroup, type MenuGroupProps, MenuItem, type MenuItemProps, type MenuProps, MenuSeparator, Meter, type MeterProps, type MeterTone, MetricInput, type MetricInputProps, Modal, type ModalProps, type NavFavouriteItem, NavFavourites, type NavFavouritesLabels, type NavFavouritesProps, NavGroup, type NavGroupProps, NavItem, type NavItemProps, NewsLead, type NewsLeadProps, NotFoundState, Note, type NoteProps, type NoteTone, NoticeQueue, type NoticeQueueProps, PageHeader, type PageHeaderProps, PageIntro, type PageIntroProps, Pagination, type PaginationLabels, type PaginationProps, Peek, type PeekProps, type PillTone, type PillTreatment, Pipeline, type PipelineProps, PipelineStage, type PipelineStageProps, type PipelineStageState, Placement, Popconfirm, type PopconfirmProps, Popover, type PopoverProps, type PositionedRelationshipNode, type Primitive, Progress, type ProgressProps, PromptDialog, type PromptDialogProps, Prose, type ProseProps, Radio, RadioGroup, type RadioGroupProps, type RadioProps, type RelatedLinkItem, RelatedLinks, type RelatedLinksProps, RelationshipGraph, type RelationshipGraphArrow, type RelationshipGraphCluster, type RelationshipGraphEdge, type RelationshipGraphLabels, type RelationshipGraphLegendItem, type RelationshipGraphNode, type RelationshipGraphProps, RelativeTime, type RelativeTimeFormatContext, type RelativeTimeProps, type RelativeTimeUnit, type RelativeTimeValue, ResourceGrid, type ResourceGridProps, type RgbColor, type RoutedEdge, type Row, RowAction, type RowActionProps, RowActions, type RowActionsProps, type RuleAction, type RuleActionDefinition, RuleBuilder, type RuleBuilderLabels, type RuleBuilderProps, type RuleCondition, type RuleDefinition, type RuleFieldDefinition, type RuleGroup, type RuleOperatorDefinition, type RuleValidationIssue, type RuleValueEditorProps, SaveStatus, type SaveStatusLabels, type SaveStatusProps, type SaveStatusState, type SavedViewItem, SavedViewMenu, type SavedViewMenuProps, type SavedViewSection, SchedulingCalendar, type SchedulingCalendarEntry, type SchedulingCalendarLabels, type SchedulingCalendarOverlay, type SchedulingCalendarProps, type SchedulingCalendarView, type SchedulingDatePart, type SchedulingEntryState, type SchedulingOverlayKind, type SearchSegment, SearchSuggest, type SearchSuggestAction, type SearchSuggestOption, type SearchSuggestProps, SectionHead, type SectionHeadProps, type Segment, Segmented, SegmentedOption, type SegmentedOptionProps, type SegmentedProps, Select, type SelectAllState, type SelectProps, type ShellNav, Sidebar, type SidebarProps, SidebarSection, type SidebarSectionProps, Skeleton, type SkeletonProps, type SortDir, type SortDirection, type SortKey, Spinner, type SpinnerProps, StarButton, type StarButtonProps, Stat, StatLine, type StatLineItem, type StatLineProps, type StatProps, type StatTrend, type StateVariant, StatusPill, type StatusPillProps, StatusRow, type StatusRowProps, Step, type StepProps, type StepState, type StepStateLabels, Steps, type StepsProps, SubNavItem, type SubNavItemProps, Switch, type SwitchProps, Tab, TabPanel, type TabPanelProps, type TabProps, Table, type TableDensity, type TableProps, TableWrap, type TableWrapProps, Tabs, type TabsProps, Td, type TdProps, Textarea, type TextareaProps, Th, type ThProps, Timeline, TimelineItem, type TimelineItemProps, type TimelineProps, Toast, type ToastProps, type ToastTone, Toaster, type ToasterProps, ToggleRow, type ToggleRowProps, Tooltip, type TooltipProps, Tr, type TrProps, Tree, type TreeLike, type TreeNavAction, type TreeNodeData, type TreeNodeProps, type TreeProps, type UixLabels, UixLabelsProvider, type UixLabelsProviderProps, type UixLinkProps, type UixRenderLink, type UseAnchoredPositionOptions, type UseTableOptions, type UseTableResult, type UseVirtualRowsOptions, type UseVirtualRowsResult, UserChip, type UserChipProps, ViewMenu, type ViewMenuColumn, type ViewMenuColumnLabels, type ViewMenuProps, type ViewState, type VirtualWindow, type ZonedDateSpan, addCalendarDays, addCalendarMonths, appendRuleNode, applyBrandProfile, applyFilters, boundRelationshipGraph, buildMonthGrid, buildThreeWayDiff, clampMetricValue, clampWidth, classifyLayeredEdges, clusterIdFor, contrastRatio, cx, enumerateDateSpan, filterComboboxOptions, findRuleNodeDepth, flattenTree, foldForSearch, hexToRgb, highlightSegments, hsvToHex, hsvToRgb, isDateInRange, isDateUnavailable, isRuleGroup, layeredNeighbor, layoutLayeredGraph, layoutRelationshipGraph, mapRuleGroup, matchFilter, meetsContrast, mergePinned, moveRuleNode, multiSort, nextSortDir, normalizeHex, parseDateValue, parseMetricValue, parseView, relationshipNeighbors, relativeTimeValue, removeRuleNode, renderPlainLink, reorder, restoreBrandProfile, rgbToHex, rgbToHsv, ruleDepth, searchRows, searchSegments, selectAllState, selectRangeDate, serializeView, shouldVirtualize, startOfMonth, stepMetricValue, summarizeDiff, summarizeRule, toDateKey, toggleId, togglePage, toggleSort, traverseRelationshipNode, treeNav, useAnchoredPosition, useDialog, useTable, useUixLabels, useVirtualRows, validateRuleDefinition, virtualWindow, wrapEdgeLabel, zonedDateKey, zonedDateSpan };
3475
+ export { type AccessStateProps, Alert, type AlertProps, type AlertTone, AppShell, type AppShellProps, type AppliedBrandProfileSnapshot, type AsyncOperationState, AsyncOperationStatus, type AsyncOperationStatusProps, Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, type BoundedRelationshipGraph, type BrandProfile, BrandProfileEditor, type BrandProfileEditorLabels, type BrandProfileEditorProps, type BrandProfileLogo, type BrandProfileTypography, BrandProfiles, type BreadcrumbItem, Breadcrumbs, type BreadcrumbsProps, BuilderCanvas, type BuilderCanvasItem, type BuilderCanvasLabels, type BuilderCanvasProps, type BuilderPaletteItem, BulkBar, type BulkBarProps, Button, ButtonGroup, type ButtonGroupProps, ButtonLink, type ButtonLinkProps, type ButtonProps, type ButtonSize, type ButtonVariant, type CalendarDay, type CalendarWeekday, Card, CardLink, type CardLinkProps, type CardProps, CellStrong, CellSub, Checkbox, type CheckboxProps, Chip, ChipGroup, type ChipGroupProps, type ChipProps, CollapsibleSection, type CollapsibleSectionProps, ColorPicker, type ColorPickerLabels, type ColorPickerProps, type ColumnFilter, Combobox, type ComboboxOption, type ComboboxProps, CommandGroup, type CommandGroupProps, CommandItem, type CommandItemProps, CommandPalette, type CommandPaletteProps, Comment, type CommentProps, Comments, type CommentsProps, Composer, ComposerBar, type ComposerBarProps, type ComposerProps, ConfirmDialog, type ConfirmDialogProps, ContentList, ContentListItem, type ContentListItemProps, type ContentListProps, CopyButton, type CopyButtonProps, DEFAULT_BRAND_PROFILE_EDITOR_LABELS, DEFAULT_BUILDER_CANVAS_LABELS, DEFAULT_COLOR_PICKER_LABELS, DEFAULT_DATE_RANGE_PICKER_LABELS, DEFAULT_DIFF_VIEWER_LABELS, DEFAULT_MATCH_REVIEW_LABELS, DEFAULT_PAGINATION_LABELS, DEFAULT_RELATIONSHIP_GRAPH_LABELS, DEFAULT_RULE_BUILDER_LABELS, DEFAULT_SAVE_STATUS_LABELS, DEFAULT_SCHEDULING_CALENDAR_LABELS, DEFAULT_STEP_STATE_LABELS, DateRangePicker, type DateRangePickerLabels, type DateRangePickerProps, type DateRangeValue, DescriptionItem, type DescriptionItemProps, DescriptionList, type DescriptionListItem, type DescriptionListProps, DetailLayout, type DetailLayoutProps, DetailPage, type DetailPageLinkProps, type DetailPageMetric, type DetailPageProps, type DetailPageTab, type DiffEntry, type DiffKind, type DiffResolution, type DiffSummary, DiffViewer, type DiffViewerLabels, type DiffViewerProps, Drawer, type DrawerProps, EmptyState, type EmptyStateProps, ErrorState, type ErrorStateProps, EventRow, type EventRowProps, ExpandToggle, type ExpandToggleProps, FeaturedRundown, FeaturedRundownItem, type FeaturedRundownItemProps, type FeaturedRundownProps, FeaturedStage, type FeaturedStageProps, Field, type FieldProps, type FilterKind, type FilterOp, type FilterOption, FilterPopover, type FilterPopoverProps, FilteredEmptyState, type FlatNode, Flow, FlowNode, type FlowNodeProps, type FlowNodeState, type FlowProps, type FlowVariant, type FoldedText, ForbiddenState, Highlighted, type HighlightedProps, type HsvColor, Inbox, InboxDetail, type InboxDetailProps, InboxItem, type InboxItemProps, InboxList, type InboxListProps, type InboxProps, InfoTip, type InfoTipProps, Input, InputGroup, type InputGroupProps, type InputProps, type JsonPrimitive, type JsonValue, Kanban, KanbanCard, type KanbanCardProps, KanbanColumn, type KanbanColumnProps, type KanbanMoveDirection, type KanbanProps, Kbd, KbdCombo, type KbdComboProps, type KbdPlatform, type KbdProps, Label, type LabelProps, type LayeredColumn, type LayeredEdgeClassification, type LayeredEdgeKind, type LayeredItem, type LayeredLayout, type LayeredLayoutOptions, type LayeredNavigationKey, LicensePositionBar, type LicensePositionBarProps, List, ListItem, type ListItemProps, type ListProps, LoadingState, type LoadingStateProps, Mark, type MarkProps, type MatchBulkResult, MatchReview, type MatchReviewAction, type MatchReviewCandidate, type MatchReviewField, type MatchReviewLabels, type MatchReviewProps, Menu, MenuGroup, type MenuGroupProps, MenuItem, type MenuItemProps, type MenuProps, MenuSeparator, Meter, type MeterProps, type MeterTone, MetricInput, type MetricInputProps, Modal, type ModalProps, type NavFavouriteItem, NavFavourites, type NavFavouritesLabels, type NavFavouritesProps, NavGroup, type NavGroupProps, NavItem, type NavItemProps, NewsLead, type NewsLeadProps, NotFoundState, Note, type NoteProps, type NoteTone, NoticeQueue, type NoticeQueueProps, PageHeader, type PageHeaderProps, PageIntro, type PageIntroProps, Pagination, type PaginationLabels, type PaginationProps, Peek, type PeekProps, type PillTone, type PillTreatment, Pipeline, type PipelineProps, PipelineStage, type PipelineStageProps, type PipelineStageState, Placement, Popconfirm, type PopconfirmProps, Popover, type PopoverProps, type PositionedRelationshipNode, type Primitive, Progress, type ProgressProps, PromptDialog, type PromptDialogProps, Prose, type ProseProps, Radio, RadioGroup, type RadioGroupProps, type RadioProps, type RelatedLinkItem, RelatedLinks, type RelatedLinksProps, RelationshipGraph, type RelationshipGraphArrow, type RelationshipGraphCluster, type RelationshipGraphEdge, type RelationshipGraphLabels, type RelationshipGraphLegendItem, type RelationshipGraphNode, type RelationshipGraphProps, RelativeTime, type RelativeTimeFormatContext, type RelativeTimeProps, type RelativeTimeUnit, type RelativeTimeValue, ResourceGrid, type ResourceGridProps, type RgbColor, type RoutedEdge, type Row, RowAction, type RowActionProps, RowActions, type RowActionsProps, type RuleAction, type RuleActionDefinition, RuleBuilder, type RuleBuilderLabels, type RuleBuilderProps, type RuleCondition, type RuleDefinition, type RuleFieldDefinition, type RuleGroup, type RuleOperatorDefinition, type RuleValidationIssue, type RuleValueEditorProps, SaveStatus, type SaveStatusLabels, type SaveStatusProps, type SaveStatusState, type SavedViewItem, SavedViewMenu, type SavedViewMenuProps, type SavedViewSection, SchedulingCalendar, type SchedulingCalendarEntry, type SchedulingCalendarLabels, type SchedulingCalendarOverlay, type SchedulingCalendarProps, type SchedulingCalendarView, type SchedulingDatePart, type SchedulingEntryState, type SchedulingOverlayKind, type SearchSegment, SearchSuggest, type SearchSuggestAction, type SearchSuggestOption, type SearchSuggestProps, SectionHead, type SectionHeadProps, type Segment, Segmented, SegmentedOption, type SegmentedOptionProps, type SegmentedProps, Select, type SelectAllState, type SelectProps, type ShellNav, Sidebar, type SidebarProps, SidebarSection, type SidebarSectionProps, Skeleton, type SkeletonProps, type SortDir, type SortDirection, type SortKey, Spinner, type SpinnerProps, StarButton, type StarButtonProps, Stat, StatLine, type StatLineItem, type StatLineProps, type StatProps, type StatTrend, type StateVariant, StatusPill, type StatusPillProps, StatusRow, type StatusRowProps, Step, type StepProps, type StepState, type StepStateLabels, Steps, type StepsProps, SubNavItem, type SubNavItemProps, Switch, type SwitchProps, TOAST_LEAVE_MS, Tab, TabPanel, type TabPanelProps, type TabProps, Table, type TableDensity, type TableProps, TableWrap, type TableWrapProps, Tabs, type TabsProps, Td, type TdProps, Textarea, type TextareaProps, Th, type ThProps, Timeline, TimelineItem, type TimelineItemProps, type TimelineProps, Toast, type ToastAction, type ToastApi, type ToastDismissReason, type ToastKind, type ToastOptions, type ToastPromiseMessages, type ToastProps, type ToastRecord, type ToastStore, type ToastTone, Toaster, type ToasterProps, ToggleRow, type ToggleRowProps, Tooltip, type TooltipProps, Tr, type TrProps, Tree, type TreeLike, type TreeNavAction, type TreeNodeData, type TreeNodeProps, type TreeProps, type UixLabels, UixLabelsProvider, type UixLabelsProviderProps, type UixLinkProps, type UixRenderLink, type UndoableToastOptions, type UseAnchoredPositionOptions, type UseTableOptions, type UseTableResult, type UseVirtualRowsOptions, type UseVirtualRowsResult, UserChip, type UserChipProps, ViewMenu, type ViewMenuColumn, type ViewMenuColumnLabels, type ViewMenuProps, type ViewState, type VirtualWindow, type ZonedDateSpan, addCalendarDays, addCalendarMonths, appendRuleNode, applyBrandProfile, applyFilters, boundRelationshipGraph, buildMonthGrid, buildThreeWayDiff, clampMetricValue, clampWidth, classifyLayeredEdges, clusterIdFor, contrastRatio, createToastApi, createToastStore, cx, enumerateDateSpan, filterComboboxOptions, findRuleNodeDepth, flattenTree, foldForSearch, hexToRgb, highlightSegments, hsvToHex, hsvToRgb, isDateInRange, isDateUnavailable, isRuleGroup, layeredNeighbor, layoutLayeredGraph, layoutRelationshipGraph, mapRuleGroup, matchFilter, meetsContrast, mergePinned, moveRuleNode, multiSort, nextSortDir, normalizeHex, parseDateValue, parseMetricValue, parseView, relationshipNeighbors, relativeTimeValue, removeRuleNode, renderPlainLink, reorder, restoreBrandProfile, rgbToHex, rgbToHsv, ruleDepth, searchRows, searchSegments, selectAllState, selectRangeDate, serializeView, shouldVirtualize, startOfMonth, stepMetricValue, summarizeDiff, summarizeRule, toDateKey, toast, toggleId, togglePage, toggleSort, traverseRelationshipNode, treeNav, useAnchoredPosition, useDialog, useTable, useUixLabels, useVirtualRows, validateRuleDefinition, virtualWindow, wrapEdgeLabel, zonedDateKey, zonedDateSpan };
package/dist/index.d.ts CHANGED
@@ -41,6 +41,7 @@ export { NavFavouriteItem, NavFavourites, NavFavouritesLabels, NavFavouritesProp
41
41
  export { Alert, AlertProps, AlertTone } from './components/Alert.js';
42
42
  export { Spinner, SpinnerProps } from './components/Spinner.js';
43
43
  export { Toast, ToastProps, ToastTone, Toaster, ToasterProps } from './components/Toast.js';
44
+ export { TOAST_LEAVE_MS, ToastAction, ToastApi, ToastDismissReason, ToastKind, ToastOptions, ToastPromiseMessages, ToastRecord, ToastStore, UndoableToastOptions, createToastApi, createToastStore, toast } from './toast-store.js';
44
45
  export { BulkBar, BulkBarProps, CellStrong, CellSub, ExpandToggle, ExpandToggleProps, Highlighted, HighlightedProps, Mark, MarkProps, RowAction, RowActionProps, RowActions, RowActionsProps, SortDirection, Table, TableDensity, TableProps, TableWrap, TableWrapProps, Td, TdProps, Th, ThProps, Tr, TrProps } from './components/Table.js';
45
46
  export { ColumnFilter, FilterKind, FilterOp, Primitive, Row, Segment, SelectAllState, SortDir, SortKey, ViewState, VirtualWindow, applyFilters, clampWidth, highlightSegments, matchFilter, mergePinned, multiSort, nextSortDir, parseView, reorder, searchRows, selectAllState, serializeView, shouldVirtualize, toggleId, togglePage, toggleSort, virtualWindow } from './table-engine.js';
46
47
  export { DEFAULT_PAGINATION_LABELS, Pagination, PaginationLabels, PaginationProps } from './components/Pagination.js';
package/dist/index.js CHANGED
@@ -41,6 +41,7 @@ import { NavFavourites } from "./components/NavFavourites.js";
41
41
  import { Alert } from "./components/Alert.js";
42
42
  import { Spinner } from "./components/Spinner.js";
43
43
  import { Toast, Toaster } from "./components/Toast.js";
44
+ import { toast, createToastStore, createToastApi, TOAST_LEAVE_MS } from "./toast-store.js";
44
45
  import {
45
46
  TableWrap,
46
47
  Table,
@@ -285,6 +286,7 @@ export {
285
286
  Steps,
286
287
  SubNavItem,
287
288
  Switch,
289
+ TOAST_LEAVE_MS,
288
290
  Tab,
289
291
  TabPanel,
290
292
  Table,
@@ -316,6 +318,8 @@ export {
316
318
  clusterIdFor,
317
319
  computePosition,
318
320
  contrastRatio,
321
+ createToastApi,
322
+ createToastStore,
319
323
  cx,
320
324
  enumerateDateSpan,
321
325
  filterComboboxOptions,
@@ -352,6 +356,7 @@ export {
352
356
  summarizeDiff,
353
357
  summarizeRule,
354
358
  toDateKey,
359
+ toast,
355
360
  traverseRelationshipNode,
356
361
  treeNav,
357
362
  useAnchoredPosition,
@@ -0,0 +1,98 @@
1
+ import { ReactNode } from 'react';
2
+
3
+ /**
4
+ * The imperative toast queue behind `toast()` (HAR-1363; TENSOR A2/C3, MOTUS A3/A4/C-13).
5
+ * Plain module state, no React: mutation helpers and server-action wrappers can call
6
+ * `toast.success(...)` from anywhere on the client; every mounted `Toaster` subscribed to
7
+ * the store renders it. Timing (auto-dismiss, pause on hover/focus/hidden tab) belongs to the
8
+ * `Toaster`, because it depends on what the user is looking at.
9
+ */
10
+ type ToastKind = 'success' | 'error' | 'warning' | 'info' | 'loading' | 'default';
11
+ interface ToastAction {
12
+ label: string;
13
+ /** Runs, then the toast closes (reason `"action"`). */
14
+ onClick: () => void;
15
+ }
16
+ /** Why a toast closed. */
17
+ type ToastDismissReason = 'timeout' | 'close' | 'action' | 'programmatic';
18
+ interface ToastOptions {
19
+ /** Reuse an id to replace a toast in place instead of stacking a duplicate. */
20
+ id?: string;
21
+ /** A second, quieter line. */
22
+ description?: ReactNode;
23
+ /**
24
+ * Milliseconds on screen while visible and not paused. `null` keeps it until dismissed.
25
+ * Defaults: success/info/default 5 s, warning 8 s, error and loading stay.
26
+ */
27
+ duration?: number | null;
28
+ action?: ToastAction;
29
+ /** Replaces the tone glyph. */
30
+ icon?: ReactNode;
31
+ /** Called once when the toast closes, with the reason. */
32
+ onDismiss?: (reason: ToastDismissReason) => void;
33
+ }
34
+ interface ToastRecord {
35
+ id: string;
36
+ kind: ToastKind;
37
+ message: ReactNode;
38
+ description?: ReactNode;
39
+ duration: number | null;
40
+ action?: ToastAction;
41
+ icon?: ReactNode;
42
+ /** Set while the leave animation runs; the record is removed after it. */
43
+ leaving?: boolean;
44
+ /** Bumped on every update, so a Toaster restarts the toast's timer. */
45
+ version: number;
46
+ onDismiss?: (reason: ToastDismissReason) => void;
47
+ }
48
+ interface ToastPromiseMessages<T> {
49
+ loading: ReactNode;
50
+ success: ReactNode | ((value: T) => ReactNode);
51
+ error: ReactNode | ((error: unknown) => ReactNode);
52
+ }
53
+ interface UndoableToastOptions extends Omit<ToastOptions, 'action' | 'onDismiss'> {
54
+ /** The user pressed Undo: revert the optimistic change. `onCommit` is not called. */
55
+ onUndo: () => void;
56
+ /** The toast closed without Undo (timeout, ×, or a later dismiss): make the change final. */
57
+ onCommit: () => void;
58
+ /** Default "Undo". */
59
+ undoLabel?: string;
60
+ }
61
+ interface ToastStore {
62
+ show: (kind: ToastKind, message: ReactNode, options?: ToastOptions) => string;
63
+ update: (id: string, patch: Partial<Omit<ToastRecord, 'id' | 'version'>>) => void;
64
+ dismiss: (id?: string, reason?: ToastDismissReason) => void;
65
+ subscribe: (listener: () => void) => () => void;
66
+ getSnapshot: () => readonly ToastRecord[];
67
+ }
68
+ /** How long the leave animation gets before the record is removed. */
69
+ declare const TOAST_LEAVE_MS = 200;
70
+ /** A separate queue, e.g. per test or per embedded app. Most products use the default `toast`. */
71
+ declare function createToastStore(): ToastStore;
72
+ interface ToastApi {
73
+ (message: ReactNode, options?: ToastOptions): string;
74
+ success: (message: ReactNode, options?: ToastOptions) => string;
75
+ error: (message: ReactNode, options?: ToastOptions) => string;
76
+ warning: (message: ReactNode, options?: ToastOptions) => string;
77
+ info: (message: ReactNode, options?: ToastOptions) => string;
78
+ loading: (message: ReactNode, options?: ToastOptions) => string;
79
+ /** Close one toast, or every toast without an id. */
80
+ dismiss: (id?: string) => void;
81
+ /** Change a toast in place (message, kind, description, duration, action). */
82
+ update: (id: string, patch: Partial<Omit<ToastRecord, 'id' | 'version'>>) => void;
83
+ /** A loading toast that turns into success or error when the promise settles. Returns the promise. */
84
+ promise: <T>(promise: Promise<T>, messages: ToastPromiseMessages<T>, options?: ToastOptions) => Promise<T>;
85
+ /**
86
+ * An optimistic action with an Undo button (TENSOR C2 `useUndoableAction`): `onCommit` runs
87
+ * when the toast closes without Undo; `onUndo` runs instead when Undo is pressed.
88
+ */
89
+ undoable: (message: ReactNode, options: UndoableToastOptions) => string;
90
+ /** The store this API writes to; pass it to `<Toaster store>` for a non-default queue. */
91
+ store: ToastStore;
92
+ }
93
+ /** Binds the `toast()` API to a store. */
94
+ declare function createToastApi(store: ToastStore): ToastApi;
95
+ /** The default queue; a `<Toaster />` without a `store` prop renders it. */
96
+ declare const toast: ToastApi;
97
+
98
+ export { TOAST_LEAVE_MS, type ToastAction, type ToastApi, type ToastDismissReason, type ToastKind, type ToastOptions, type ToastPromiseMessages, type ToastRecord, type ToastStore, type UndoableToastOptions, createToastApi, createToastStore, toast };
@@ -0,0 +1,110 @@
1
+ const DEFAULT_DURATION = {
2
+ success: 5e3,
3
+ info: 5e3,
4
+ default: 5e3,
5
+ warning: 8e3,
6
+ error: null,
7
+ loading: null
8
+ };
9
+ const TOAST_LEAVE_MS = 200;
10
+ let counter = 0;
11
+ function createToastStore() {
12
+ let records = [];
13
+ const listeners = /* @__PURE__ */ new Set();
14
+ const emit = () => {
15
+ for (const listener of listeners) listener();
16
+ };
17
+ const set = (next) => {
18
+ records = next;
19
+ emit();
20
+ };
21
+ const show = (kind, message, options = {}) => {
22
+ const id = options.id ?? `uix-toast-${++counter}`;
23
+ const record = {
24
+ id,
25
+ kind,
26
+ message,
27
+ description: options.description,
28
+ duration: options.duration === void 0 ? DEFAULT_DURATION[kind] : options.duration,
29
+ action: options.action,
30
+ icon: options.icon,
31
+ onDismiss: options.onDismiss,
32
+ version: 0
33
+ };
34
+ const existing = records.find((r) => r.id === id);
35
+ set(existing ? records.map((r) => r.id === id ? { ...record, version: r.version + 1 } : r) : [...records, record]);
36
+ return id;
37
+ };
38
+ const update = (id, patch) => {
39
+ if (!records.some((r) => r.id === id)) return;
40
+ set(records.map((r) => {
41
+ if (r.id !== id) return r;
42
+ const kind = patch.kind ?? r.kind;
43
+ const duration = "duration" in patch ? patch.duration ?? null : patch.kind ? DEFAULT_DURATION[kind] : r.duration;
44
+ return { ...r, ...patch, kind, duration, leaving: false, version: r.version + 1 };
45
+ }));
46
+ };
47
+ const dismiss = (id, reason = "programmatic") => {
48
+ const closing = records.filter((r) => (id === void 0 || r.id === id) && !r.leaving);
49
+ if (closing.length === 0) return;
50
+ const ids = new Set(closing.map((r) => r.id));
51
+ set(records.map((r) => ids.has(r.id) ? { ...r, leaving: true } : r));
52
+ for (const r of closing) r.onDismiss?.(reason);
53
+ const remove = () => set(records.filter((r) => !(ids.has(r.id) && r.leaving)));
54
+ if (typeof setTimeout === "function") setTimeout(remove, TOAST_LEAVE_MS);
55
+ else remove();
56
+ };
57
+ return {
58
+ show,
59
+ update,
60
+ dismiss,
61
+ subscribe: (listener) => {
62
+ listeners.add(listener);
63
+ return () => {
64
+ listeners.delete(listener);
65
+ };
66
+ },
67
+ getSnapshot: () => records
68
+ };
69
+ }
70
+ function createToastApi(store) {
71
+ const api = ((message, options) => store.show("default", message, options));
72
+ api.success = (message, options) => store.show("success", message, options);
73
+ api.error = (message, options) => store.show("error", message, options);
74
+ api.warning = (message, options) => store.show("warning", message, options);
75
+ api.info = (message, options) => store.show("info", message, options);
76
+ api.loading = (message, options) => store.show("loading", message, options);
77
+ api.dismiss = (id) => store.dismiss(id, "programmatic");
78
+ api.update = (id, patch) => store.update(id, patch);
79
+ api.promise = (promise, messages, options) => {
80
+ const id = store.show("loading", messages.loading, options);
81
+ promise.then(
82
+ (value) => store.update(id, { kind: "success", message: typeof messages.success === "function" ? messages.success(value) : messages.success }),
83
+ (error) => store.update(id, { kind: "error", message: typeof messages.error === "function" ? messages.error(error) : messages.error })
84
+ );
85
+ return promise;
86
+ };
87
+ api.undoable = (message, { onUndo, onCommit, undoLabel = "Undo", duration = 6e3, ...options }) => {
88
+ let undone = false;
89
+ return store.show("default", message, {
90
+ ...options,
91
+ duration,
92
+ action: { label: undoLabel, onClick: () => {
93
+ undone = true;
94
+ onUndo();
95
+ } },
96
+ onDismiss: () => {
97
+ if (!undone) onCommit();
98
+ }
99
+ });
100
+ };
101
+ api.store = store;
102
+ return api;
103
+ }
104
+ const toast = createToastApi(createToastStore());
105
+ export {
106
+ TOAST_LEAVE_MS,
107
+ createToastApi,
108
+ createToastStore,
109
+ toast
110
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tensor_1/react",
3
- "version": "2.27.0",
3
+ "version": "2.28.0",
4
4
  "description": "Thin React wrappers over the UIx v2 CSS component library.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",