@wello-dev/notifications 0.0.0-stage → 1.0.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/dist/index.cjs ADDED
@@ -0,0 +1,1418 @@
1
+ "use client";
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/index.ts
22
+ var index_exports = {};
23
+ __export(index_exports, {
24
+ CloseIcon: () => CloseIcon,
25
+ ErrorIcon: () => ErrorIcon,
26
+ InfoIcon: () => InfoIcon,
27
+ LoadingIcon: () => LoadingIcon,
28
+ NOTIFICATION_SOUNDS: () => NOTIFICATION_SOUNDS,
29
+ SuccessIcon: () => SuccessIcon,
30
+ Toaster: () => Toaster,
31
+ WarningIcon: () => WarningIcon,
32
+ createTabNotifications: () => createTabNotifications,
33
+ playNotificationSound: () => playNotificationSound,
34
+ playSound: () => playSound,
35
+ playTone: () => playTone,
36
+ resolveColor: () => resolveColor,
37
+ resolveLabels: () => resolveLabels,
38
+ scheduleSound: () => scheduleSound,
39
+ tabNotifications: () => tabNotifications,
40
+ toast: () => toast,
41
+ toastStore: () => toastStore,
42
+ useToasts: () => useToasts
43
+ });
44
+ module.exports = __toCommonJS(index_exports);
45
+
46
+ // #style-inject:#style-inject
47
+ function styleInject(css, { insertAt } = {}) {
48
+ if (!css || typeof document === "undefined") return;
49
+ const head = document.head || document.getElementsByTagName("head")[0];
50
+ const style = document.createElement("style");
51
+ style.type = "text/css";
52
+ if (insertAt === "top") {
53
+ if (head.firstChild) {
54
+ head.insertBefore(style, head.firstChild);
55
+ } else {
56
+ head.appendChild(style);
57
+ }
58
+ } else {
59
+ head.appendChild(style);
60
+ }
61
+ if (style.styleSheet) {
62
+ style.styleSheet.cssText = css;
63
+ } else {
64
+ style.appendChild(document.createTextNode(css));
65
+ }
66
+ }
67
+
68
+ // src/styles.css
69
+ styleInject('@layer theme, base, components, utilities;\n@layer components {\n .wello-notifications-toaster {\n --wello-notifications-bg: rgb(255 255 255 / 0.95);\n --wello-notifications-fg: #0f172a;\n --wello-notifications-muted: #64748b;\n --wello-notifications-border: rgb(226 232 240 / 0.7);\n --wello-notifications-ring: rgb(0 0 0 / 0.05);\n --wello-notifications-hover: rgb(0 0 0 / 0.05);\n --wello-notifications-shadow: 0 8px 24px -12px rgb(0 0 0 / 0.22);\n --wello-notifications-radius: 16px;\n --wello-notifications-font: inherit;\n position: fixed;\n display: flex;\n flex-direction: column;\n box-sizing: border-box;\n width: var(--wello-notifications-width, 390px);\n max-width: calc(100vw - 2 * var(--wello-notifications-offset, 16px));\n margin: 0;\n padding: 0;\n list-style: none;\n pointer-events: none;\n font-family: var(--wello-notifications-font);\n color-scheme: light;\n }\n .dark .wello-notifications-toaster:not([data-theme=light]),\n [data-theme=dark] .wello-notifications-toaster:not([data-theme=light]),\n .wello-notifications-toaster[data-theme=dark] {\n --wello-notifications-bg: rgb(15 23 42 / 0.92);\n --wello-notifications-fg: #f1f5f9;\n --wello-notifications-muted: #94a3b8;\n --wello-notifications-border: rgb(255 255 255 / 0.1);\n --wello-notifications-ring: rgb(255 255 255 / 0.06);\n --wello-notifications-hover: rgb(255 255 255 / 0.1);\n --wello-notifications-shadow: 0 8px 24px -12px rgb(0 0 0 / 0.45);\n color-scheme: dark;\n }\n .wello-notifications-toaster[data-y=top] {\n top: calc(var(--wello-notifications-offset, 16px) - var(--wello-notifications-gap, 12px) / 2);\n }\n .wello-notifications-toaster[data-y=bottom] {\n bottom: calc(var(--wello-notifications-offset, 16px) - var(--wello-notifications-gap, 12px) / 2);\n flex-direction: column-reverse;\n }\n .wello-notifications-toaster[data-x=start] {\n inset-inline-start: var(--wello-notifications-offset, 16px);\n }\n .wello-notifications-toaster[data-x=end] {\n inset-inline-end: var(--wello-notifications-offset, 16px);\n }\n .wello-notifications-toaster[data-x=center] {\n left: 50%;\n transform: translateX(-50%);\n }\n @media (max-width: 480px) {\n .wello-notifications-toaster {\n width: auto;\n max-width: none;\n inset-inline: var(--wello-notifications-offset, 16px);\n transform: none;\n }\n .wello-notifications-toaster[data-x=center] {\n left: var(--wello-notifications-offset, 16px);\n }\n }\n .wello-notifications-item {\n display: grid;\n grid-template-rows: 1fr;\n transition: grid-template-rows 200ms ease 110ms;\n }\n .wello-notifications-item-inner {\n min-height: 0;\n padding-block: calc(var(--wello-notifications-gap, 12px) / 2);\n }\n .wello-notifications-item[data-state=closing] {\n grid-template-rows: 0fr;\n }\n .wello-notifications-item[data-state=closing] .wello-notifications-item-inner {\n overflow: hidden;\n }\n .wello-notifications-toast,\n .wello-notifications-custom {\n pointer-events: auto;\n touch-action: pan-y;\n transform: translateX(var(--wello-notifications-swipe, 0px));\n transition:\n transform 200ms ease,\n opacity 160ms ease,\n scale 160ms ease;\n animation: wello-notifications-in 260ms cubic-bezier(0.21, 1.02, 0.73, 1);\n }\n .wello-notifications-toaster[data-y=top] .wello-notifications-toast,\n .wello-notifications-toaster[data-y=top] .wello-notifications-custom {\n --wello-notifications-enter: -14px;\n }\n .wello-notifications-toast[data-swiping],\n .wello-notifications-custom[data-swiping] {\n transition: none;\n user-select: none;\n }\n .wello-notifications-item[data-state=closing] .wello-notifications-toast,\n .wello-notifications-item[data-state=closing] .wello-notifications-custom {\n opacity: 0;\n scale: 0.96;\n }\n .wello-notifications-item[data-swiped=end] .wello-notifications-toast,\n .wello-notifications-item[data-swiped=end] .wello-notifications-custom {\n transform: translateX(calc(100% + 32px));\n }\n .wello-notifications-item[data-swiped=start] .wello-notifications-toast,\n .wello-notifications-item[data-swiped=start] .wello-notifications-custom {\n transform: translateX(calc(-100% - 32px));\n }\n .wello-notifications-toast {\n position: relative;\n display: flex;\n align-items: flex-start;\n gap: 12px;\n box-sizing: border-box;\n width: 100%;\n padding-block: 14px;\n padding-inline: 14px 8px;\n overflow: hidden;\n border: 1px solid var(--wello-notifications-border);\n border-radius: var(--wello-notifications-radius);\n background: var(--wello-notifications-bg);\n color: var(--wello-notifications-fg);\n box-shadow: var(--wello-notifications-shadow), 0 0 0 1px var(--wello-notifications-ring);\n backdrop-filter: blur(24px) saturate(1.5);\n -webkit-backdrop-filter: blur(24px) saturate(1.5);\n font-size: 14px;\n line-height: 1.4;\n text-align: start;\n }\n .wello-notifications-toast::before {\n content: "";\n position: absolute;\n inset-block: 0.55rem;\n inset-inline-start: 0;\n width: 3px;\n border-radius: 999px;\n background: var(--wello-notifications-tone);\n }\n .wello-notifications-icon {\n display: flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n border-radius: var(--wello-notifications-radius);\n color: var(--wello-notifications-tone);\n background: color-mix(in oklab, var(--wello-notifications-tone) 16%, transparent);\n box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--wello-notifications-tone) 28%, transparent);\n font-size: 1.15rem;\n }\n .wello-notifications-content {\n display: flex;\n flex: 1;\n flex-direction: column;\n gap: 4px;\n min-width: 0;\n padding-block: 2px;\n padding-inline-end: 8px;\n overflow-wrap: anywhere;\n }\n .wello-notifications-toast > .wello-notifications-content:first-child {\n padding-inline-start: 2px;\n }\n .wello-notifications-title {\n margin: 0;\n font-weight: 600;\n line-height: 1.35;\n color: var(--wello-notifications-tone);\n }\n .wello-notifications-toast[data-type=default] .wello-notifications-title,\n .wello-notifications-toast[data-type=loading] .wello-notifications-title {\n color: var(--wello-notifications-fg);\n }\n .wello-notifications-description {\n margin: 0;\n color: var(--wello-notifications-muted);\n line-height: 1.55;\n }\n .wello-notifications-actions {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n margin-top: 6px;\n }\n .wello-notifications-button {\n display: inline-flex;\n align-items: center;\n height: 28px;\n padding-inline: 10px;\n border: 1px solid transparent;\n border-radius: 8px;\n font: inherit;\n font-size: 13px;\n font-weight: 600;\n cursor: pointer;\n transition: background-color 120ms ease, filter 120ms ease;\n }\n .wello-notifications-action {\n background: var(--wello-notifications-tone);\n color: #fff;\n }\n .wello-notifications-toast[data-type=default] .wello-notifications-action,\n .wello-notifications-toast[data-type=loading] .wello-notifications-action {\n background: var(--wello-notifications-fg);\n color: var(--wello-notifications-bg);\n }\n .wello-notifications-action:hover {\n filter: brightness(1.08);\n }\n .wello-notifications-cancel {\n background: transparent;\n border-color: var(--wello-notifications-border);\n color: var(--wello-notifications-fg);\n }\n .wello-notifications-cancel:hover {\n background: var(--wello-notifications-hover);\n }\n .wello-notifications-button:focus-visible,\n .wello-notifications-close:focus-visible {\n outline: 2px solid var(--wello-notifications-tone);\n outline-offset: 2px;\n }\n .wello-notifications-close {\n display: flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n margin: -2px 0;\n padding: 0;\n border: 0;\n border-radius: 10px;\n background: transparent;\n color: var(--wello-notifications-muted);\n font-size: 16px;\n cursor: pointer;\n transition: background-color 120ms ease, color 120ms ease;\n }\n .wello-notifications-close:hover {\n background: var(--wello-notifications-hover);\n color: var(--wello-notifications-fg);\n }\n .wello-notifications-progress {\n position: absolute;\n inset-inline: 0;\n bottom: 0;\n height: 3px;\n background: var(--wello-notifications-tone);\n opacity: 0.45;\n transform-origin: left;\n animation-name: wello-notifications-progress;\n animation-timing-function: linear;\n animation-fill-mode: forwards;\n }\n [dir=rtl] .wello-notifications-progress {\n transform-origin: right;\n }\n .wello-notifications-spin {\n animation: wello-notifications-spin 0.9s linear infinite;\n }\n @keyframes wello-notifications-in {\n from {\n opacity: 0;\n translate: 0 var(--wello-notifications-enter, 14px);\n scale: 0.96;\n }\n }\n @keyframes wello-notifications-progress {\n from {\n transform: scaleX(1);\n }\n to {\n transform: scaleX(0);\n }\n }\n @keyframes wello-notifications-spin {\n to {\n transform: rotate(360deg);\n }\n }\n @media (prefers-reduced-motion: reduce) {\n .wello-notifications-toast,\n .wello-notifications-custom,\n .wello-notifications-item {\n animation-duration: 1ms;\n transition-duration: 1ms;\n transition-delay: 0ms;\n }\n .wello-notifications-spin {\n animation-duration: 2.5s;\n }\n }\n}\n');
70
+
71
+ // src/core/store.ts
72
+ var state = { toasts: [], paused: false };
73
+ var listeners = /* @__PURE__ */ new Set();
74
+ var counter = 0;
75
+ function emit(next) {
76
+ state = next;
77
+ listeners.forEach((listener) => listener());
78
+ }
79
+ var toastStore = {
80
+ getState: () => state,
81
+ subscribe(listener) {
82
+ listeners.add(listener);
83
+ return () => {
84
+ listeners.delete(listener);
85
+ };
86
+ },
87
+ /** `true` when at least one `<Toaster>` is mounted */
88
+ hasToaster: () => listeners.size > 0,
89
+ nextId: () => `wello-notifications-${++counter}`,
90
+ /** Adds a toast, or replaces the open one with the same id. */
91
+ upsert(record) {
92
+ const existing = state.toasts.find((t) => t.id === record.id);
93
+ if (existing?.open) {
94
+ emit({
95
+ ...state,
96
+ toasts: state.toasts.map(
97
+ (t) => t.id === record.id ? { ...record, version: t.version + 1, createdAt: t.createdAt, open: true } : t
98
+ )
99
+ });
100
+ } else {
101
+ const fresh = { ...record, version: 0, createdAt: Date.now(), open: true };
102
+ emit({ ...state, toasts: [fresh, ...state.toasts.filter((t) => t.id !== record.id)] });
103
+ }
104
+ return record.id;
105
+ },
106
+ /** Merges changes into an open toast. Returns false when there is none with this id. */
107
+ patch(id, changes) {
108
+ const existing = state.toasts.find((t) => t.id === id && t.open);
109
+ if (!existing) return false;
110
+ emit({
111
+ ...state,
112
+ toasts: state.toasts.map(
113
+ (t) => t === existing ? { ...t, ...changes, id, version: t.version + 1, open: true } : t
114
+ )
115
+ });
116
+ return true;
117
+ },
118
+ /** Starts closing one toast, or all of them. */
119
+ dismiss(id, reason = "close") {
120
+ const closing = state.toasts.filter((t) => t.open && (id === void 0 || t.id === id));
121
+ if (!closing.length) return;
122
+ const keep = toastStore.hasToaster();
123
+ emit({
124
+ ...state,
125
+ toasts: keep ? state.toasts.map((t) => closing.includes(t) ? { ...t, open: false } : t) : state.toasts.filter((t) => !closing.includes(t))
126
+ });
127
+ for (const t of closing) {
128
+ if (reason === "auto") t.onAutoClose?.(t);
129
+ t.onClose?.(t);
130
+ }
131
+ },
132
+ /** Removes a toast right away (after its exit animation). */
133
+ remove(id) {
134
+ if (!state.toasts.some((t) => t.id === id)) return;
135
+ emit({ ...state, toasts: state.toasts.filter((t) => t.id !== id) });
136
+ },
137
+ setPaused(paused) {
138
+ if (state.paused !== paused) emit({ ...state, paused });
139
+ },
140
+ /** Test helper: forget every toast. */
141
+ reset() {
142
+ emit({ toasts: [], paused: false });
143
+ }
144
+ };
145
+
146
+ // src/core/toast.ts
147
+ function toRecord(type, message, options = {}) {
148
+ const { id, title, description, autoClose, duration, ...rest } = options;
149
+ const resolvedTitle = title ?? message;
150
+ const resolvedDescription = description ?? (title != null && title !== message ? message : void 0);
151
+ return {
152
+ ...rest,
153
+ id: id ?? toastStore.nextId(),
154
+ type,
155
+ title: resolvedTitle,
156
+ description: resolvedDescription,
157
+ duration: duration ?? autoClose
158
+ };
159
+ }
160
+ function show(type, message, options) {
161
+ return toastStore.upsert(toRecord(type, message, options));
162
+ }
163
+ var typed = (type) => (message, options) => show(type, message, options);
164
+ function promise(source, messages, options) {
165
+ const id = show("loading", messages.loading, options);
166
+ const run = typeof source === "function" ? source() : source;
167
+ const settle = (type, content) => toastStore.upsert(toRecord(type, content, { ...options, id }));
168
+ return run.then(
169
+ (data) => {
170
+ settle("success", typeof messages.success === "function" ? messages.success(data) : messages.success);
171
+ messages.finally?.();
172
+ return data;
173
+ },
174
+ (error) => {
175
+ settle("error", typeof messages.error === "function" ? messages.error(error) : messages.error);
176
+ messages.finally?.();
177
+ throw error;
178
+ }
179
+ );
180
+ }
181
+ function update(id, changes) {
182
+ if (changes !== null && typeof changes === "object" && !isReactElement(changes)) {
183
+ const { id: _ignored, autoClose, duration, ...rest } = changes;
184
+ const patch = { ...rest };
185
+ if (duration !== void 0 || autoClose !== void 0) patch.duration = duration ?? autoClose;
186
+ return toastStore.patch(id, patch);
187
+ }
188
+ return toastStore.patch(id, { title: changes });
189
+ }
190
+ function isReactElement(value) {
191
+ return "$$typeof" in value;
192
+ }
193
+ var dismiss = (id) => toastStore.dismiss(id);
194
+ var toast = Object.assign(typed("default"), {
195
+ success: typed("success"),
196
+ error: typed("error"),
197
+ /** Same as `error` */
198
+ danger: typed("error"),
199
+ warning: typed("warning"),
200
+ info: typed("info"),
201
+ loading: typed("loading"),
202
+ /** Same as `toast()` */
203
+ message: typed("default"),
204
+ /** Fully custom content; `render` gets the toast (use `toast.dismiss(t.id)` to close it). */
205
+ custom: (render, options) => toastStore.upsert({ ...toRecord("default", null, options), title: void 0, render }),
206
+ promise,
207
+ update,
208
+ /** Closes one toast, or all of them without an id */
209
+ dismiss,
210
+ /** Same as `dismiss` */
211
+ close: dismiss,
212
+ /** Closes every toast */
213
+ clear: () => toastStore.dismiss(),
214
+ /** Stops every timer, e.g. while a dialog is open */
215
+ pauseAll: () => toastStore.setPaused(true),
216
+ resumeAll: () => toastStore.setPaused(false),
217
+ /** `true` while the toast is open */
218
+ isActive: (id) => toastStore.getState().toasts.some((t) => t.id === id && t.open)
219
+ });
220
+
221
+ // src/core/use-toasts.ts
222
+ var import_react = require("react");
223
+ var NONE = [];
224
+ function useToasts() {
225
+ return (0, import_react.useSyncExternalStore)(
226
+ toastStore.subscribe,
227
+ () => toastStore.getState().toasts,
228
+ () => NONE
229
+ ).filter((t) => t.open);
230
+ }
231
+
232
+ // src/components/toaster.tsx
233
+ var import_react3 = require("react");
234
+ var import_react_dom = require("react-dom");
235
+
236
+ // src/lib/colors.ts
237
+ var PALETTE = {
238
+ slate: [
239
+ "#f8fafc",
240
+ "#f1f5f9",
241
+ "#e2e8f0",
242
+ "#cbd5e1",
243
+ "#94a3b8",
244
+ "#64748b",
245
+ "#475569",
246
+ "#334155",
247
+ "#1e293b",
248
+ "#0f172a",
249
+ "#020617"
250
+ ],
251
+ gray: [
252
+ "#f9fafb",
253
+ "#f3f4f6",
254
+ "#e5e7eb",
255
+ "#d1d5db",
256
+ "#9ca3af",
257
+ "#6b7280",
258
+ "#4b5563",
259
+ "#374151",
260
+ "#1f2937",
261
+ "#111827",
262
+ "#030712"
263
+ ],
264
+ zinc: [
265
+ "#fafafa",
266
+ "#f4f4f5",
267
+ "#e4e4e7",
268
+ "#d4d4d8",
269
+ "#a1a1aa",
270
+ "#71717a",
271
+ "#52525b",
272
+ "#3f3f46",
273
+ "#27272a",
274
+ "#18181b",
275
+ "#09090b"
276
+ ],
277
+ neutral: [
278
+ "#fafafa",
279
+ "#f5f5f5",
280
+ "#e5e5e5",
281
+ "#d4d4d4",
282
+ "#a3a3a3",
283
+ "#737373",
284
+ "#525252",
285
+ "#404040",
286
+ "#262626",
287
+ "#171717",
288
+ "#0a0a0a"
289
+ ],
290
+ stone: [
291
+ "#fafaf9",
292
+ "#f5f5f4",
293
+ "#e7e5e4",
294
+ "#d6d3d1",
295
+ "#a8a29e",
296
+ "#78716c",
297
+ "#57534e",
298
+ "#44403c",
299
+ "#292524",
300
+ "#1c1917",
301
+ "#0c0a09"
302
+ ],
303
+ red: [
304
+ "#fef2f2",
305
+ "#fee2e2",
306
+ "#fecaca",
307
+ "#fca5a5",
308
+ "#f87171",
309
+ "#ef4444",
310
+ "#dc2626",
311
+ "#b91c1c",
312
+ "#991b1b",
313
+ "#7f1d1d",
314
+ "#450a0a"
315
+ ],
316
+ orange: [
317
+ "#fff7ed",
318
+ "#ffedd5",
319
+ "#fed7aa",
320
+ "#fdba74",
321
+ "#fb923c",
322
+ "#f97316",
323
+ "#ea580c",
324
+ "#c2410c",
325
+ "#9a3412",
326
+ "#7c2d12",
327
+ "#431407"
328
+ ],
329
+ amber: [
330
+ "#fffbeb",
331
+ "#fef3c7",
332
+ "#fde68a",
333
+ "#fcd34d",
334
+ "#fbbf24",
335
+ "#f59e0b",
336
+ "#d97706",
337
+ "#b45309",
338
+ "#92400e",
339
+ "#78350f",
340
+ "#451a03"
341
+ ],
342
+ yellow: [
343
+ "#fefce8",
344
+ "#fef9c3",
345
+ "#fef08a",
346
+ "#fde047",
347
+ "#facc15",
348
+ "#eab308",
349
+ "#ca8a04",
350
+ "#a16207",
351
+ "#854d0e",
352
+ "#713f12",
353
+ "#422006"
354
+ ],
355
+ lime: [
356
+ "#f7fee7",
357
+ "#ecfccb",
358
+ "#d9f99d",
359
+ "#bef264",
360
+ "#a3e635",
361
+ "#84cc16",
362
+ "#65a30d",
363
+ "#4d7c0f",
364
+ "#3f6212",
365
+ "#365314",
366
+ "#1a2e05"
367
+ ],
368
+ green: [
369
+ "#f0fdf4",
370
+ "#dcfce7",
371
+ "#bbf7d0",
372
+ "#86efac",
373
+ "#4ade80",
374
+ "#22c55e",
375
+ "#16a34a",
376
+ "#15803d",
377
+ "#166534",
378
+ "#14532d",
379
+ "#052e16"
380
+ ],
381
+ emerald: [
382
+ "#ecfdf5",
383
+ "#d1fae5",
384
+ "#a7f3d0",
385
+ "#6ee7b7",
386
+ "#34d399",
387
+ "#10b981",
388
+ "#059669",
389
+ "#047857",
390
+ "#065f46",
391
+ "#064e3b",
392
+ "#022c22"
393
+ ],
394
+ teal: [
395
+ "#f0fdfa",
396
+ "#ccfbf1",
397
+ "#99f6e4",
398
+ "#5eead4",
399
+ "#2dd4bf",
400
+ "#14b8a6",
401
+ "#0d9488",
402
+ "#0f766e",
403
+ "#115e59",
404
+ "#134e4a",
405
+ "#042f2e"
406
+ ],
407
+ cyan: [
408
+ "#ecfeff",
409
+ "#cffafe",
410
+ "#a5f3fc",
411
+ "#67e8f9",
412
+ "#22d3ee",
413
+ "#06b6d4",
414
+ "#0891b2",
415
+ "#0e7490",
416
+ "#155e75",
417
+ "#164e63",
418
+ "#083344"
419
+ ],
420
+ sky: [
421
+ "#f0f9ff",
422
+ "#e0f2fe",
423
+ "#bae6fd",
424
+ "#7dd3fc",
425
+ "#38bdf8",
426
+ "#0ea5e9",
427
+ "#0284c7",
428
+ "#0369a1",
429
+ "#075985",
430
+ "#0c4a6e",
431
+ "#082f49"
432
+ ],
433
+ blue: [
434
+ "#eff6ff",
435
+ "#dbeafe",
436
+ "#bfdbfe",
437
+ "#93c5fd",
438
+ "#60a5fa",
439
+ "#3b82f6",
440
+ "#2563eb",
441
+ "#1d4ed8",
442
+ "#1e40af",
443
+ "#1e3a8a",
444
+ "#172554"
445
+ ],
446
+ indigo: [
447
+ "#eef2ff",
448
+ "#e0e7ff",
449
+ "#c7d2fe",
450
+ "#a5b4fc",
451
+ "#818cf8",
452
+ "#6366f1",
453
+ "#4f46e5",
454
+ "#4338ca",
455
+ "#3730a3",
456
+ "#312e81",
457
+ "#1e1b4b"
458
+ ],
459
+ violet: [
460
+ "#f5f3ff",
461
+ "#ede9fe",
462
+ "#ddd6fe",
463
+ "#c4b5fd",
464
+ "#a78bfa",
465
+ "#8b5cf6",
466
+ "#7c3aed",
467
+ "#6d28d9",
468
+ "#5b21b6",
469
+ "#4c1d95",
470
+ "#2e1065"
471
+ ],
472
+ purple: [
473
+ "#faf5ff",
474
+ "#f3e8ff",
475
+ "#e9d5ff",
476
+ "#d8b4fe",
477
+ "#c084fc",
478
+ "#a855f7",
479
+ "#9333ea",
480
+ "#7e22ce",
481
+ "#6b21a8",
482
+ "#581c87",
483
+ "#3b0764"
484
+ ],
485
+ fuchsia: [
486
+ "#fdf4ff",
487
+ "#fae8ff",
488
+ "#f5d0fe",
489
+ "#f0abfc",
490
+ "#e879f9",
491
+ "#d946ef",
492
+ "#c026d3",
493
+ "#a21caf",
494
+ "#86198f",
495
+ "#701a75",
496
+ "#4a044e"
497
+ ],
498
+ pink: [
499
+ "#fdf2f8",
500
+ "#fce7f3",
501
+ "#fbcfe8",
502
+ "#f9a8d4",
503
+ "#f472b6",
504
+ "#ec4899",
505
+ "#db2777",
506
+ "#be185d",
507
+ "#9d174d",
508
+ "#831843",
509
+ "#500724"
510
+ ],
511
+ rose: [
512
+ "#fff1f2",
513
+ "#ffe4e6",
514
+ "#fecdd3",
515
+ "#fda4af",
516
+ "#fb7185",
517
+ "#f43f5e",
518
+ "#e11d48",
519
+ "#be123c",
520
+ "#9f1239",
521
+ "#881337",
522
+ "#4c0519"
523
+ ]
524
+ };
525
+ var SHADES = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950];
526
+ function resolveColor(color) {
527
+ const match = /^([a-z]+)(?:-(\d{2,3}))?$/.exec(color);
528
+ const palette = match && PALETTE[match[1]];
529
+ if (!palette) return color;
530
+ const index = SHADES.indexOf(match[2] === void 0 ? 500 : Number(match[2]));
531
+ return index === -1 ? color : palette[index];
532
+ }
533
+
534
+ // src/lib/labels.ts
535
+ var LABELS = {
536
+ en: { region: "Notifications", close: "Close" },
537
+ ar: { region: "\u0627\u0644\u0625\u0634\u0639\u0627\u0631\u0627\u062A", close: "\u0625\u063A\u0644\u0627\u0642" }
538
+ };
539
+ function resolveLabels(locale = "en", overrides) {
540
+ return { ...LABELS[locale] ?? LABELS.en, ...overrides };
541
+ }
542
+
543
+ // src/lib/utils.ts
544
+ function cx(...parts) {
545
+ return parts.filter(Boolean).join(" ");
546
+ }
547
+ var toCssSize = (value) => typeof value === "number" ? `${value}px` : value;
548
+
549
+ // src/components/icons.tsx
550
+ var import_jsx_runtime = require("react/jsx-runtime");
551
+ function icon(paths) {
552
+ return function Icon(props) {
553
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
554
+ "svg",
555
+ {
556
+ viewBox: "0 0 24 24",
557
+ width: "1em",
558
+ height: "1em",
559
+ fill: "none",
560
+ stroke: "currentColor",
561
+ strokeWidth: 2.25,
562
+ strokeLinecap: "round",
563
+ strokeLinejoin: "round",
564
+ "aria-hidden": "true",
565
+ ...props,
566
+ children: paths
567
+ }
568
+ );
569
+ };
570
+ }
571
+ var SuccessIcon = icon(
572
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
573
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
574
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m9 12 2 2 4-4" })
575
+ ] })
576
+ );
577
+ var ErrorIcon = icon(
578
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
579
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
580
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m15 9-6 6M9 9l6 6" })
581
+ ] })
582
+ );
583
+ var WarningIcon = icon(
584
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
585
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
586
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 8v4M12 16h.01" })
587
+ ] })
588
+ );
589
+ var InfoIcon = icon(
590
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
591
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
592
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 16v-4M12 8h.01" })
593
+ ] })
594
+ );
595
+ var LoadingIcon = icon(/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }));
596
+ var CloseIcon = icon(/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M18 6 6 18M6 6l12 12" }));
597
+ var DEFAULT_ICONS = {
598
+ success: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SuccessIcon, {}),
599
+ error: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorIcon, {}),
600
+ warning: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(WarningIcon, {}),
601
+ info: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InfoIcon, {}),
602
+ loading: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoadingIcon, { className: "wello-notifications-spin" })
603
+ };
604
+
605
+ // src/components/toast-item.tsx
606
+ var import_react2 = require("react");
607
+
608
+ // src/sound/notification-sound.ts
609
+ var TIMBRES = {
610
+ // Warm bell: octave and a slightly inharmonic upper partial for shimmer.
611
+ bell: [
612
+ { ratio: 1, gain: 1, decay: 1 },
613
+ { ratio: 2, gain: 0.28, decay: 0.55 },
614
+ { ratio: 3.01, gain: 0.1, decay: 0.32 },
615
+ { ratio: 5.43, gain: 0.035, decay: 0.16 }
616
+ ],
617
+ // Wooden mallet: strong fundamental, quick bright "knock" on top.
618
+ marimba: [
619
+ { ratio: 1, gain: 1, decay: 1 },
620
+ { ratio: 3.98, gain: 0.16, decay: 0.12 },
621
+ { ratio: 9.8, gain: 0.03, decay: 0.05 }
622
+ ],
623
+ // Soft, darker string pluck for errors.
624
+ pluck: [
625
+ { ratio: 1, gain: 1, decay: 1, type: "triangle" },
626
+ { ratio: 2, gain: 0.18, decay: 0.4 },
627
+ { ratio: 0.5, gain: 0.22, decay: 0.7 }
628
+ ],
629
+ // Thin and bright, cuts through for urgent alerts.
630
+ glass: [
631
+ { ratio: 1, gain: 1, decay: 1 },
632
+ { ratio: 2.76, gain: 0.22, decay: 0.45 },
633
+ { ratio: 5.4, gain: 0.07, decay: 0.2 }
634
+ ]
635
+ };
636
+ var N = {
637
+ G4: 392,
638
+ A4: 440,
639
+ C5: 523.25,
640
+ D5: 587.33,
641
+ E5: 659.25,
642
+ F5: 698.46,
643
+ G5: 783.99,
644
+ A5: 880,
645
+ B5: 987.77,
646
+ C6: 1046.5,
647
+ E6: 1318.51,
648
+ G6: 1567.98
649
+ };
650
+ var SOUNDS = {
651
+ // Rising major arpeggio: bright and "done".
652
+ success: [
653
+ { f: N.C5, at: 0, dur: 0.5, gain: 0.7, timbre: "marimba" },
654
+ { f: N.E5, at: 0.075, dur: 0.5, gain: 0.7, timbre: "marimba" },
655
+ { f: N.G5, at: 0.15, dur: 0.55, gain: 0.75, timbre: "marimba" },
656
+ { f: N.C6, at: 0.225, dur: 0.9, gain: 0.6, timbre: "bell" }
657
+ ],
658
+ // Falling minor step, low and soft: noticeable without being harsh.
659
+ error: [
660
+ { f: N.E5, at: 0, dur: 0.35, gain: 0.75, timbre: "pluck" },
661
+ { f: N.C5, at: 0.13, dur: 0.6, gain: 0.85, timbre: "pluck" }
662
+ ],
663
+ // Two even knocks: "look here".
664
+ warning: [
665
+ { f: N.D5, at: 0, dur: 0.32, gain: 0.85, timbre: "marimba" },
666
+ { f: N.D5, at: 0.17, dur: 0.45, gain: 0.85, timbre: "marimba" }
667
+ ],
668
+ // A single soft bell with its octave.
669
+ info: [
670
+ { f: N.A5, at: 0, dur: 1.1, gain: 0.95, timbre: "bell" },
671
+ { f: N.E6, at: 0.03, dur: 0.6, gain: 0.25, timbre: "bell" }
672
+ ],
673
+ // Chat-style "ding-dong".
674
+ message: [
675
+ { f: N.E6, at: 0, dur: 0.6, gain: 0.55, timbre: "bell" },
676
+ { f: N.B5, at: 0.12, dur: 1, gain: 0.65, timbre: "bell" }
677
+ ],
678
+ // Three quick rising pairs: urgent, repeats so it isn't missed.
679
+ critical: [0, 0.32, 0.64].flatMap((start) => [
680
+ { f: N.A5, at: start, dur: 0.22, gain: 0.7, timbre: "glass" },
681
+ { f: N.E6, at: start + 0.1, dur: 0.3, gain: 0.7, timbre: "glass" }
682
+ ]),
683
+ // Tiny bubble pop for plain toasts.
684
+ pop: [{ f: N.G5, at: 0, dur: 0.3, gain: 2.2, timbre: "marimba", glide: 1.6 }]
685
+ };
686
+ var PRIORITY_SOUNDS = {
687
+ critical: "critical",
688
+ high: "message",
689
+ normal: "info",
690
+ low: "pop"
691
+ };
692
+ var isSoundName = (value) => value in SOUNDS;
693
+ var toSoundName = (name) => isSoundName(name) ? name : PRIORITY_SOUNDS[name] ?? "info";
694
+ var ATTACK = 6e-3;
695
+ function scheduleNote(ctx, out, note, start) {
696
+ const t0 = start + note.at;
697
+ for (const partial of TIMBRES[note.timbre]) {
698
+ const frequency = note.f * partial.ratio;
699
+ if (frequency > 16e3) continue;
700
+ const peak = (note.gain ?? 1) * partial.gain;
701
+ const end = t0 + ATTACK + Math.max(0.04, note.dur * partial.decay);
702
+ const oscillator = ctx.createOscillator();
703
+ const envelope = ctx.createGain();
704
+ oscillator.type = partial.type ?? "sine";
705
+ if (note.glide) {
706
+ oscillator.frequency.setValueAtTime(frequency * note.glide, t0);
707
+ oscillator.frequency.exponentialRampToValueAtTime(frequency, t0 + 0.04);
708
+ } else {
709
+ oscillator.frequency.setValueAtTime(frequency, t0);
710
+ }
711
+ envelope.gain.setValueAtTime(1e-4, t0);
712
+ envelope.gain.exponentialRampToValueAtTime(peak, t0 + ATTACK);
713
+ envelope.gain.exponentialRampToValueAtTime(1e-4, end);
714
+ oscillator.connect(envelope);
715
+ envelope.connect(out);
716
+ oscillator.start(t0);
717
+ oscillator.stop(end + 0.02);
718
+ }
719
+ }
720
+ function createRoom(ctx) {
721
+ const seconds = 1.2;
722
+ const length = Math.floor(ctx.sampleRate * seconds);
723
+ const buffer = ctx.createBuffer(2, length, ctx.sampleRate);
724
+ for (let channel = 0; channel < 2; channel++) {
725
+ const data = buffer.getChannelData(channel);
726
+ for (let i = 0; i < length; i++) {
727
+ data[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / length, 3.5);
728
+ }
729
+ }
730
+ return buffer;
731
+ }
732
+ var buses = /* @__PURE__ */ new WeakMap();
733
+ function bus(ctx) {
734
+ const existing = buses.get(ctx);
735
+ if (existing) return existing;
736
+ const input = ctx.createGain();
737
+ const tone = ctx.createBiquadFilter();
738
+ tone.type = "lowpass";
739
+ tone.frequency.value = 7500;
740
+ const reverb = ctx.createConvolver();
741
+ reverb.buffer = createRoom(ctx);
742
+ const wet = ctx.createGain();
743
+ wet.gain.value = 0.16;
744
+ const compressor = ctx.createDynamicsCompressor();
745
+ compressor.threshold.value = -16;
746
+ compressor.knee.value = 10;
747
+ compressor.ratio.value = 4;
748
+ compressor.attack.value = 2e-3;
749
+ compressor.release.value = 0.2;
750
+ input.connect(tone);
751
+ tone.connect(compressor);
752
+ tone.connect(reverb);
753
+ reverb.connect(wet);
754
+ wet.connect(compressor);
755
+ compressor.connect(ctx.destination);
756
+ const created = { input };
757
+ buses.set(ctx, created);
758
+ return created;
759
+ }
760
+ function scheduleSound(ctx, name, volume = 0.7, when = ctx.currentTime + 0.01) {
761
+ const notes = SOUNDS[toSoundName(name)];
762
+ const level = ctx.createGain();
763
+ level.gain.value = Math.min(1, Math.max(0, volume)) * 0.32;
764
+ level.connect(bus(ctx).input);
765
+ for (const note of notes) scheduleNote(ctx, level, note, when);
766
+ return Math.max(...notes.map((n) => n.at + n.dur)) + 1.2;
767
+ }
768
+ var context = null;
769
+ var buffers = /* @__PURE__ */ new Map();
770
+ function audioContext() {
771
+ if (typeof window === "undefined") return null;
772
+ try {
773
+ const Ctor = window.AudioContext ?? window.webkitAudioContext;
774
+ if (!Ctor) return null;
775
+ context ?? (context = new Ctor());
776
+ if (context.state === "suspended") void context.resume().catch(() => {
777
+ });
778
+ return context;
779
+ } catch {
780
+ return null;
781
+ }
782
+ }
783
+ function playSound(name = "info", options = {}) {
784
+ const ctx = audioContext();
785
+ if (!ctx) return;
786
+ try {
787
+ scheduleSound(ctx, name, options.volume ?? 0.7);
788
+ } catch {
789
+ }
790
+ }
791
+ function playTone(priority = "normal", volume = 0.7) {
792
+ playSound(priority, { volume });
793
+ }
794
+ var NOTIFICATION_SOUNDS = Object.keys(SOUNDS);
795
+ async function playNotificationSound(url, options = {}) {
796
+ const { volume = 0.7, fallback = "info" } = options;
797
+ if (url && isSoundName(url)) return playSound(url, { volume });
798
+ if (url) {
799
+ const ctx = audioContext();
800
+ if (ctx) {
801
+ try {
802
+ let buffer = buffers.get(url);
803
+ if (!buffer) {
804
+ buffer = fetch(url).then((response) => {
805
+ if (!response.ok) throw new Error(`${response.status}`);
806
+ return response.arrayBuffer();
807
+ }).then((data) => ctx.decodeAudioData(data));
808
+ buffers.set(url, buffer);
809
+ buffer.catch(() => buffers.delete(url));
810
+ }
811
+ const source = ctx.createBufferSource();
812
+ const gain = ctx.createGain();
813
+ gain.gain.value = volume;
814
+ source.buffer = await buffer;
815
+ source.connect(gain);
816
+ gain.connect(ctx.destination);
817
+ source.start(0);
818
+ return;
819
+ } catch {
820
+ }
821
+ }
822
+ try {
823
+ const audio = new Audio(url);
824
+ audio.volume = volume;
825
+ await audio.play();
826
+ return;
827
+ } catch {
828
+ }
829
+ }
830
+ if (fallback) playSound(fallback, { volume });
831
+ }
832
+
833
+ // src/components/toast-item.tsx
834
+ var import_jsx_runtime2 = require("react/jsx-runtime");
835
+ var EXIT_MS = 320;
836
+ var SWIPE_THRESHOLD = 64;
837
+ var TYPE_SOUNDS = {
838
+ success: "success",
839
+ error: "error",
840
+ warning: "warning",
841
+ info: "info",
842
+ default: "pop"
843
+ };
844
+ function ToastItem({ toast: toast2, settings, paused, labels }) {
845
+ const { id, open, version, type } = toast2;
846
+ const { duration, classNames, styles } = settings;
847
+ const cardRef = (0, import_react2.useRef)(null);
848
+ const [swipedOut, setSwipedOut] = (0, import_react2.useState)(null);
849
+ const part = (name, base) => ({
850
+ className: cx(base, classNames[name]),
851
+ style: styles[name]
852
+ });
853
+ const opened = (0, import_react2.useRef)(false);
854
+ (0, import_react2.useEffect)(() => {
855
+ if (opened.current) return;
856
+ opened.current = true;
857
+ toast2.onOpen?.(toast2);
858
+ }, []);
859
+ const soundedType = (0, import_react2.useRef)(null);
860
+ (0, import_react2.useEffect)(() => {
861
+ const sound = settings.sound;
862
+ if (!sound || !open || type === "loading" || soundedType.current === type) return;
863
+ soundedType.current = type;
864
+ const builtIn = TYPE_SOUNDS[type] ?? "info";
865
+ void playNotificationSound(typeof sound === "string" ? sound : builtIn, { fallback: builtIn });
866
+ }, [type, open]);
867
+ const remaining = (0, import_react2.useRef)(duration);
868
+ const lastVersion = (0, import_react2.useRef)(version);
869
+ if (lastVersion.current !== version) {
870
+ lastVersion.current = version;
871
+ remaining.current = duration;
872
+ }
873
+ (0, import_react2.useEffect)(() => {
874
+ const left = remaining.current;
875
+ if (!open || paused || left === false || !Number.isFinite(left)) return;
876
+ const startedAt = Date.now();
877
+ const timer = setTimeout(() => toastStore.dismiss(id, "auto"), Math.max(0, left));
878
+ return () => {
879
+ clearTimeout(timer);
880
+ if (lastVersion.current === version) {
881
+ remaining.current = Math.max(0, left - (Date.now() - startedAt));
882
+ }
883
+ };
884
+ }, [id, open, paused, version, duration]);
885
+ (0, import_react2.useEffect)(() => {
886
+ if (open) return;
887
+ const timer = setTimeout(() => toastStore.remove(id), EXIT_MS);
888
+ return () => clearTimeout(timer);
889
+ }, [id, open]);
890
+ const drag = (0, import_react2.useRef)(null);
891
+ const canSwipe = settings.swipe && settings.dismissible && open;
892
+ function onPointerDown(event) {
893
+ if (!canSwipe || event.button !== 0) return;
894
+ if (event.target.closest("button, a, input, textarea, select")) return;
895
+ drag.current = { x: event.clientX, y: event.clientY, pointer: event.pointerId, dx: 0 };
896
+ }
897
+ function onPointerMove(event) {
898
+ const state2 = drag.current;
899
+ const card = cardRef.current;
900
+ if (!state2 || !card || event.pointerId !== state2.pointer) return;
901
+ const dx = event.clientX - state2.x;
902
+ if (state2.dx === 0 && Math.abs(dx) < 6) return;
903
+ if (state2.dx === 0 && Math.abs(event.clientY - state2.y) > Math.abs(dx)) {
904
+ drag.current = null;
905
+ return;
906
+ }
907
+ if (state2.dx === 0) card.setPointerCapture(event.pointerId);
908
+ state2.dx = dx;
909
+ card.dataset.swiping = "";
910
+ card.style.setProperty("--wello-notifications-swipe", `${dx}px`);
911
+ card.style.opacity = String(Math.max(0.2, 1 - Math.abs(dx) / 220));
912
+ }
913
+ function onPointerUp(event) {
914
+ const state2 = drag.current;
915
+ const card = cardRef.current;
916
+ drag.current = null;
917
+ if (!state2 || !card || event.pointerId !== state2.pointer) return;
918
+ delete card.dataset.swiping;
919
+ if (Math.abs(state2.dx) >= SWIPE_THRESHOLD) {
920
+ setSwipedOut(state2.dx > 0 ? "end" : "start");
921
+ toastStore.dismiss(id);
922
+ return;
923
+ }
924
+ card.style.removeProperty("--wello-notifications-swipe");
925
+ card.style.opacity = "";
926
+ }
927
+ function onKeyDown(event) {
928
+ if (event.key === "Escape" && settings.dismissible) {
929
+ event.stopPropagation();
930
+ toastStore.dismiss(id);
931
+ }
932
+ }
933
+ const important = toast2.important ?? type === "error";
934
+ const tone = { "--wello-notifications-tone": settings.color };
935
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
936
+ "li",
937
+ {
938
+ className: "wello-notifications-item",
939
+ "data-state": open ? "open" : "closing",
940
+ "data-swiped": swipedOut ?? void 0,
941
+ onKeyDown,
942
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "wello-notifications-item-inner", children: toast2.render ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
943
+ "div",
944
+ {
945
+ ref: cardRef,
946
+ className: cx("wello-notifications-custom", settings.className),
947
+ style: { ...tone, ...settings.style },
948
+ role: important ? "alert" : "status",
949
+ "aria-live": important ? "assertive" : "polite",
950
+ "aria-atomic": "true",
951
+ "data-type": type,
952
+ onPointerDown,
953
+ onPointerMove,
954
+ onPointerUp,
955
+ onPointerCancel: onPointerUp,
956
+ children: toast2.render(toast2)
957
+ }
958
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
959
+ "div",
960
+ {
961
+ ref: cardRef,
962
+ className: cx("wello-notifications-toast", classNames.toast, settings.className),
963
+ style: { ...tone, ...styles.toast, ...settings.style },
964
+ role: important ? "alert" : "status",
965
+ "aria-live": important ? "assertive" : "polite",
966
+ "aria-atomic": "true",
967
+ "data-type": type,
968
+ "data-dismissible": settings.dismissible || void 0,
969
+ onPointerDown,
970
+ onPointerMove,
971
+ onPointerUp,
972
+ onPointerCancel: onPointerUp,
973
+ children: [
974
+ settings.icon != null && settings.icon !== false && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { ...part("icon", "wello-notifications-icon"), children: settings.icon }),
975
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { ...part("content", "wello-notifications-content"), children: [
976
+ toast2.title != null && toast2.title !== false && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ...part("title", "wello-notifications-title"), children: toast2.title }),
977
+ toast2.description != null && toast2.description !== false && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ...part("description", "wello-notifications-description"), children: toast2.description }),
978
+ (toast2.action || toast2.cancel) && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { ...part("actions", "wello-notifications-actions"), children: [
979
+ toast2.cancel && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
980
+ ActionButton,
981
+ {
982
+ action: toast2.cancel,
983
+ id,
984
+ ...part("cancel", "wello-notifications-button wello-notifications-cancel")
985
+ }
986
+ ),
987
+ toast2.action && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
988
+ ActionButton,
989
+ {
990
+ action: toast2.action,
991
+ id,
992
+ ...part("action", "wello-notifications-button wello-notifications-action")
993
+ }
994
+ )
995
+ ] })
996
+ ] }),
997
+ settings.dismissible && settings.closeButton && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
998
+ "button",
999
+ {
1000
+ type: "button",
1001
+ ...part("closeButton", "wello-notifications-close"),
1002
+ "aria-label": labels.close,
1003
+ title: labels.close,
1004
+ onClick: () => toastStore.dismiss(id),
1005
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CloseIcon, {})
1006
+ }
1007
+ ),
1008
+ settings.progress && duration !== false && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1009
+ "span",
1010
+ {
1011
+ ...part("progress", "wello-notifications-progress"),
1012
+ style: {
1013
+ animationDuration: `${duration}ms`,
1014
+ animationPlayState: paused || !open ? "paused" : "running",
1015
+ ...styles.progress
1016
+ },
1017
+ "aria-hidden": "true"
1018
+ },
1019
+ version
1020
+ )
1021
+ ]
1022
+ }
1023
+ ) })
1024
+ }
1025
+ );
1026
+ }
1027
+ function ActionButton({
1028
+ action,
1029
+ id,
1030
+ className,
1031
+ style
1032
+ }) {
1033
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1034
+ "button",
1035
+ {
1036
+ type: "button",
1037
+ className,
1038
+ style,
1039
+ onClick: (event) => {
1040
+ action.onClick?.(event);
1041
+ if (action.closeOnClick !== false && !event.defaultPrevented) toastStore.dismiss(id);
1042
+ },
1043
+ children: action.label
1044
+ }
1045
+ );
1046
+ }
1047
+
1048
+ // src/components/toaster.tsx
1049
+ var import_jsx_runtime3 = require("react/jsx-runtime");
1050
+ var DEFAULT_COLORS = {
1051
+ default: "slate-500",
1052
+ success: "emerald-600",
1053
+ error: "red-600",
1054
+ warning: "amber-600",
1055
+ info: "blue-600",
1056
+ loading: "slate-500"
1057
+ };
1058
+ var DEFAULT_DURATIONS = {
1059
+ error: 5500,
1060
+ warning: 5e3,
1061
+ loading: false
1062
+ };
1063
+ var EMPTY = { toasts: [], paused: false };
1064
+ var getServerState = () => EMPTY;
1065
+ var nextToasterId = 0;
1066
+ var activeToasterId = null;
1067
+ var mountedToasters = /* @__PURE__ */ new Set();
1068
+ var toasterListeners = /* @__PURE__ */ new Set();
1069
+ function subscribeToasters(listener) {
1070
+ toasterListeners.add(listener);
1071
+ return () => {
1072
+ toasterListeners.delete(listener);
1073
+ };
1074
+ }
1075
+ function registerToaster(id) {
1076
+ mountedToasters.add(id);
1077
+ if (activeToasterId === null) {
1078
+ activeToasterId = id;
1079
+ toasterListeners.forEach((listener) => listener());
1080
+ }
1081
+ return () => {
1082
+ mountedToasters.delete(id);
1083
+ if (activeToasterId === id) {
1084
+ activeToasterId = mountedToasters.values().next().value ?? null;
1085
+ toasterListeners.forEach((listener) => listener());
1086
+ }
1087
+ };
1088
+ }
1089
+ function useDocumentDirection(dir) {
1090
+ const [documentDir, setDocumentDir] = (0, import_react3.useState)("ltr");
1091
+ (0, import_react3.useEffect)(() => {
1092
+ if (dir !== "auto" || typeof document === "undefined") return;
1093
+ const html = document.documentElement;
1094
+ const read = () => setDocumentDir(
1095
+ (html.getAttribute("dir") || getComputedStyle(html).direction) === "rtl" ? "rtl" : "ltr"
1096
+ );
1097
+ read();
1098
+ const observer = new MutationObserver(read);
1099
+ observer.observe(html, { attributes: true, attributeFilter: ["dir"] });
1100
+ return () => observer.disconnect();
1101
+ }, [dir]);
1102
+ return dir === "ltr" || dir === "rtl" ? dir : documentDir;
1103
+ }
1104
+ function usePageHidden(enabled) {
1105
+ const [hidden, setHidden] = (0, import_react3.useState)(false);
1106
+ (0, import_react3.useEffect)(() => {
1107
+ if (!enabled) return;
1108
+ const update2 = () => setHidden(document.visibilityState === "hidden");
1109
+ update2();
1110
+ document.addEventListener("visibilitychange", update2);
1111
+ return () => document.removeEventListener("visibilitychange", update2);
1112
+ }, [enabled]);
1113
+ return enabled && hidden;
1114
+ }
1115
+ function useSystemDark(enabled) {
1116
+ const [dark, setDark] = (0, import_react3.useState)(false);
1117
+ (0, import_react3.useEffect)(() => {
1118
+ if (!enabled || typeof matchMedia === "undefined") return;
1119
+ const query = matchMedia("(prefers-color-scheme: dark)");
1120
+ const update2 = () => setDark(query.matches);
1121
+ update2();
1122
+ query.addEventListener("change", update2);
1123
+ return () => query.removeEventListener("change", update2);
1124
+ }, [enabled]);
1125
+ return dark;
1126
+ }
1127
+ function splitPosition(position, dir) {
1128
+ const [y, x] = position.split("-");
1129
+ const physical = {
1130
+ left: dir === "ltr" ? "start" : "end",
1131
+ right: dir === "ltr" ? "end" : "start"
1132
+ };
1133
+ return { y, x: physical[x] ?? x };
1134
+ }
1135
+ function Toaster(props) {
1136
+ const {
1137
+ position = "bottom-start",
1138
+ dir: dirProp = "auto",
1139
+ limit = 4,
1140
+ duration = 4e3,
1141
+ durations,
1142
+ colors,
1143
+ icons,
1144
+ closeButton = true,
1145
+ progress = true,
1146
+ swipe = true,
1147
+ pauseOnHover = true,
1148
+ pauseWhenPageHidden = true,
1149
+ sound = false,
1150
+ theme,
1151
+ width = 390,
1152
+ gap = 12,
1153
+ offset = 16,
1154
+ zIndex = 11e3,
1155
+ locale,
1156
+ labels: labelOverrides,
1157
+ classNames = {},
1158
+ styles = {},
1159
+ className,
1160
+ style,
1161
+ container
1162
+ } = props;
1163
+ const state2 = (0, import_react3.useSyncExternalStore)(toastStore.subscribe, toastStore.getState, getServerState);
1164
+ const dir = useDocumentDirection(dirProp);
1165
+ const pageHidden = usePageHidden(pauseWhenPageHidden);
1166
+ const systemDark = useSystemDark(theme === "system");
1167
+ const labels = (0, import_react3.useMemo)(() => resolveLabels(locale, labelOverrides), [locale, labelOverrides]);
1168
+ const [hovered, setHovered] = (0, import_react3.useState)(null);
1169
+ const instanceId = (0, import_react3.useMemo)(() => ++nextToasterId, []);
1170
+ const isActiveToaster = (0, import_react3.useSyncExternalStore)(
1171
+ subscribeToasters,
1172
+ () => activeToasterId === instanceId,
1173
+ () => false
1174
+ );
1175
+ const [mounted, setMounted] = (0, import_react3.useState)(false);
1176
+ (0, import_react3.useEffect)(() => {
1177
+ setMounted(true);
1178
+ return registerToaster(instanceId);
1179
+ }, [instanceId]);
1180
+ const groups = (0, import_react3.useMemo)(() => {
1181
+ const byKey = /* @__PURE__ */ new Map();
1182
+ for (const t of state2.toasts) {
1183
+ const { y, x } = splitPosition(t.position ?? position, dir);
1184
+ const key = `${y}-${x}`;
1185
+ if (!byKey.has(key)) byKey.set(key, { y, x, toasts: [] });
1186
+ byKey.get(key).toasts.push(t);
1187
+ }
1188
+ for (const group of byKey.values()) {
1189
+ const shown = new Set(
1190
+ group.toasts.filter((t) => t.open).sort((a, b) => a.createdAt - b.createdAt).slice(0, Math.max(1, limit))
1191
+ );
1192
+ group.toasts = group.toasts.filter((t) => !t.open || shown.has(t));
1193
+ }
1194
+ return [...byKey.entries()];
1195
+ }, [state2.toasts, position, dir, limit]);
1196
+ (0, import_react3.useEffect)(() => {
1197
+ if (hovered && !groups.some(([key]) => key === hovered)) setHovered(null);
1198
+ }, [groups, hovered]);
1199
+ if (!mounted || !isActiveToaster) return null;
1200
+ const target = container ?? document.body;
1201
+ const resolve = (t) => {
1202
+ const typeDuration = durations?.[t.type] ?? DEFAULT_DURATIONS[t.type];
1203
+ const customIcon = icons && t.type in icons ? icons[t.type] : void 0;
1204
+ return {
1205
+ duration: t.duration ?? (typeDuration === void 0 ? duration : typeDuration),
1206
+ color: resolveColor(t.color ?? colors?.[t.type] ?? DEFAULT_COLORS[t.type]),
1207
+ icon: t.icon === false ? null : t.icon ?? (customIcon !== void 0 ? customIcon : DEFAULT_ICONS[t.type]),
1208
+ dismissible: t.dismissible ?? t.type !== "loading",
1209
+ closeButton,
1210
+ progress: t.progress ?? progress,
1211
+ swipe,
1212
+ sound: t.sound ?? sound,
1213
+ className: t.className,
1214
+ style: t.style,
1215
+ classNames: { ...classNames, ...t.classNames },
1216
+ styles: { ...styles, ...t.styles }
1217
+ };
1218
+ };
1219
+ const resolvedTheme = theme === "system" ? systemDark ? "dark" : "light" : theme;
1220
+ return (0, import_react_dom.createPortal)(
1221
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { role: "region", "aria-label": labels.region, className: "wello-notifications-region", dir, children: groups.map(([key, group]) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1222
+ "ol",
1223
+ {
1224
+ className: cx("wello-notifications-toaster", classNames.toaster, className),
1225
+ "data-y": group.y,
1226
+ "data-x": group.x,
1227
+ "data-theme": resolvedTheme,
1228
+ dir,
1229
+ style: {
1230
+ "--wello-notifications-width": toCssSize(width),
1231
+ "--wello-notifications-gap": `${gap}px`,
1232
+ "--wello-notifications-offset": toCssSize(offset),
1233
+ zIndex,
1234
+ ...styles.toaster,
1235
+ ...style
1236
+ },
1237
+ onMouseEnter: pauseOnHover ? () => setHovered(key) : void 0,
1238
+ onMouseLeave: pauseOnHover ? () => setHovered(null) : void 0,
1239
+ onFocus: pauseOnHover ? () => setHovered(key) : void 0,
1240
+ onBlur: pauseOnHover ? (event) => {
1241
+ if (!event.currentTarget.contains(event.relatedTarget))
1242
+ setHovered(null);
1243
+ } : void 0,
1244
+ children: group.toasts.map((t) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1245
+ ToastItem,
1246
+ {
1247
+ toast: t,
1248
+ settings: resolve(t),
1249
+ paused: state2.paused || pageHidden || hovered === key,
1250
+ labels
1251
+ },
1252
+ t.id
1253
+ ))
1254
+ },
1255
+ key
1256
+ )) }),
1257
+ target
1258
+ );
1259
+ }
1260
+
1261
+ // src/tab/tab-notifications.ts
1262
+ var defaultFormat = (count, title) => `(${count > 99 ? "99+" : count}) ${title}`;
1263
+ function createTabNotifications(options = {}) {
1264
+ const { formatTitle = defaultFormat, dotColor = "#ef4444", favicon = true } = options;
1265
+ let ready = false;
1266
+ let originalTitle = "";
1267
+ let originalFavicon = null;
1268
+ let unread = 0;
1269
+ let flashTimer = null;
1270
+ let stopListening = null;
1271
+ let dotted = null;
1272
+ let faviconRequest = 0;
1273
+ const hasDocument = () => typeof document !== "undefined";
1274
+ function init() {
1275
+ if (ready || !hasDocument()) return;
1276
+ ready = true;
1277
+ originalTitle = document.title;
1278
+ originalFavicon = document.querySelector('link[rel~="icon"]')?.href ?? null;
1279
+ }
1280
+ const baseTitle = () => unread > 0 ? formatTitle(unread, originalTitle) : originalTitle;
1281
+ function applyFavicon(href) {
1282
+ const links = document.querySelectorAll('link[rel~="icon"]');
1283
+ if (href === null) {
1284
+ links.forEach((link2) => link2.dataset.wnFavicon !== void 0 && link2.remove());
1285
+ return;
1286
+ }
1287
+ let link = links[0];
1288
+ if (!link) {
1289
+ link = document.createElement("link");
1290
+ link.rel = "icon";
1291
+ link.dataset.wnFavicon = "";
1292
+ document.head.appendChild(link);
1293
+ }
1294
+ link.href = href;
1295
+ }
1296
+ function drawDot(base) {
1297
+ const size = 32;
1298
+ const canvas = document.createElement("canvas");
1299
+ canvas.width = size;
1300
+ canvas.height = size;
1301
+ const ctx = canvas.getContext("2d");
1302
+ if (!ctx) return null;
1303
+ if (base) ctx.drawImage(base, 0, 0, size, size);
1304
+ const radius = base ? 7 : 10;
1305
+ const x = size - radius - 1;
1306
+ const y = base ? radius + 1 : size / 2;
1307
+ ctx.beginPath();
1308
+ ctx.arc(x, y, radius + 2, 0, Math.PI * 2);
1309
+ ctx.fillStyle = "#ffffff";
1310
+ ctx.fill();
1311
+ ctx.beginPath();
1312
+ ctx.arc(x, y, radius, 0, Math.PI * 2);
1313
+ ctx.fillStyle = dotColor;
1314
+ ctx.fill();
1315
+ try {
1316
+ return canvas.toDataURL("image/png");
1317
+ } catch {
1318
+ return null;
1319
+ }
1320
+ }
1321
+ function showDot() {
1322
+ if (!favicon) return;
1323
+ const source = originalFavicon ?? "";
1324
+ if (dotted?.source === source) return applyFavicon(dotted.url);
1325
+ const request = ++faviconRequest;
1326
+ const done = (base) => {
1327
+ if (request !== faviconRequest || unread === 0) return;
1328
+ const url = drawDot(base);
1329
+ if (!url) return;
1330
+ dotted = { source, url };
1331
+ applyFavicon(url);
1332
+ };
1333
+ if (!source) return done(null);
1334
+ const image = new Image();
1335
+ image.crossOrigin = "anonymous";
1336
+ image.onload = () => done(image);
1337
+ image.onerror = () => done(null);
1338
+ image.src = source;
1339
+ }
1340
+ function hideDot() {
1341
+ faviconRequest++;
1342
+ if (favicon) applyFavicon(originalFavicon);
1343
+ }
1344
+ const api = {
1345
+ setUnreadCount(count) {
1346
+ if (!hasDocument()) return;
1347
+ init();
1348
+ unread = Math.max(0, Math.floor(count) || 0);
1349
+ if (!flashTimer) document.title = baseTitle();
1350
+ if (unread > 0) showDot();
1351
+ else hideDot();
1352
+ },
1353
+ getUnreadCount: () => unread,
1354
+ flashTitle(message, flashOptions) {
1355
+ if (!hasDocument()) return;
1356
+ init();
1357
+ api.stopFlashing();
1358
+ const { interval = 1500, stopOnFocus = true } = typeof flashOptions === "number" ? { interval: flashOptions } : flashOptions ?? {};
1359
+ let showMessage = false;
1360
+ document.title = message;
1361
+ flashTimer = setInterval(() => {
1362
+ document.title = showMessage ? message : baseTitle();
1363
+ showMessage = !showMessage;
1364
+ }, interval);
1365
+ if (stopOnFocus) {
1366
+ const onVisible = () => {
1367
+ if (document.visibilityState === "visible" && document.hasFocus()) api.stopFlashing();
1368
+ };
1369
+ document.addEventListener("visibilitychange", onVisible);
1370
+ window.addEventListener("focus", onVisible);
1371
+ stopListening = () => {
1372
+ document.removeEventListener("visibilitychange", onVisible);
1373
+ window.removeEventListener("focus", onVisible);
1374
+ };
1375
+ }
1376
+ },
1377
+ stopFlashing() {
1378
+ if (flashTimer) clearInterval(flashTimer);
1379
+ flashTimer = null;
1380
+ stopListening?.();
1381
+ stopListening = null;
1382
+ if (ready) document.title = baseTitle();
1383
+ },
1384
+ isFlashing: () => flashTimer !== null,
1385
+ reset() {
1386
+ api.stopFlashing();
1387
+ unread = 0;
1388
+ if (!ready) return;
1389
+ document.title = originalTitle;
1390
+ hideDot();
1391
+ }
1392
+ };
1393
+ return api;
1394
+ }
1395
+ var tabNotifications = createTabNotifications();
1396
+ // Annotate the CommonJS export names for ESM import in node:
1397
+ 0 && (module.exports = {
1398
+ CloseIcon,
1399
+ ErrorIcon,
1400
+ InfoIcon,
1401
+ LoadingIcon,
1402
+ NOTIFICATION_SOUNDS,
1403
+ SuccessIcon,
1404
+ Toaster,
1405
+ WarningIcon,
1406
+ createTabNotifications,
1407
+ playNotificationSound,
1408
+ playSound,
1409
+ playTone,
1410
+ resolveColor,
1411
+ resolveLabels,
1412
+ scheduleSound,
1413
+ tabNotifications,
1414
+ toast,
1415
+ toastStore,
1416
+ useToasts
1417
+ });
1418
+ //# sourceMappingURL=index.cjs.map