@pixotope/toasts 0.0.0-stage → 0.0.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ # @pixotope/toasts
2
+
3
+ ## 0.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [`eb55995`](https://bitbucket.org/futureuniverse/futureuniverse.bitbucket.io/commits/eb559953e742891415438b65f9a5cf1034ffe330): First release of Pixotope toast notifications:
8
+
9
+ - `toast()`, `toast.success()`, `toast.error()` and friends show, update and dismiss toasts from anywhere, including outside React, backed by `@pixotope/event-bus`.
10
+ - `<Toaster>` renders them in the Pixotope toast design, with positions, a visible-toast limit, pause on hover and window blur, and a repeat count for duplicate ids.
11
+ - `useToasts()` and `renderToast` give headless access for custom toast UIs.
12
+ - Styles ship as `@pixotope/toasts/style.css` and use `@pixotope/design-tokens` variables.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Tihomir Selak
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 CHANGED
@@ -1,3 +1,86 @@
1
- # Temporary Holding Version
1
+ # @pixotope/toasts
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Pixotope toast notifications for React: call `toast()` from anywhere and render them with one `<Toaster>`. Styled with plain CSS on top of `@pixotope/design-tokens`; no styled-components or other UI library needed.
4
+
5
+ ```bash
6
+ pnpm add @pixotope/toasts @pixotope/design-tokens
7
+ ```
8
+
9
+ Requires `react` and `react-dom` 18.2+ or 19.
10
+
11
+ ## Setup
12
+
13
+ Mount one `<Toaster>` and import the token and toast stylesheets once:
14
+
15
+ ```tsx
16
+ import '@pixotope/design-tokens/tokens.css';
17
+ import '@pixotope/toasts/style.css';
18
+ import { Toaster } from '@pixotope/toasts';
19
+
20
+ export const App = () => (
21
+ <>
22
+ <Routes />
23
+ <Toaster position="bottom-right" />
24
+ </>
25
+ );
26
+ ```
27
+
28
+ ## Showing toasts
29
+
30
+ `toast()` works inside and outside React (stores, services, event handlers) and returns the toast's id.
31
+
32
+ ```ts
33
+ import { toast } from '@pixotope/toasts';
34
+
35
+ toast.error('Render failed', {
36
+ subtitle: 'Engine 2',
37
+ description: 'Out of GPU memory',
38
+ note: 'E_OUT_OF_MEMORY',
39
+ });
40
+
41
+ const id = toast({
42
+ type: 'info',
43
+ title: 'Update ready',
44
+ action: { label: 'Restart', onClick: () => restart() },
45
+ duration: Infinity,
46
+ });
47
+
48
+ toast.dismiss(id); // exit animation, then removed; calls onClose
49
+ toast.remove(); // removes every toast immediately
50
+ ```
51
+
52
+ Shorthands: `toast.success`, `toast.error`, `toast.warning`, `toast.info`, `toast.clean`, `toast.insight`.
53
+
54
+ | Option | Default | Notes |
55
+ | ------------- | --------------------- | ------------------------------------------------------------- |
56
+ | `id` | generated | Reusing a shown id updates it in place and shows `(count)` |
57
+ | `type` | `'warning'` | Sets the accent color |
58
+ | `title` | required | `ReactNode`; `subtitle`, `description`, `note` are too |
59
+ | `icon` | none | `'info'` or `'warning'` |
60
+ | `action` | none | `{ label, onClick(toast) }` renders a full-width button |
61
+ | `duration` | `3000` | Milliseconds; `Infinity` stays until dismissed |
62
+ | `showTime` | `true` | Time of the last update, with `timeLabel` on the left |
63
+ | `timeLabel` | `'Message received:'` | Set by `<Toaster>`; override per toast or in `toastOptions` |
64
+ | `dismissible` | `true` | Close button and middle-click to close |
65
+ | `copyable` | `true` | Copy-to-clipboard button |
66
+ | `onClose` | none | Called when the user or the timer dismisses the toast |
67
+
68
+ Timers pause while a toast is hovered and while the window is in the background.
69
+
70
+ ## `<Toaster>` props
71
+
72
+ | Prop | Default |
73
+ | -------------- | ----------------------------------------------- |
74
+ | `position` | `'top-right'` (any `top`/`bottom` × `left`/`center`/`right`) |
75
+ | `offset` | `{ top: 56, right: 14, bottom: 14, left: 14 }` |
76
+ | `limit` | `5`; later toasts wait for a free slot |
77
+ | `zIndex` | `9999` |
78
+ | `toastOptions` | Defaults for fields a toast leaves out |
79
+ | `renderToast` | `(toast, { dismiss, pause, resume }) => ReactNode` to replace the visual |
80
+ | `container` | `document.body` |
81
+
82
+ ## Custom rendering
83
+
84
+ `renderToast` swaps the visual while keeping the store, timers and positioning. Render `ToastNotification` inside it to wrap the default visual. For full control, `useToasts({ limit, toastOptions })` returns the toasts plus `dismiss`, `remove`, `pause`, `resume`, `pauseAll` and `resumeAll`, and starts the timers of the toasts it returns.
85
+
86
+ Toasts are held in one store per page, shared through `globalThis`, so toasts raised from a second bundled copy of this package still reach the mounted `<Toaster>`.
package/dist/index.cjs ADDED
@@ -0,0 +1,443 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _pixotope_event_bus = require("@pixotope/event-bus");
3
+ let react = require("react");
4
+ let react_dom = require("react-dom");
5
+ let react_jsx_runtime = require("react/jsx-runtime");
6
+ //#region src/core.ts
7
+ const DEFAULT_TOAST_DURATION = 3e3;
8
+ let generatedIds = 0;
9
+ const createToastId = () => `px-toast-${Date.now().toString(36)}-${(generatedIds++).toString(36)}`;
10
+ const createToastCore = () => {
11
+ const bus = new _pixotope_event_bus.EventBus();
12
+ const timers = /* @__PURE__ */ new Map();
13
+ const pendingRemovals = /* @__PURE__ */ new Map();
14
+ let toasts = [];
15
+ const setToasts = (next) => {
16
+ toasts = next;
17
+ bus.emit("toasts:change", toasts);
18
+ };
19
+ const clearTimer = (id) => {
20
+ const timer = timers.get(id);
21
+ if (timer?.handle) clearTimeout(timer.handle);
22
+ timers.delete(id);
23
+ };
24
+ const cancelRemoval = (id) => {
25
+ clearTimeout(pendingRemovals.get(id));
26
+ pendingRemovals.delete(id);
27
+ };
28
+ const remove = (id) => {
29
+ const removed = id === void 0 ? toasts : toasts.filter((t) => t.id === id);
30
+ if (!removed.length) return;
31
+ for (const toast of removed) {
32
+ clearTimer(toast.id);
33
+ cancelRemoval(toast.id);
34
+ }
35
+ setToasts(id === void 0 ? [] : toasts.filter((t) => t.id !== id));
36
+ };
37
+ const dismiss = (id) => {
38
+ const dismissed = toasts.filter((t) => t.visible && (id === void 0 || t.id === id));
39
+ if (!dismissed.length) return;
40
+ const dismissedIds = new Set(dismissed.map((t) => t.id));
41
+ for (const toastId of dismissedIds) {
42
+ clearTimer(toastId);
43
+ pendingRemovals.set(toastId, setTimeout(() => {
44
+ pendingRemovals.delete(toastId);
45
+ remove(toastId);
46
+ }, 300));
47
+ }
48
+ setToasts(toasts.map((t) => dismissedIds.has(t.id) ? {
49
+ ...t,
50
+ visible: false
51
+ } : t));
52
+ for (const toast of dismissed) toast.onClose?.(toast);
53
+ };
54
+ const upsert = ({ id, ...input }) => {
55
+ const now = Date.now();
56
+ if (!toasts.find((t) => t.id === id)) {
57
+ setToasts([...toasts, {
58
+ ...input,
59
+ id,
60
+ type: input.type ?? "warning",
61
+ count: 1,
62
+ createdAt: now,
63
+ updatedAt: now,
64
+ visible: true
65
+ }]);
66
+ return;
67
+ }
68
+ cancelRemoval(id);
69
+ clearTimer(id);
70
+ setToasts(toasts.map((t) => t.id === id ? {
71
+ ...t,
72
+ ...input,
73
+ type: input.type ?? t.type,
74
+ count: t.count + 1,
75
+ updatedAt: now,
76
+ visible: true
77
+ } : t));
78
+ };
79
+ bus.on("toast:upsert", upsert);
80
+ bus.on("toast:dismiss", dismiss);
81
+ bus.on("toast:remove", remove);
82
+ const runTimer = (id, timer) => {
83
+ timer.startedAt = Date.now();
84
+ timer.handle = setTimeout(() => {
85
+ timers.delete(id);
86
+ dismiss(id);
87
+ }, timer.remaining);
88
+ };
89
+ const pause = (id) => {
90
+ const timer = timers.get(id);
91
+ if (!timer?.handle) return;
92
+ clearTimeout(timer.handle);
93
+ timer.handle = void 0;
94
+ timer.remaining -= Date.now() - timer.startedAt;
95
+ };
96
+ const resume = (id) => {
97
+ const timer = timers.get(id);
98
+ if (!timer || timer.handle) return;
99
+ runTimer(id, timer);
100
+ };
101
+ return {
102
+ bus,
103
+ getToasts: () => toasts,
104
+ subscribe: (listener) => bus.on("toasts:change", listener),
105
+ startTimer: (toast, duration) => {
106
+ if (!toast.visible || !Number.isFinite(duration)) return;
107
+ if (timers.get(toast.id)?.version === toast.updatedAt) return;
108
+ clearTimer(toast.id);
109
+ const timer = {
110
+ handle: void 0,
111
+ remaining: duration,
112
+ startedAt: 0,
113
+ version: toast.updatedAt
114
+ };
115
+ timers.set(toast.id, timer);
116
+ runTimer(toast.id, timer);
117
+ },
118
+ pause,
119
+ resume,
120
+ pauseAll: () => timers.forEach((_, id) => pause(id)),
121
+ resumeAll: () => timers.forEach((_, id) => resume(id))
122
+ };
123
+ };
124
+ const coreKey = Symbol.for("@pixotope/toasts");
125
+ const getToastCore = () => globalThis[coreKey] ??= createToastCore();
126
+ //#endregion
127
+ //#region src/toast.ts
128
+ const show = (input) => {
129
+ const id = input.id ?? createToastId();
130
+ getToastCore().bus.emit("toast:upsert", {
131
+ ...input,
132
+ id
133
+ });
134
+ return id;
135
+ };
136
+ const shorthand = (type) => (title, options) => show({
137
+ ...options,
138
+ title,
139
+ type
140
+ });
141
+ /**
142
+ * Shows a toast and returns its id. Works outside React; a mounted
143
+ * `<Toaster>` renders it.
144
+ */
145
+ const toast = Object.assign(show, {
146
+ success: shorthand("success"),
147
+ error: shorthand("error"),
148
+ warning: shorthand("warning"),
149
+ info: shorthand("info"),
150
+ clean: shorthand("clean"),
151
+ insight: shorthand("insight"),
152
+ /** Plays the exit animation, then removes; omit the id to dismiss all. */
153
+ dismiss: (id) => getToastCore().bus.emit("toast:dismiss", id),
154
+ /** Removes immediately without calling `onClose`; omit the id to remove all. */
155
+ remove: (id) => getToastCore().bus.emit("toast:remove", id)
156
+ });
157
+ //#endregion
158
+ //#region src/Toaster.module.scss
159
+ var Toaster_module_default = { "toaster": "NAvrhW_toaster" };
160
+ //#endregion
161
+ //#region src/icons.tsx
162
+ const CloseIcon = (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
163
+ viewBox: "0 0 16 16",
164
+ width: "16",
165
+ height: "16",
166
+ "aria-hidden": true,
167
+ ...props,
168
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
169
+ fillRule: "evenodd",
170
+ clipRule: "evenodd",
171
+ d: "M11.6801 3L13 4.31994L9.31994 8L13 11.6801L11.6801 13L8 9.31994L4.31994 13L3 11.6801L6.68006 8L3 4.31994L4.31994 3L8 6.68006L11.6801 3Z"
172
+ })
173
+ });
174
+ const CopyIcon = (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
175
+ viewBox: "0 0 16 16",
176
+ width: "16",
177
+ height: "16",
178
+ "aria-hidden": true,
179
+ ...props,
180
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
181
+ fillRule: "evenodd",
182
+ clipRule: "evenodd",
183
+ d: "M1 10H1.87178e-06L0 2.00842C-2.58519e-07 0.904675 0.894148 0.00958455 1.99789 0.00842272L9.99956 0L10.0006 0.999999L1.99895 1.00842C1.44707 1.009 1 1.45655 1 2.00842L1 10Z"
184
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
185
+ fillRule: "evenodd",
186
+ clipRule: "evenodd",
187
+ d: "M11.0001 3C11.1327 3 11.2599 3.05268 11.3536 3.14645L15.8536 7.64645C15.9474 7.74021 16.0001 7.86739 16.0001 8V14.5C16.0001 15.3284 15.3285 16 14.5001 16H4.50009C3.67166 16 3.00009 15.3284 3.00009 14.5V4.5C3.00009 3.67157 3.67166 3 4.50009 3H11.0001ZM10.0001 4H4.50009C4.22395 4 4.00009 4.22386 4.00009 4.5V14.5C4.00009 14.7761 4.22395 15 4.50009 15H14.5001C14.7762 15 15.0001 14.7761 15.0001 14.5V9.00129H11.0001C10.4478 9.00129 10.0001 8.55357 10.0001 8.00129V4ZM11.0001 4.20711V8.00129H14.7943L11.0001 4.20711Z"
188
+ })]
189
+ });
190
+ const InfoIcon = (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
191
+ viewBox: "0 0 16 16",
192
+ width: "16",
193
+ height: "16",
194
+ "aria-hidden": true,
195
+ ...props,
196
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
197
+ fillRule: "evenodd",
198
+ d: "M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm1-3V7H7v6h2zM8 5a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"
199
+ })
200
+ });
201
+ const WarningIcon = (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
202
+ viewBox: "0 0 17 16",
203
+ width: "17",
204
+ height: "16",
205
+ "aria-hidden": true,
206
+ ...props,
207
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
208
+ fillRule: "evenodd",
209
+ clipRule: "evenodd",
210
+ d: "M0.750968 16C0.193431 16 -0.169191 15.4133 0.0801473 14.9146L7.33015 0.41459C7.60654 -0.138197 8.3954 -0.138197 8.67179 0.41459L15.9218 14.9146C16.1711 15.4133 15.8085 16 15.251 16H0.750968ZM7.25097 6H8.75097V11H7.25097V6ZM8.75097 12.75C8.75097 13.1642 8.41518 13.5 8.00097 13.5C7.58675 13.5 7.25097 13.1642 7.25097 12.75C7.25097 12.3358 7.58675 12 8.00097 12C8.41518 12 8.75097 12.3358 8.75097 12.75Z"
211
+ })
212
+ });
213
+ //#endregion
214
+ //#region src/ToastNotification.module.scss
215
+ var ToastNotification_module_default = {
216
+ "action": "_2Xv1UW_action",
217
+ "buttons": "_2Xv1UW_buttons",
218
+ "content": "_2Xv1UW_content",
219
+ "copyButton": "_2Xv1UW_copyButton",
220
+ "description": "_2Xv1UW_description",
221
+ "enter": "_2Xv1UW_enter",
222
+ "exit": "_2Xv1UW_exit",
223
+ "icon": "_2Xv1UW_icon",
224
+ "iconButton": "_2Xv1UW_iconButton",
225
+ "note": "_2Xv1UW_note",
226
+ "subtitle": "_2Xv1UW_subtitle",
227
+ "time": "_2Xv1UW_time",
228
+ "timer": "_2Xv1UW_timer",
229
+ "title": "_2Xv1UW_title",
230
+ "toast": "_2Xv1UW_toast"
231
+ };
232
+ //#endregion
233
+ //#region src/ToastNotification.tsx
234
+ const joinClassNames = (...names) => names.filter(Boolean).join(" ");
235
+ /** The default Pixotope toast visual, also usable inside `renderToast`. */
236
+ const ToastNotification = ({ toast, onDismiss, className, onMouseDown, onMouseEnter, ...rest }) => {
237
+ const titleRef = (0, react.useRef)(null);
238
+ const subtitleRef = (0, react.useRef)(null);
239
+ const descriptionRef = (0, react.useRef)(null);
240
+ const [copied, setCopied] = (0, react.useState)(false);
241
+ const { type, icon, subtitle, description, note, action, showTime = true, timeLabel, dismissible = true, copyable = true, count, updatedAt, visible } = toast;
242
+ const duration = toast.duration ?? 3e3;
243
+ const formattedTime = new Date(updatedAt).toLocaleTimeString();
244
+ const copyToClipboard = () => {
245
+ const lines = [`${type}: ${titleRef.current?.innerText ?? ""}`];
246
+ if (subtitleRef.current) lines.push(subtitleRef.current.innerText);
247
+ if (descriptionRef.current?.innerText) lines.push(descriptionRef.current.innerText);
248
+ if (showTime) lines.push(` at ${formattedTime}`);
249
+ navigator.clipboard.writeText(lines.join("\n"));
250
+ setCopied(true);
251
+ };
252
+ const Icon = icon === "info" ? InfoIcon : icon === "warning" ? WarningIcon : null;
253
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
254
+ role: type === "error" ? "alert" : "status",
255
+ className: joinClassNames(ToastNotification_module_default.toast, toast.className, className),
256
+ "data-type": type,
257
+ "data-visible": visible,
258
+ onMouseDown: (event) => {
259
+ if (event.button === 1 && dismissible) onDismiss?.();
260
+ onMouseDown?.(event);
261
+ },
262
+ onMouseEnter: (event) => {
263
+ setCopied(false);
264
+ onMouseEnter?.(event);
265
+ },
266
+ ...rest,
267
+ children: [
268
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
269
+ className: ToastNotification_module_default.buttons,
270
+ children: [copyable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
271
+ type: "button",
272
+ className: joinClassNames(ToastNotification_module_default.iconButton, ToastNotification_module_default.copyButton),
273
+ "data-copied": copied,
274
+ "aria-label": "Copy notification",
275
+ onClick: copyToClipboard,
276
+ children: copied ? "copied" : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopyIcon, {})
277
+ }), dismissible && onDismiss && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
278
+ type: "button",
279
+ className: ToastNotification_module_default.iconButton,
280
+ "aria-label": "Close notification",
281
+ onClick: onDismiss,
282
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CloseIcon, {})
283
+ })]
284
+ }),
285
+ Icon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { className: ToastNotification_module_default.icon }),
286
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
287
+ className: ToastNotification_module_default.content,
288
+ children: [
289
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
290
+ ref: titleRef,
291
+ className: ToastNotification_module_default.title,
292
+ children: toast.title
293
+ }),
294
+ subtitle && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
295
+ ref: subtitleRef,
296
+ className: ToastNotification_module_default.subtitle,
297
+ children: subtitle
298
+ }),
299
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
300
+ ref: descriptionRef,
301
+ className: ToastNotification_module_default.description,
302
+ children: description
303
+ }),
304
+ note && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
305
+ className: ToastNotification_module_default.note,
306
+ children: note
307
+ }),
308
+ action && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
309
+ type: "button",
310
+ className: ToastNotification_module_default.action,
311
+ onClick: () => action.onClick(toast),
312
+ children: action.label
313
+ }),
314
+ showTime && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
315
+ className: ToastNotification_module_default.time,
316
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: timeLabel }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [formattedTime, count > 1 && ` (${count})`] })]
317
+ })
318
+ ]
319
+ }),
320
+ visible && Number.isFinite(duration) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
321
+ className: ToastNotification_module_default.timer,
322
+ style: { animationDuration: `${duration}ms` },
323
+ "data-testid": "toast-timer"
324
+ }, updatedAt)
325
+ ]
326
+ });
327
+ };
328
+ //#endregion
329
+ //#region src/useToasts.ts
330
+ const withDefaults = (toast, defaults) => {
331
+ if (!defaults) return toast;
332
+ const merged = { ...toast };
333
+ for (const [key, value] of Object.entries(defaults)) if (merged[key] === void 0) merged[key] = value;
334
+ return merged;
335
+ };
336
+ /**
337
+ * Headless access to the toast store, for rendering toasts with your own
338
+ * components. Starts the auto-dismiss timer of every toast it returns.
339
+ */
340
+ const useToasts = ({ limit = Infinity, toastOptions } = {}) => {
341
+ const core = getToastCore();
342
+ const all = (0, react.useSyncExternalStore)(core.subscribe, core.getToasts, core.getToasts);
343
+ const toasts = (0, react.useMemo)(() => all.slice(0, limit).map((t) => withDefaults(t, toastOptions)), [
344
+ all,
345
+ limit,
346
+ toastOptions
347
+ ]);
348
+ (0, react.useEffect)(() => {
349
+ for (const toast of toasts) core.startTimer(toast, toast.duration ?? 3e3);
350
+ }, [core, toasts]);
351
+ return {
352
+ toasts,
353
+ dismiss: toast.dismiss,
354
+ remove: toast.remove,
355
+ pause: core.pause,
356
+ resume: core.resume,
357
+ pauseAll: core.pauseAll,
358
+ resumeAll: core.resumeAll
359
+ };
360
+ };
361
+ //#endregion
362
+ //#region src/Toaster.tsx
363
+ const DEFAULT_OFFSET = {
364
+ top: 56,
365
+ right: 14,
366
+ bottom: 14,
367
+ left: 14
368
+ };
369
+ const DEFAULT_TOAST_OPTIONS = { timeLabel: "Message received:" };
370
+ const placement = (position, offset) => {
371
+ const [vertical, horizontal] = position.split("-");
372
+ return {
373
+ [vertical]: offset[vertical],
374
+ ...horizontal === "center" ? { left: "50%" } : { [horizontal]: offset[horizontal] }
375
+ };
376
+ };
377
+ /** Renders toasts raised with `toast()`. Mount one per app. */
378
+ const Toaster = ({ position = "top-right", offset, limit = 5, zIndex = 9999, className, toastOptions, renderToast, container }) => {
379
+ const mergedOptions = (0, react.useMemo)(() => ({
380
+ ...DEFAULT_TOAST_OPTIONS,
381
+ ...toastOptions
382
+ }), [toastOptions]);
383
+ const { toasts, dismiss, pause, resume, pauseAll, resumeAll } = useToasts({
384
+ limit,
385
+ toastOptions: mergedOptions
386
+ });
387
+ const [windowFocused, setWindowFocused] = (0, react.useState)(true);
388
+ (0, react.useEffect)(() => {
389
+ const onBlur = () => {
390
+ pauseAll();
391
+ setWindowFocused(false);
392
+ };
393
+ const onFocus = () => {
394
+ resumeAll();
395
+ setWindowFocused(true);
396
+ };
397
+ window.addEventListener("blur", onBlur);
398
+ window.addEventListener("focus", onFocus);
399
+ return () => {
400
+ window.removeEventListener("blur", onBlur);
401
+ window.removeEventListener("focus", onFocus);
402
+ };
403
+ }, [pauseAll, resumeAll]);
404
+ const target = container ?? (typeof document === "undefined" ? null : document.body);
405
+ if (!target) return null;
406
+ return (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
407
+ "aria-live": "polite",
408
+ "aria-label": "Notifications",
409
+ className: [Toaster_module_default.toaster, className].filter(Boolean).join(" "),
410
+ "data-position": position,
411
+ "data-paused": !windowFocused,
412
+ style: {
413
+ ...placement(position, {
414
+ ...DEFAULT_OFFSET,
415
+ ...offset
416
+ }),
417
+ zIndex
418
+ },
419
+ children: toasts.map((toast) => {
420
+ const helpers = {
421
+ dismiss: () => dismiss(toast.id),
422
+ pause: () => pause(toast.id),
423
+ resume: () => resume(toast.id)
424
+ };
425
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
426
+ onMouseEnter: helpers.pause,
427
+ onMouseLeave: helpers.resume,
428
+ children: renderToast ? renderToast(toast, helpers) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToastNotification, {
429
+ toast,
430
+ onDismiss: helpers.dismiss
431
+ })
432
+ }, toast.id);
433
+ })
434
+ }), target);
435
+ };
436
+ //#endregion
437
+ exports.DEFAULT_TOAST_DURATION = DEFAULT_TOAST_DURATION;
438
+ exports.ToastNotification = ToastNotification;
439
+ exports.Toaster = Toaster;
440
+ exports.toast = toast;
441
+ exports.useToasts = useToasts;
442
+
443
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["EventBus","useRef","useState","styles","useSyncExternalStore","useMemo","toastApi","useMemo","useState","createPortal","styles"],"sources":["../src/core.ts","../src/toast.ts","../src/Toaster.module.scss","../src/icons.tsx","../src/ToastNotification.module.scss","../src/ToastNotification.tsx","../src/useToasts.ts","../src/Toaster.tsx"],"sourcesContent":["import { EventBus } from '@pixotope/event-bus';\nimport type { Toast, ToastInput } from './types';\n\nexport const DEFAULT_TOAST_DURATION = 3000;\n/** Matches the exit animation length in ToastNotification.module.scss. */\nexport const TOAST_EXIT_DURATION = 300;\n\ntype ToastEvents = {\n 'toast:upsert': ToastInput & { id: string };\n 'toast:dismiss': string | undefined;\n 'toast:remove': string | undefined;\n 'toasts:change': readonly Toast[];\n};\n\ninterface ToastTimer {\n handle: ReturnType<typeof setTimeout> | undefined;\n remaining: number;\n startedAt: number;\n /** The `updatedAt` the timer was started for; an update restarts it. */\n version: number;\n}\n\nexport interface ToastCore {\n bus: EventBus<ToastEvents>;\n getToasts: () => readonly Toast[];\n subscribe: (listener: () => void) => () => void;\n startTimer: (toast: Toast, duration: number) => void;\n pause: (id: string) => void;\n resume: (id: string) => void;\n pauseAll: () => void;\n resumeAll: () => void;\n}\n\nlet generatedIds = 0;\nexport const createToastId = () =>\n `px-toast-${Date.now().toString(36)}-${(generatedIds++).toString(36)}`;\n\nexport const createToastCore = (): ToastCore => {\n const bus = new EventBus<ToastEvents>();\n const timers = new Map<string, ToastTimer>();\n const pendingRemovals = new Map<string, ReturnType<typeof setTimeout>>();\n let toasts: readonly Toast[] = [];\n\n const setToasts = (next: readonly Toast[]) => {\n toasts = next;\n bus.emit('toasts:change', toasts);\n };\n\n const clearTimer = (id: string) => {\n const timer = timers.get(id);\n if (timer?.handle) clearTimeout(timer.handle);\n timers.delete(id);\n };\n\n const cancelRemoval = (id: string) => {\n clearTimeout(pendingRemovals.get(id));\n pendingRemovals.delete(id);\n };\n\n const remove = (id: string | undefined) => {\n const removed =\n id === undefined ? toasts : toasts.filter((t) => t.id === id);\n if (!removed.length) return;\n for (const toast of removed) {\n clearTimer(toast.id);\n cancelRemoval(toast.id);\n }\n setToasts(id === undefined ? [] : toasts.filter((t) => t.id !== id));\n };\n\n const dismiss = (id: string | undefined) => {\n const dismissed = toasts.filter(\n (t) => t.visible && (id === undefined || t.id === id)\n );\n if (!dismissed.length) return;\n const dismissedIds = new Set(dismissed.map((t) => t.id));\n for (const toastId of dismissedIds) {\n clearTimer(toastId);\n pendingRemovals.set(\n toastId,\n setTimeout(() => {\n pendingRemovals.delete(toastId);\n remove(toastId);\n }, TOAST_EXIT_DURATION)\n );\n }\n setToasts(\n toasts.map((t) => (dismissedIds.has(t.id) ? { ...t, visible: false } : t))\n );\n for (const toast of dismissed) toast.onClose?.(toast);\n };\n\n const upsert = ({ id, ...input }: ToastInput & { id: string }) => {\n const now = Date.now();\n const existing = toasts.find((t) => t.id === id);\n if (!existing) {\n setToasts([\n ...toasts,\n {\n ...input,\n id,\n type: input.type ?? 'warning',\n count: 1,\n createdAt: now,\n updatedAt: now,\n visible: true,\n },\n ]);\n return;\n }\n cancelRemoval(id);\n clearTimer(id);\n setToasts(\n toasts.map((t) =>\n t.id === id\n ? {\n ...t,\n ...input,\n type: input.type ?? t.type,\n count: t.count + 1,\n updatedAt: now,\n visible: true,\n }\n : t\n )\n );\n };\n\n bus.on('toast:upsert', upsert);\n bus.on('toast:dismiss', dismiss);\n bus.on('toast:remove', remove);\n\n const runTimer = (id: string, timer: ToastTimer) => {\n timer.startedAt = Date.now();\n timer.handle = setTimeout(() => {\n timers.delete(id);\n dismiss(id);\n }, timer.remaining);\n };\n\n const pause = (id: string) => {\n const timer = timers.get(id);\n if (!timer?.handle) return;\n clearTimeout(timer.handle);\n timer.handle = undefined;\n timer.remaining -= Date.now() - timer.startedAt;\n };\n\n const resume = (id: string) => {\n const timer = timers.get(id);\n if (!timer || timer.handle) return;\n runTimer(id, timer);\n };\n\n return {\n bus,\n getToasts: () => toasts,\n subscribe: (listener) => bus.on('toasts:change', listener),\n startTimer: (toast, duration) => {\n if (!toast.visible || !Number.isFinite(duration)) return;\n if (timers.get(toast.id)?.version === toast.updatedAt) return;\n clearTimer(toast.id);\n const timer: ToastTimer = {\n handle: undefined,\n remaining: duration,\n startedAt: 0,\n version: toast.updatedAt,\n };\n timers.set(toast.id, timer);\n runTimer(toast.id, timer);\n },\n pause,\n resume,\n pauseAll: () => timers.forEach((_, id) => pause(id)),\n resumeAll: () => timers.forEach((_, id) => resume(id)),\n };\n};\n\n// Kept on globalThis so toasts raised from a second bundled copy of this\n// package still reach the <Toaster> rendered by the first.\nconst coreKey = Symbol.for('@pixotope/toasts');\ntype GlobalWithCore = typeof globalThis & { [coreKey]?: ToastCore };\n\nexport const getToastCore = (): ToastCore =>\n ((globalThis as GlobalWithCore)[coreKey] ??= createToastCore());\n","import { createToastId, getToastCore } from './core';\nimport type { ToastInput, ToastOptions, ToastType } from './types';\n\nexport type ToastShorthand = (\n title: ToastInput['title'],\n options?: Omit<ToastOptions, 'type'>\n) => string;\n\nconst show = (input: ToastInput): string => {\n const id = input.id ?? createToastId();\n getToastCore().bus.emit('toast:upsert', { ...input, id });\n return id;\n};\n\nconst shorthand =\n (type: ToastType): ToastShorthand =>\n (title, options) =>\n show({ ...options, title, type });\n\n/**\n * Shows a toast and returns its id. Works outside React; a mounted\n * `<Toaster>` renders it.\n */\nexport const toast = Object.assign(show, {\n success: shorthand('success'),\n error: shorthand('error'),\n warning: shorthand('warning'),\n info: shorthand('info'),\n clean: shorthand('clean'),\n insight: shorthand('insight'),\n /** Plays the exit animation, then removes; omit the id to dismiss all. */\n dismiss: (id?: string) => getToastCore().bus.emit('toast:dismiss', id),\n /** Removes immediately without calling `onClose`; omit the id to remove all. */\n remove: (id?: string) => getToastCore().bus.emit('toast:remove', id),\n});\n",".toaster {\n position: fixed;\n display: flex;\n flex-direction: column;\n gap: 4px;\n pointer-events: none;\n}\n.toaster > * {\n pointer-events: auto;\n}\n.toaster[data-position$=-right] {\n align-items: flex-end;\n --px-toast-enter-transform: translateX(100%);\n}\n.toaster[data-position$=-left] {\n align-items: flex-start;\n --px-toast-enter-transform: translateX(-100%);\n}\n.toaster[data-position$=-center] {\n align-items: center;\n transform: translateX(-50%);\n}\n.toaster[data-position=top-center] {\n --px-toast-enter-transform: translateY(-100%);\n}\n.toaster[data-position=bottom-center] {\n --px-toast-enter-transform: translateY(100%);\n}","import type { SVGProps } from 'react';\n\ntype IconProps = SVGProps<SVGSVGElement>;\n\nexport const CloseIcon = (props: IconProps) => (\n <svg viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden {...props}>\n <path\n fillRule=\"evenodd\"\n clipRule=\"evenodd\"\n d=\"M11.6801 3L13 4.31994L9.31994 8L13 11.6801L11.6801 13L8 9.31994L4.31994 13L3 11.6801L6.68006 8L3 4.31994L4.31994 3L8 6.68006L11.6801 3Z\"\n />\n </svg>\n);\n\nexport const CopyIcon = (props: IconProps) => (\n <svg viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden {...props}>\n <path\n fillRule=\"evenodd\"\n clipRule=\"evenodd\"\n d=\"M1 10H1.87178e-06L0 2.00842C-2.58519e-07 0.904675 0.894148 0.00958455 1.99789 0.00842272L9.99956 0L10.0006 0.999999L1.99895 1.00842C1.44707 1.009 1 1.45655 1 2.00842L1 10Z\"\n />\n <path\n fillRule=\"evenodd\"\n clipRule=\"evenodd\"\n d=\"M11.0001 3C11.1327 3 11.2599 3.05268 11.3536 3.14645L15.8536 7.64645C15.9474 7.74021 16.0001 7.86739 16.0001 8V14.5C16.0001 15.3284 15.3285 16 14.5001 16H4.50009C3.67166 16 3.00009 15.3284 3.00009 14.5V4.5C3.00009 3.67157 3.67166 3 4.50009 3H11.0001ZM10.0001 4H4.50009C4.22395 4 4.00009 4.22386 4.00009 4.5V14.5C4.00009 14.7761 4.22395 15 4.50009 15H14.5001C14.7762 15 15.0001 14.7761 15.0001 14.5V9.00129H11.0001C10.4478 9.00129 10.0001 8.55357 10.0001 8.00129V4ZM11.0001 4.20711V8.00129H14.7943L11.0001 4.20711Z\"\n />\n </svg>\n);\n\nexport const InfoIcon = (props: IconProps) => (\n <svg viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden {...props}>\n <path\n fillRule=\"evenodd\"\n d=\"M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm1-3V7H7v6h2zM8 5a1 1 0 1 0 0-2 1 1 0 0 0 0 2z\"\n />\n </svg>\n);\n\nexport const WarningIcon = (props: IconProps) => (\n <svg viewBox=\"0 0 17 16\" width=\"17\" height=\"16\" aria-hidden {...props}>\n <path\n fillRule=\"evenodd\"\n clipRule=\"evenodd\"\n d=\"M0.750968 16C0.193431 16 -0.169191 15.4133 0.0801473 14.9146L7.33015 0.41459C7.60654 -0.138197 8.3954 -0.138197 8.67179 0.41459L15.9218 14.9146C16.1711 15.4133 15.8085 16 15.251 16H0.750968ZM7.25097 6H8.75097V11H7.25097V6ZM8.75097 12.75C8.75097 13.1642 8.41518 13.5 8.00097 13.5C7.58675 13.5 7.25097 13.1642 7.25097 12.75C7.25097 12.3358 7.58675 12 8.00097 12C8.41518 12 8.75097 12.3358 8.75097 12.75Z\"\n />\n </svg>\n);\n","@keyframes enter {\n from {\n transform: var(--px-toast-enter-transform, translateX(100%));\n opacity: 0;\n }\n to {\n transform: none;\n opacity: 1;\n }\n}\n@keyframes exit {\n from {\n transform: none;\n opacity: 1;\n }\n to {\n transform: var(--px-toast-enter-transform, translateX(100%));\n opacity: 0;\n }\n}\n@keyframes timer {\n from {\n width: 100%;\n }\n to {\n width: 0;\n }\n}\n.toast {\n --px-toast-accent: var(--support-03);\n position: relative;\n display: flex;\n gap: 8px;\n width: 320px;\n padding: 12px 24px 12px 20px;\n border: 0 solid var(--px-toast-accent);\n border-left-width: 5px;\n background-color: var(--ui-02);\n user-select: text;\n animation: enter 0.3s ease-in-out;\n}\n.toast[data-type=info] {\n --px-toast-accent: var(--support-04);\n}\n.toast[data-type=error] {\n --px-toast-accent: var(--interactive-07);\n}\n.toast[data-type=success] {\n --px-toast-accent: var(--support-12);\n}\n.toast[data-type=insight] {\n --px-toast-accent: var(--support-13);\n}\n.toast[data-type=clean] {\n --px-toast-accent: transparent;\n}\n.toast[data-visible=false] {\n animation: exit 0.3s ease-in-out forwards;\n}\n\n.timer {\n position: absolute;\n bottom: 0;\n left: 0;\n width: 100%;\n height: 4px;\n background: var(--text-01);\n opacity: 0.3;\n animation: timer linear 1 forwards;\n}\n.toast:hover .timer, [data-paused=true] .timer {\n animation-play-state: paused;\n}\n\n.icon {\n flex-shrink: 0;\n fill: var(--px-toast-accent);\n}\n\n.buttons {\n position: absolute;\n top: 12px;\n right: 0;\n display: flex;\n gap: 8px;\n margin-right: 14px;\n}\n\n.iconButton {\n display: inline-flex;\n margin: 0;\n padding: 0;\n border: none;\n background: transparent;\n color: var(--text-01);\n font-size: 12px;\n cursor: pointer;\n}\n.iconButton svg {\n fill: currentColor;\n}\n.iconButton:focus-visible {\n outline: 1px solid var(--interactive-01);\n}\n\n.copyButton {\n opacity: 1;\n transition: opacity 0.2s ease-in-out;\n}\n.copyButton svg {\n opacity: 0;\n transition: opacity 0.2s ease-in-out;\n}\n.toast:hover .copyButton svg, .copyButton:focus-visible svg {\n opacity: 1;\n}\n.copyButton[data-copied=true] {\n opacity: 0;\n transition-duration: 1s;\n}\n\n.content {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n justify-content: flex-start;\n width: 100%;\n color: var(--text-01);\n}\n\n.title {\n margin: 0 0 4px;\n padding-bottom: 4px;\n color: var(--text-01);\n font-size: 16px;\n font-weight: 600;\n line-height: 22px;\n letter-spacing: 0.16px;\n text-align: left;\n}\n\n.subtitle {\n margin-top: -4px;\n margin-bottom: 8px;\n color: var(--text-03);\n font-size: 12px;\n font-weight: 400;\n line-height: 16px;\n letter-spacing: 0.32px;\n}\n\n.description {\n margin-bottom: 8px;\n color: var(--text-01);\n font-size: 14px;\n font-weight: 400;\n line-height: 20px;\n letter-spacing: 0.16px;\n text-align: left;\n overflow-wrap: anywhere;\n}\n\n.note {\n align-self: stretch;\n margin-bottom: 8px;\n padding: 4px 8px;\n border-radius: 2px;\n background-color: var(--faded-light-green-2);\n color: var(--text-01);\n font-family: \"IBM Plex Mono\", monospace;\n font-size: 14px;\n font-weight: 400;\n line-height: 20px;\n text-align: left;\n}\n\n.action {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n margin-bottom: 8px;\n padding: 10px 22px;\n border: 2px solid var(--interactive-01);\n background: var(--interactive-01);\n color: var(--ui-01);\n font: inherit;\n font-size: 14px;\n font-weight: 600;\n line-height: 16px;\n text-align: center;\n cursor: pointer;\n transition: opacity 0.3s ease, background 0.3s ease;\n}\n.action:hover {\n opacity: 0.9;\n}\n.action:active {\n opacity: 0.8;\n}\n.action:focus {\n outline: none;\n}\n.action:focus-visible {\n outline: 2px solid var(--interactive-03-hover);\n}\n\n.time {\n display: flex;\n justify-content: space-between;\n width: 100%;\n color: var(--text-03);\n font-size: 12px;\n font-weight: 400;\n line-height: 16px;\n}","import { useRef, useState, type HTMLAttributes } from 'react';\nimport { CloseIcon, CopyIcon, InfoIcon, WarningIcon } from './icons';\nimport styles from './ToastNotification.module.scss';\nimport type { Toast } from './types';\nimport { DEFAULT_TOAST_DURATION } from './core';\n\nexport interface ToastNotificationProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n 'title'\n> {\n toast: Toast;\n onDismiss?: () => void;\n}\n\nconst joinClassNames = (...names: (string | false | undefined)[]) =>\n names.filter(Boolean).join(' ');\n\n/** The default Pixotope toast visual, also usable inside `renderToast`. */\nexport const ToastNotification = ({\n toast,\n onDismiss,\n className,\n onMouseDown,\n onMouseEnter,\n ...rest\n}: ToastNotificationProps) => {\n const titleRef = useRef<HTMLHeadingElement>(null);\n const subtitleRef = useRef<HTMLDivElement>(null);\n const descriptionRef = useRef<HTMLDivElement>(null);\n const [copied, setCopied] = useState(false);\n\n const {\n type,\n icon,\n subtitle,\n description,\n note,\n action,\n showTime = true,\n timeLabel,\n dismissible = true,\n copyable = true,\n count,\n updatedAt,\n visible,\n } = toast;\n const duration = toast.duration ?? DEFAULT_TOAST_DURATION;\n const formattedTime = new Date(updatedAt).toLocaleTimeString();\n\n const copyToClipboard = () => {\n const lines = [`${type}: ${titleRef.current?.innerText ?? ''}`];\n if (subtitleRef.current) lines.push(subtitleRef.current.innerText);\n if (descriptionRef.current?.innerText) {\n lines.push(descriptionRef.current.innerText);\n }\n if (showTime) lines.push(` at ${formattedTime}`);\n void navigator.clipboard.writeText(lines.join('\\n'));\n setCopied(true);\n };\n\n const Icon =\n icon === 'info' ? InfoIcon : icon === 'warning' ? WarningIcon : null;\n\n return (\n <div\n role={type === 'error' ? 'alert' : 'status'}\n className={joinClassNames(styles.toast, toast.className, className)}\n data-type={type}\n data-visible={visible}\n onMouseDown={(event) => {\n if (event.button === 1 && dismissible) onDismiss?.();\n onMouseDown?.(event);\n }}\n onMouseEnter={(event) => {\n setCopied(false);\n onMouseEnter?.(event);\n }}\n {...rest}\n >\n <div className={styles.buttons}>\n {copyable && (\n <button\n type=\"button\"\n className={joinClassNames(styles.iconButton, styles.copyButton)}\n data-copied={copied}\n aria-label=\"Copy notification\"\n onClick={copyToClipboard}\n >\n {copied ? 'copied' : <CopyIcon />}\n </button>\n )}\n {dismissible && onDismiss && (\n <button\n type=\"button\"\n className={styles.iconButton}\n aria-label=\"Close notification\"\n onClick={onDismiss}\n >\n <CloseIcon />\n </button>\n )}\n </div>\n {Icon && <Icon className={styles.icon} />}\n <div className={styles.content}>\n <h3 ref={titleRef} className={styles.title}>\n {toast.title}\n </h3>\n {subtitle && (\n <div ref={subtitleRef} className={styles.subtitle}>\n {subtitle}\n </div>\n )}\n <div ref={descriptionRef} className={styles.description}>\n {description}\n </div>\n {note && <div className={styles.note}>{note}</div>}\n {action && (\n <button\n type=\"button\"\n className={styles.action}\n onClick={() => action.onClick(toast)}\n >\n {action.label}\n </button>\n )}\n {showTime && (\n <div className={styles.time}>\n <div>{timeLabel}</div>\n <div>\n {formattedTime}\n {count > 1 && ` (${count})`}\n </div>\n </div>\n )}\n </div>\n {visible && Number.isFinite(duration) && (\n <span\n key={updatedAt}\n className={styles.timer}\n style={{ animationDuration: `${duration}ms` }}\n data-testid=\"toast-timer\"\n />\n )}\n </div>\n );\n};\n","import { useEffect, useMemo, useSyncExternalStore } from 'react';\nimport { DEFAULT_TOAST_DURATION, getToastCore } from './core';\nimport { toast as toastApi } from './toast';\nimport type { Toast, ToastOptions } from './types';\n\nexport type ToastDefaults = Omit<ToastOptions, 'id'>;\n\nexport interface UseToastsOptions {\n /** Most toasts returned at once; the rest wait, and their timers do not run yet. */\n limit?: number;\n /** Defaults for fields a toast leaves undefined. */\n toastOptions?: ToastDefaults;\n}\n\nexport interface UseToastsResult {\n toasts: Toast[];\n dismiss: (id?: string) => void;\n remove: (id?: string) => void;\n pause: (id: string) => void;\n resume: (id: string) => void;\n pauseAll: () => void;\n resumeAll: () => void;\n}\n\nconst withDefaults = (toast: Toast, defaults: ToastDefaults | undefined) => {\n if (!defaults) return toast;\n const merged: Record<string, unknown> = { ...toast };\n for (const [key, value] of Object.entries(defaults)) {\n if (merged[key] === undefined) merged[key] = value;\n }\n return merged as unknown as Toast;\n};\n\n/**\n * Headless access to the toast store, for rendering toasts with your own\n * components. Starts the auto-dismiss timer of every toast it returns.\n */\nexport const useToasts = ({\n limit = Infinity,\n toastOptions,\n}: UseToastsOptions = {}): UseToastsResult => {\n const core = getToastCore();\n const all = useSyncExternalStore(\n core.subscribe,\n core.getToasts,\n core.getToasts\n );\n\n const toasts = useMemo(\n () => all.slice(0, limit).map((t) => withDefaults(t, toastOptions)),\n [all, limit, toastOptions]\n );\n\n useEffect(() => {\n for (const toast of toasts) {\n core.startTimer(toast, toast.duration ?? DEFAULT_TOAST_DURATION);\n }\n }, [core, toasts]);\n\n return {\n toasts,\n dismiss: toastApi.dismiss,\n remove: toastApi.remove,\n pause: core.pause,\n resume: core.resume,\n pauseAll: core.pauseAll,\n resumeAll: core.resumeAll,\n };\n};\n","import {\n useEffect,\n useMemo,\n useState,\n type CSSProperties,\n type ReactNode,\n} from 'react';\nimport { createPortal } from 'react-dom';\nimport styles from './Toaster.module.scss';\nimport { ToastNotification } from './ToastNotification';\nimport type { Toast, ToastPosition } from './types';\nimport { useToasts, type ToastDefaults } from './useToasts';\n\nexport interface ToastOffset {\n top?: number;\n right?: number;\n bottom?: number;\n left?: number;\n}\n\nexport interface RenderToastHelpers {\n dismiss: () => void;\n pause: () => void;\n resume: () => void;\n}\n\nexport interface ToasterProps {\n position?: ToastPosition;\n /** Distance in px from the viewport edges the position touches. */\n offset?: ToastOffset;\n /** Most toasts shown at once; later ones wait for a free slot. */\n limit?: number;\n zIndex?: number;\n className?: string;\n /** Defaults for fields a toast leaves undefined. */\n toastOptions?: ToastDefaults;\n /** Replaces the default visual; render `ToastNotification` to wrap it. */\n renderToast?: (toast: Toast, helpers: RenderToastHelpers) => ReactNode;\n /** Portal target; defaults to `document.body`. */\n container?: Element | null;\n}\n\nconst DEFAULT_OFFSET: Required<ToastOffset> = {\n top: 56,\n right: 14,\n bottom: 14,\n left: 14,\n};\n\nconst DEFAULT_TOAST_OPTIONS: ToastDefaults = {\n timeLabel: 'Message received:',\n};\n\nconst placement = (\n position: ToastPosition,\n offset: Required<ToastOffset>\n): CSSProperties => {\n const [vertical, horizontal] = position.split('-') as [\n 'top' | 'bottom',\n 'left' | 'center' | 'right',\n ];\n return {\n [vertical]: offset[vertical],\n ...(horizontal === 'center'\n ? { left: '50%' }\n : { [horizontal]: offset[horizontal] }),\n };\n};\n\n/** Renders toasts raised with `toast()`. Mount one per app. */\nexport const Toaster = ({\n position = 'top-right',\n offset,\n limit = 5,\n zIndex = 9999,\n className,\n toastOptions,\n renderToast,\n container,\n}: ToasterProps) => {\n const mergedOptions = useMemo(\n () => ({ ...DEFAULT_TOAST_OPTIONS, ...toastOptions }),\n [toastOptions]\n );\n\n const { toasts, dismiss, pause, resume, pauseAll, resumeAll } = useToasts({\n limit,\n toastOptions: mergedOptions,\n });\n const [windowFocused, setWindowFocused] = useState(true);\n\n // Timers stop while the window is in the background, as in ToastsPortal\n useEffect(() => {\n const onBlur = () => {\n pauseAll();\n setWindowFocused(false);\n };\n const onFocus = () => {\n resumeAll();\n setWindowFocused(true);\n };\n window.addEventListener('blur', onBlur);\n window.addEventListener('focus', onFocus);\n return () => {\n window.removeEventListener('blur', onBlur);\n window.removeEventListener('focus', onFocus);\n };\n }, [pauseAll, resumeAll]);\n\n const target =\n container ?? (typeof document === 'undefined' ? null : document.body);\n if (!target) return null;\n\n return createPortal(\n <section\n aria-live=\"polite\"\n aria-label=\"Notifications\"\n className={[styles.toaster, className].filter(Boolean).join(' ')}\n data-position={position}\n data-paused={!windowFocused}\n style={{\n ...placement(position, { ...DEFAULT_OFFSET, ...offset }),\n zIndex,\n }}\n >\n {toasts.map((toast) => {\n const helpers: RenderToastHelpers = {\n dismiss: () => dismiss(toast.id),\n pause: () => pause(toast.id),\n resume: () => resume(toast.id),\n };\n return (\n <div\n key={toast.id}\n onMouseEnter={helpers.pause}\n onMouseLeave={helpers.resume}\n >\n {renderToast ? (\n renderToast(toast, helpers)\n ) : (\n <ToastNotification toast={toast} onDismiss={helpers.dismiss} />\n )}\n </div>\n );\n })}\n </section>,\n target\n );\n};\n"],"mappings":"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"}