lumatoast 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,972 @@
1
+ // src/core/tokens.ts
2
+ var DURATION_SHORT = 2e3;
3
+ var DURATION_DEFAULT = 4e3;
4
+ var DURATION_LONG = 6e3;
5
+ var DURATION_INFINITE = Infinity;
6
+ var ANIMATION_ENTER_MS = 320;
7
+ var ANIMATION_EXIT_MS = 300;
8
+ var ANIMATION_STACK_MS = 280;
9
+ var TOAST_WIDTH_PX = 340;
10
+ var TOAST_GAP_PX = 12;
11
+ var Z_INDEX_BASE = 1e3;
12
+ var CSS_VARS = {
13
+ // Card layout
14
+ WIDTH: "--luma-width",
15
+ RADIUS: "--luma-radius",
16
+ PADDING: "--luma-padding",
17
+ GAP: "--luma-gap",
18
+ // Background & border
19
+ BG: "--luma-bg",
20
+ BORDER_COLOR: "--luma-border-color",
21
+ BORDER_WIDTH: "--luma-border-width",
22
+ SHADOW: "--luma-shadow",
23
+ BACKDROP: "--luma-backdrop",
24
+ // Typography
25
+ FONT: "--luma-font",
26
+ TITLE_SIZE: "--luma-title-size",
27
+ TITLE_WEIGHT: "--luma-title-weight",
28
+ TITLE_COLOR: "--luma-title-color",
29
+ DESC_SIZE: "--luma-desc-size",
30
+ DESC_COLOR: "--luma-desc-color",
31
+ LINE_HEIGHT: "--luma-line-height",
32
+ LETTER_SPACING: "--luma-letter-spacing",
33
+ // Icon
34
+ ICON_SIZE: "--luma-icon-size",
35
+ // Close button
36
+ CLOSE_COLOR: "--luma-close-color",
37
+ CLOSE_SIZE: "--luma-close-size",
38
+ // Action button
39
+ ACTION_BG: "--luma-action-bg",
40
+ ACTION_COLOR: "--luma-action-color",
41
+ ACTION_HOVER_BG: "--luma-action-hover-bg",
42
+ ACTION_RADIUS: "--luma-action-radius",
43
+ ACTION_PADDING: "--luma-action-padding",
44
+ ACTION_SIZE: "--luma-action-size",
45
+ ACTION_WEIGHT: "--luma-action-weight",
46
+ ACTION_BORDER: "--luma-action-border",
47
+ ACTION_HOVER_BORDER: "--luma-action-hover-border",
48
+ // Progress bar
49
+ PROGRESS_HEIGHT: "--luma-progress-height",
50
+ PROGRESS_RADIUS: "--luma-progress-radius",
51
+ PROGRESS_SUCCESS: "--luma-progress-success",
52
+ PROGRESS_ERROR: "--luma-progress-error",
53
+ PROGRESS_WARNING: "--luma-progress-warning",
54
+ PROGRESS_INFO: "--luma-progress-info",
55
+ PROGRESS_LOADING: "--luma-progress-loading",
56
+ PROGRESS_CUSTOM: "--luma-progress-custom",
57
+ // Accent colors (used for type-specific accents like border-left)
58
+ ACCENT_SUCCESS: "--luma-accent-success",
59
+ ACCENT_ERROR: "--luma-accent-error",
60
+ ACCENT_WARNING: "--luma-accent-warning",
61
+ ACCENT_INFO: "--luma-accent-info",
62
+ // Animation durations (set by animationSpeed config)
63
+ DURATION_ENTER: "--luma-duration-enter",
64
+ DURATION_EXIT: "--luma-duration-exit",
65
+ DURATION_STACK: "--luma-duration-stack"
66
+ };
67
+ var CLASS_CONTAINER = "luma-toast-container";
68
+ var CLASS_TOAST = "luma-toast";
69
+ var CLASS_CARD = "luma-toast-card";
70
+ var CLASS_ICON = "luma-toast-icon";
71
+ var CLASS_CONTENT = "luma-toast-content";
72
+ var CLASS_TITLE = "luma-toast-title";
73
+ var CLASS_DESCRIPTION = "luma-toast-description";
74
+ var CLASS_ACTION = "luma-toast-action";
75
+ var CLASS_CLOSE = "luma-toast-close";
76
+ var CLASS_PROGRESS = "luma-progress";
77
+ var CLASS_EXIT = "luma-toast-exit";
78
+
79
+ // src/core/constants.ts
80
+ var DEFAULT_DURATION = DURATION_DEFAULT;
81
+ var DEFAULT_POSITION = "top-right";
82
+ var DEFAULT_THEME = "linear";
83
+ var DEFAULT_DISMISSIBLE = true;
84
+ var DEFAULT_SHOW_ICON = true;
85
+ var DEFAULT_CLOSE_ON_CLICK = false;
86
+ var DEFAULT_PROGRESS_POS = "bottom";
87
+ var DEFAULT_ANIMATION_ENTER = "slide";
88
+ var DEFAULT_ANIMATION_EXIT = "slide";
89
+ var DEFAULT_ANIMATION_SPEED = "normal";
90
+ var MAX_VISIBLE_TOASTS = 5;
91
+ var STACK_GAP = TOAST_GAP_PX;
92
+
93
+ // src/utils/id.ts
94
+ var counter = 0;
95
+ function createToastId(prefix = "toast") {
96
+ counter += 1;
97
+ return `${prefix}-${Date.now()}-${counter}`;
98
+ }
99
+
100
+ // src/utils/events.ts
101
+ var EventEmitter = class {
102
+ listeners = /* @__PURE__ */ new Set();
103
+ subscribe(listener) {
104
+ this.listeners.add(listener);
105
+ return () => {
106
+ this.listeners.delete(listener);
107
+ };
108
+ }
109
+ emit(payload) {
110
+ this.listeners.forEach((listener) => listener(payload));
111
+ }
112
+ clear() {
113
+ this.listeners.clear();
114
+ }
115
+ };
116
+
117
+ // src/core/manager.ts
118
+ var globalConfig = {
119
+ duration: DEFAULT_DURATION,
120
+ position: DEFAULT_POSITION,
121
+ theme: DEFAULT_THEME,
122
+ dismissible: DEFAULT_DISMISSIBLE,
123
+ maxVisible: MAX_VISIBLE_TOASTS,
124
+ showIcon: DEFAULT_SHOW_ICON,
125
+ closeOnClick: DEFAULT_CLOSE_ON_CLICK,
126
+ progressPosition: DEFAULT_PROGRESS_POS,
127
+ animationEnter: DEFAULT_ANIMATION_ENTER,
128
+ animationExit: DEFAULT_ANIMATION_EXIT,
129
+ animationSpeed: DEFAULT_ANIMATION_SPEED
130
+ };
131
+ var SPEED_MAP = {
132
+ slow: { enter: 500, exit: 450, stack: 400 },
133
+ normal: { enter: 320, exit: 300, stack: 280 },
134
+ fast: { enter: 180, exit: 160, stack: 150 }
135
+ };
136
+ function applyAnimationSpeed(speed) {
137
+ if (typeof document === "undefined") return;
138
+ const { enter, exit, stack } = SPEED_MAP[speed];
139
+ const root = document.documentElement;
140
+ root.style.setProperty("--luma-duration-enter", `${enter}ms`);
141
+ root.style.setProperty("--luma-duration-exit", `${exit}ms`);
142
+ root.style.setProperty("--luma-duration-stack", `${stack}ms`);
143
+ }
144
+ function configure(config) {
145
+ globalConfig = { ...globalConfig, ...config };
146
+ if (config.animationSpeed) {
147
+ applyAnimationSpeed(config.animationSpeed);
148
+ }
149
+ }
150
+ var ToastManager = class {
151
+ visible = [];
152
+ queue = [];
153
+ changes = new EventEmitter();
154
+ /**
155
+ * Returns all visible toasts.
156
+ */
157
+ getToasts() {
158
+ return [...this.visible];
159
+ }
160
+ /**
161
+ * Create a new toast.
162
+ */
163
+ create(type, options = {}) {
164
+ const toast2 = {
165
+ id: options.id ?? createToastId(),
166
+ type,
167
+ title: options.title,
168
+ description: options.description,
169
+ duration: options.duration ?? globalConfig.duration,
170
+ dismissible: options.dismissible ?? globalConfig.dismissible,
171
+ position: options.position ?? globalConfig.position,
172
+ theme: options.theme ?? globalConfig.theme,
173
+ action: options.action,
174
+ // customization
175
+ className: options.className,
176
+ style: options.style,
177
+ showIcon: options.showIcon ?? globalConfig.showIcon,
178
+ closeOnClick: options.closeOnClick ?? globalConfig.closeOnClick,
179
+ progressPosition: options.progressPosition ?? globalConfig.progressPosition,
180
+ animationEnter: options.animationEnter ?? globalConfig.animationEnter,
181
+ animationExit: options.animationExit ?? globalConfig.animationExit,
182
+ // internal
183
+ createdAt: Date.now(),
184
+ visible: false
185
+ };
186
+ if (this.visible.length >= globalConfig.maxVisible) {
187
+ this.queue.push(toast2);
188
+ } else {
189
+ toast2.visible = true;
190
+ this.visible.push(toast2);
191
+ this.changes.emit(this.getToasts());
192
+ }
193
+ return toast2;
194
+ }
195
+ /**
196
+ * Remove a toast by id.
197
+ */
198
+ dismiss(id) {
199
+ const index = this.visible.findIndex((t) => t.id === id);
200
+ if (index === -1) return;
201
+ this.visible.splice(index, 1);
202
+ if (this.queue.length > 0) {
203
+ const next = this.queue.shift();
204
+ next.visible = true;
205
+ this.visible.push(next);
206
+ }
207
+ this.changes.emit(this.getToasts());
208
+ }
209
+ /**
210
+ * Remove all visible and queued toasts.
211
+ */
212
+ dismissAll() {
213
+ this.visible = [];
214
+ this.queue = [];
215
+ this.changes.emit([]);
216
+ }
217
+ /**
218
+ * Remove the most recently added visible toast.
219
+ */
220
+ dismissLatest() {
221
+ const latest = this.visible[this.visible.length - 1];
222
+ if (!latest) return;
223
+ this.dismiss(latest.id);
224
+ }
225
+ /**
226
+ * Update an existing toast by id.
227
+ * Works on both visible and queued toasts.
228
+ */
229
+ update(id, updates) {
230
+ const visibleToast = this.visible.find((t) => t.id === id);
231
+ if (visibleToast) {
232
+ Object.assign(visibleToast, updates);
233
+ if (updates.duration !== void 0) {
234
+ visibleToast.createdAt = Date.now();
235
+ }
236
+ this.changes.emit(this.getToasts());
237
+ return visibleToast;
238
+ }
239
+ const queuedToast = this.queue.find((t) => t.id === id);
240
+ if (queuedToast) {
241
+ Object.assign(queuedToast, updates);
242
+ if (updates.duration !== void 0) {
243
+ queuedToast.createdAt = Date.now();
244
+ }
245
+ return queuedToast;
246
+ }
247
+ return null;
248
+ }
249
+ };
250
+ var toastManager = new ToastManager();
251
+
252
+ // src/core/toast.ts
253
+ var toast = {
254
+ /**
255
+ * Show a success toast.
256
+ */
257
+ success(message, options = {}) {
258
+ return toastManager.create("success", {
259
+ ...options,
260
+ description: message
261
+ });
262
+ },
263
+ /**
264
+ * Show an error toast.
265
+ */
266
+ error(message, options = {}) {
267
+ return toastManager.create("error", {
268
+ ...options,
269
+ description: message
270
+ });
271
+ },
272
+ /**
273
+ * Show a warning toast.
274
+ */
275
+ warning(message, options = {}) {
276
+ return toastManager.create("warning", {
277
+ ...options,
278
+ description: message
279
+ });
280
+ },
281
+ /**
282
+ * Show an info toast.
283
+ */
284
+ info(message, options = {}) {
285
+ return toastManager.create("info", {
286
+ ...options,
287
+ description: message
288
+ });
289
+ },
290
+ /**
291
+ * Show a persistent loading toast that must be dismissed manually
292
+ * or converted via `toast.update()` / `toast.promise()`.
293
+ */
294
+ loading(message, options = {}) {
295
+ return toastManager.create("loading", {
296
+ ...options,
297
+ duration: Infinity,
298
+ dismissible: false,
299
+ description: message
300
+ });
301
+ },
302
+ /**
303
+ * Show a fully custom toast.
304
+ * You control the title, description, action, theme, and position.
305
+ *
306
+ * @example
307
+ * toast.custom("Custom notification", { title: "Hey!", theme: "cyberpunk" });
308
+ */
309
+ custom(message, options = {}) {
310
+ return toastManager.create("custom", {
311
+ ...options,
312
+ description: message
313
+ });
314
+ },
315
+ /**
316
+ * Dismiss a toast by id.
317
+ */
318
+ dismiss(id) {
319
+ toastManager.dismiss(id);
320
+ },
321
+ /**
322
+ * Dismiss all visible and queued toasts.
323
+ */
324
+ dismissAll() {
325
+ toastManager.dismissAll();
326
+ },
327
+ /**
328
+ * Dismiss the most recently added visible toast.
329
+ */
330
+ dismissLatest() {
331
+ toastManager.dismissLatest();
332
+ },
333
+ /**
334
+ * Update an existing toast's properties.
335
+ * Useful for transitioning a loading toast to success or error.
336
+ */
337
+ update(id, updates) {
338
+ return toastManager.update(id, updates);
339
+ },
340
+ /**
341
+ * Track a promise: show a loading toast, then auto-transition to
342
+ * success or error based on the promise outcome.
343
+ *
344
+ * @example
345
+ * toast.promise(fetch("/api/save"), {
346
+ * loading: { description: "Saving..." },
347
+ * success: { description: "Saved!" },
348
+ * error: { description: "Failed to save." }
349
+ * });
350
+ */
351
+ promise(promise, options) {
352
+ const loadingToast = toast.loading(
353
+ options.loading.description ?? "",
354
+ { ...options.loading }
355
+ );
356
+ return promise.then((result) => {
357
+ toast.update(loadingToast.id, {
358
+ type: "success",
359
+ ...options.success,
360
+ duration: options.success.duration ?? 3e3,
361
+ dismissible: true
362
+ });
363
+ return result;
364
+ }).catch((error) => {
365
+ toast.update(loadingToast.id, {
366
+ type: "error",
367
+ ...options.error,
368
+ duration: options.error.duration ?? 4e3,
369
+ dismissible: true
370
+ });
371
+ throw error;
372
+ });
373
+ },
374
+ /**
375
+ * Subscribe to toast state changes.
376
+ * Returns an unsubscribe function.
377
+ *
378
+ * @example
379
+ * const unsub = toast.subscribe((toasts) => console.log(toasts));
380
+ * // later:
381
+ * unsub();
382
+ */
383
+ subscribe(listener) {
384
+ return toastManager.changes.subscribe(listener);
385
+ },
386
+ /**
387
+ * Set global defaults for all toasts.
388
+ * Per-toast options always override these.
389
+ *
390
+ * @example
391
+ * toast.configure({ position: "bottom-right", theme: "minimal", duration: 3000 });
392
+ */
393
+ configure
394
+ };
395
+
396
+ // src/renderer/gestures.ts
397
+ var DISMISS_THRESHOLD = 120;
398
+ var MAX_ROTATION = 8;
399
+ var EXIT_DISTANCE = 450;
400
+ function attachSwipeGesture(element, toastId, position, onPause, onResume) {
401
+ const isVertical = position === "top-center" || position === "bottom-center";
402
+ const isBottom = position.startsWith("bottom");
403
+ let startX = 0;
404
+ let startY = 0;
405
+ let currentX = 0;
406
+ let currentY = 0;
407
+ let dragging = false;
408
+ const card = element.querySelector(".luma-toast-card");
409
+ card.style.touchAction = isVertical ? "pan-x" : "pan-y";
410
+ const handlePointerDown = (event) => {
411
+ const target = event.target;
412
+ if (target.closest(".luma-toast-close") || target.closest(".luma-toast-action")) {
413
+ return;
414
+ }
415
+ dragging = true;
416
+ startX = event.clientX;
417
+ startY = event.clientY;
418
+ currentX = 0;
419
+ currentY = 0;
420
+ onPause();
421
+ element.style.transition = "none";
422
+ element.setPointerCapture(event.pointerId);
423
+ };
424
+ const handlePointerMove = (event) => {
425
+ if (!dragging) return;
426
+ currentX = event.clientX - startX;
427
+ currentY = event.clientY - startY;
428
+ if (isVertical) {
429
+ const clampedY = isBottom ? Math.max(0, currentY) : Math.min(0, currentY);
430
+ element.style.transform = `translate3d(0, ${clampedY}px, 0)`;
431
+ element.style.opacity = `${Math.max(0.35, 1 - Math.abs(clampedY) / 180)}`;
432
+ } else {
433
+ const rotate = Math.sign(currentX) * Math.min(Math.abs(currentX) / 20, MAX_ROTATION);
434
+ element.style.transform = `translate3d(${currentX}px, 0, 0) rotate(${rotate}deg)`;
435
+ element.style.opacity = `${Math.max(0.35, 1 - Math.abs(currentX) / 180)}`;
436
+ }
437
+ };
438
+ const handlePointerUp = (event) => {
439
+ if (!dragging) return;
440
+ dragging = false;
441
+ element.releasePointerCapture(event.pointerId);
442
+ element.style.transition = "transform 280ms cubic-bezier(.2,.8,.2,1), opacity 220ms ease";
443
+ const delta = isVertical ? currentY : currentX;
444
+ if (Math.abs(delta) >= DISMISS_THRESHOLD) {
445
+ if (isVertical) {
446
+ const exitY = delta > 0 ? EXIT_DISTANCE : -EXIT_DISTANCE;
447
+ element.style.transform = `translate3d(0, ${exitY}px, 0)`;
448
+ } else {
449
+ const exitX = delta > 0 ? EXIT_DISTANCE : -EXIT_DISTANCE;
450
+ const exitRotate = delta > 0 ? 12 : -12;
451
+ element.style.transform = `translate3d(${exitX}px, 0, 0) rotate(${exitRotate}deg)`;
452
+ }
453
+ element.style.opacity = "0";
454
+ setTimeout(() => {
455
+ toast.dismiss(toastId);
456
+ }, 280);
457
+ return;
458
+ }
459
+ element.style.transform = "";
460
+ element.style.opacity = "1";
461
+ onResume();
462
+ };
463
+ element.addEventListener("pointerdown", handlePointerDown);
464
+ element.addEventListener("pointermove", handlePointerMove);
465
+ element.addEventListener("pointerup", handlePointerUp);
466
+ element.addEventListener("pointercancel", handlePointerUp);
467
+ }
468
+
469
+ // src/utils/dom.ts
470
+ function createElement(tag, classNames = []) {
471
+ const element = document.createElement(tag);
472
+ if (classNames.length) {
473
+ element.classList.add(...classNames);
474
+ }
475
+ return element;
476
+ }
477
+ function applyStyles(element, style) {
478
+ if (!style || typeof style !== "object") return;
479
+ for (const [key, value] of Object.entries(style)) {
480
+ if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
481
+ if (typeof value !== "string") continue;
482
+ if (key.startsWith("--")) {
483
+ element.style.setProperty(key, value);
484
+ } else {
485
+ element.style[key] = value;
486
+ }
487
+ }
488
+ }
489
+
490
+ // src/renderer/container.ts
491
+ var CONTAINER_CLASS = "luma-toast-container";
492
+ var containers = /* @__PURE__ */ new Map();
493
+ function getContainer(position) {
494
+ const existing = containers.get(position);
495
+ if (existing) return existing;
496
+ const container = createElement("div", [
497
+ CONTAINER_CLASS,
498
+ `luma-${position}`
499
+ ]);
500
+ container.dataset.position = position;
501
+ container.setAttribute("role", "region");
502
+ container.setAttribute("aria-live", "polite");
503
+ container.setAttribute("aria-atomic", "false");
504
+ container.setAttribute("aria-relevant", "additions removals");
505
+ document.body.appendChild(container);
506
+ containers.set(position, container);
507
+ return container;
508
+ }
509
+ function removeContainer(position) {
510
+ const container = containers.get(position);
511
+ if (!container) return;
512
+ container.remove();
513
+ containers.delete(position);
514
+ }
515
+
516
+ // src/icons/success.ts
517
+ var successIcon = `
518
+ <svg viewBox="0 0 24 24" width="20" height="20" fill="none">
519
+ <circle cx="12" cy="12" r="10" fill="#22C55E"/>
520
+ <path
521
+ d="M7.5 12.5L10.5 15.5L16.5 9.5"
522
+ stroke="white"
523
+ stroke-width="2"
524
+ stroke-linecap="round"
525
+ stroke-linejoin="round"
526
+ />
527
+ </svg>
528
+ `;
529
+
530
+ // src/icons/error.ts
531
+ var errorIcon = `
532
+ <svg viewBox="0 0 24 24" width="20" height="20" fill="none">
533
+ <circle cx="12" cy="12" r="10" fill="#EF4444"/>
534
+ <path
535
+ d="M9 9L15 15M15 9L9 15"
536
+ stroke="white"
537
+ stroke-width="2"
538
+ stroke-linecap="round"
539
+ />
540
+ </svg>
541
+ `;
542
+
543
+ // src/icons/warning.ts
544
+ var warningIcon = `
545
+ <svg viewBox="0 0 24 24" width="20" height="20" fill="none">
546
+ <path
547
+ d="M12 3L22 20H2L12 3Z"
548
+ fill="#F59E0B"
549
+ />
550
+ <path
551
+ d="M12 9V13"
552
+ stroke="white"
553
+ stroke-width="2"
554
+ stroke-linecap="round"
555
+ />
556
+ <circle cx="12" cy="17" r="1" fill="white"/>
557
+ </svg>
558
+ `;
559
+
560
+ // src/icons/info.ts
561
+ var infoIcon = `
562
+ <svg viewBox="0 0 24 24" width="20" height="20" fill="none">
563
+ <circle cx="12" cy="12" r="10" fill="#3B82F6"/>
564
+ <path
565
+ d="M12 11V16"
566
+ stroke="white"
567
+ stroke-width="2"
568
+ stroke-linecap="round"
569
+ />
570
+ <circle cx="12" cy="8" r="1.2" fill="white"/>
571
+ </svg>
572
+ `;
573
+
574
+ // src/icons/loading.ts
575
+ var loadingIcon = `
576
+ <svg class="luma-loading-icon"
577
+ viewBox="0 0 24 24"
578
+ width="20"
579
+ height="20"
580
+ fill="none">
581
+
582
+ <circle
583
+ cx="12"
584
+ cy="12"
585
+ r="9"
586
+ stroke="#64748B"
587
+ stroke-width="2"
588
+ opacity=".25"
589
+ />
590
+
591
+ <path
592
+ d="M12 3
593
+ A9 9 0 0 1 21 12"
594
+ stroke="#60A5FA"
595
+ stroke-width="2"
596
+ stroke-linecap="round"
597
+ />
598
+ </svg>
599
+ `;
600
+
601
+ // src/components/toast.ts
602
+ function renderToast(toastItem) {
603
+ const classes = [
604
+ "luma-toast",
605
+ `luma-${toastItem.type}`,
606
+ `luma-theme-${toastItem.theme}`
607
+ ];
608
+ if (toastItem.className) {
609
+ classes.push(...toastItem.className.trim().split(/\s+/));
610
+ }
611
+ const wrapper = createElement("div", classes);
612
+ wrapper.dataset.toastId = toastItem.id;
613
+ wrapper.dataset.enter = toastItem.animationEnter;
614
+ wrapper.dataset.exit = toastItem.animationExit;
615
+ wrapper.setAttribute("role", toastItem.type === "error" ? "alert" : "status");
616
+ wrapper.setAttribute("aria-live", toastItem.type === "error" ? "assertive" : "polite");
617
+ const card = createElement("div", ["luma-toast-card"]);
618
+ applyStyles(card, toastItem.style);
619
+ if (toastItem.closeOnClick) {
620
+ card.style.cursor = "pointer";
621
+ card.addEventListener("click", (e) => {
622
+ const target = e.target;
623
+ if (target.closest(".luma-toast-close") || target.closest(".luma-toast-action")) return;
624
+ toast.dismiss(toastItem.id);
625
+ });
626
+ }
627
+ if (toastItem.showIcon) {
628
+ const icon = createElement("div", ["luma-toast-icon"]);
629
+ icon.innerHTML = getIcon(toastItem.type);
630
+ card.appendChild(icon);
631
+ }
632
+ const content = createElement("div", ["luma-toast-content"]);
633
+ if (toastItem.title) {
634
+ const title = createElement("div", ["luma-toast-title"]);
635
+ title.textContent = toastItem.title;
636
+ content.appendChild(title);
637
+ }
638
+ if (toastItem.description) {
639
+ const desc = createElement("div", ["luma-toast-description"]);
640
+ desc.textContent = toastItem.description;
641
+ content.appendChild(desc);
642
+ }
643
+ if (toastItem.action) {
644
+ const action = buildActionButton(toastItem);
645
+ content.appendChild(action);
646
+ }
647
+ card.appendChild(content);
648
+ const close = createElement("button", ["luma-toast-close"]);
649
+ close.type = "button";
650
+ close.textContent = "\xD7";
651
+ close.setAttribute("aria-label", "Dismiss notification");
652
+ close.setAttribute("title", "Dismiss");
653
+ if (!toastItem.dismissible) {
654
+ close.style.display = "none";
655
+ }
656
+ close.addEventListener("pointerdown", (e) => e.stopPropagation());
657
+ close.addEventListener("click", (e) => {
658
+ e.stopPropagation();
659
+ toast.dismiss(toastItem.id);
660
+ });
661
+ card.appendChild(close);
662
+ if (Number.isFinite(toastItem.duration)) {
663
+ const progress = buildProgressBar(toastItem);
664
+ if (toastItem.progressPosition === "top") {
665
+ card.insertBefore(progress, card.firstChild);
666
+ } else {
667
+ card.appendChild(progress);
668
+ }
669
+ }
670
+ wrapper.appendChild(card);
671
+ return wrapper;
672
+ }
673
+ function buildActionButton(toastItem) {
674
+ const variant = toastItem.action?.variant ?? "solid";
675
+ const action = createElement("button", [
676
+ "luma-toast-action",
677
+ `luma-action-${variant}`
678
+ ]);
679
+ action.type = "button";
680
+ action.textContent = toastItem.action.label;
681
+ action.setAttribute("aria-label", toastItem.action.label);
682
+ action.addEventListener("pointerdown", (e) => e.stopPropagation());
683
+ action.addEventListener("click", (e) => {
684
+ e.stopPropagation();
685
+ toastItem.action?.onClick();
686
+ toast.dismiss(toastItem.id);
687
+ });
688
+ return action;
689
+ }
690
+ function buildProgressBar(toastItem) {
691
+ const progress = createElement("div", ["luma-progress"]);
692
+ const elapsed = Date.now() - toastItem.createdAt;
693
+ progress.style.animationDuration = `${toastItem.duration}ms`;
694
+ progress.style.animationDelay = `-${elapsed}ms`;
695
+ if (toastItem.progressPosition === "top") {
696
+ progress.classList.add("luma-progress-top");
697
+ }
698
+ return progress;
699
+ }
700
+ function getIcon(type) {
701
+ switch (type) {
702
+ case "success":
703
+ return successIcon;
704
+ case "error":
705
+ return errorIcon;
706
+ case "warning":
707
+ return warningIcon;
708
+ case "loading":
709
+ return loadingIcon;
710
+ case "info":
711
+ default:
712
+ return infoIcon;
713
+ }
714
+ }
715
+
716
+ // src/renderer/animations.ts
717
+ var previousPositions = /* @__PURE__ */ new Map();
718
+ var prefersReducedMotion = () => window.matchMedia("(prefers-reduced-motion: reduce)").matches;
719
+ function recordPositions(elements) {
720
+ previousPositions.clear();
721
+ elements.forEach((element, id) => {
722
+ previousPositions.set(id, element.getBoundingClientRect());
723
+ });
724
+ }
725
+ function animateStack(elements) {
726
+ if (prefersReducedMotion()) return;
727
+ elements.forEach((element, id) => {
728
+ if (element.dataset.removing === "true") return;
729
+ const previous = previousPositions.get(id);
730
+ if (!previous) return;
731
+ const next = element.getBoundingClientRect();
732
+ const deltaY = previous.top - next.top;
733
+ if (deltaY === 0) return;
734
+ element.style.transition = "none";
735
+ element.style.transform = `translateY(${deltaY}px)`;
736
+ requestAnimationFrame(() => {
737
+ element.style.transition = `transform ${ANIMATION_STACK_MS}ms cubic-bezier(.2,.8,.2,1)`;
738
+ element.style.transform = "";
739
+ });
740
+ });
741
+ }
742
+ function updateDepth(elements) {
743
+ const list = [...elements.values()];
744
+ list.forEach((element, index) => {
745
+ element.style.zIndex = String(1e3 - index);
746
+ element.style.removeProperty("--luma-scale");
747
+ });
748
+ }
749
+
750
+ // src/renderer/renderer.ts
751
+ var initialized = false;
752
+ var renderedToasts = /* @__PURE__ */ new Map();
753
+ var timers = /* @__PURE__ */ new Map();
754
+ function initializeRenderer() {
755
+ if (typeof window === "undefined") return;
756
+ if (initialized) return;
757
+ initialized = true;
758
+ toast.subscribe(syncToasts);
759
+ window.addEventListener("keydown", handleEscapeKey);
760
+ }
761
+ function handleEscapeKey(event) {
762
+ if (event.key !== "Escape") return;
763
+ toast.dismissLatest();
764
+ }
765
+ function syncToasts(toasts) {
766
+ recordPositions(renderedToasts);
767
+ const activeIds = new Set(toasts.map((t) => t.id));
768
+ for (const [id, element] of renderedToasts.entries()) {
769
+ if (!activeIds.has(id)) {
770
+ removeToast(id, element);
771
+ }
772
+ }
773
+ const grouped = /* @__PURE__ */ new Map();
774
+ toasts.forEach((toastItem) => {
775
+ const list = grouped.get(toastItem.position) ?? [];
776
+ list.push(toastItem);
777
+ grouped.set(toastItem.position, list);
778
+ });
779
+ grouped.forEach((positionToasts, position) => {
780
+ const container = getContainer(position);
781
+ positionToasts.forEach((toastItem) => {
782
+ const existing = renderedToasts.get(toastItem.id);
783
+ if (existing) {
784
+ patchToast(existing, toastItem);
785
+ if (Number.isFinite(toastItem.duration) && !timers.has(toastItem.id)) {
786
+ startTimer(toastItem, existing);
787
+ }
788
+ return;
789
+ }
790
+ const element = renderToast(toastItem);
791
+ container.appendChild(element);
792
+ renderedToasts.set(toastItem.id, element);
793
+ startTimer(toastItem, element);
794
+ });
795
+ });
796
+ requestAnimationFrame(() => {
797
+ animateStack(renderedToasts);
798
+ updateDepth(renderedToasts);
799
+ });
800
+ }
801
+ function patchToast(element, toastItem) {
802
+ const baseClasses = [
803
+ "luma-toast",
804
+ `luma-${toastItem.type}`,
805
+ `luma-theme-${toastItem.theme}`
806
+ ];
807
+ if (toastItem.className) {
808
+ baseClasses.push(...toastItem.className.trim().split(/\s+/));
809
+ }
810
+ element.className = baseClasses.join(" ");
811
+ element.dataset.enter = toastItem.animationEnter;
812
+ element.dataset.exit = toastItem.animationExit;
813
+ const card = element.querySelector(".luma-toast-card");
814
+ if (!card) return;
815
+ applyStyles(card, toastItem.style);
816
+ const iconSlot = card.querySelector(".luma-toast-icon");
817
+ if (toastItem.showIcon) {
818
+ if (iconSlot) {
819
+ iconSlot.innerHTML = resolveIcon(toastItem.type);
820
+ }
821
+ } else {
822
+ iconSlot?.remove();
823
+ }
824
+ const title = card.querySelector(".luma-toast-title");
825
+ if (toastItem.title) {
826
+ if (title) {
827
+ title.textContent = toastItem.title;
828
+ } else {
829
+ const node = document.createElement("div");
830
+ node.className = "luma-toast-title";
831
+ node.textContent = toastItem.title;
832
+ card.querySelector(".luma-toast-content")?.prepend(node);
833
+ }
834
+ } else {
835
+ title?.remove();
836
+ }
837
+ const description = card.querySelector(".luma-toast-description");
838
+ if (toastItem.description) {
839
+ if (description) {
840
+ description.textContent = toastItem.description;
841
+ } else {
842
+ const node = document.createElement("div");
843
+ node.className = "luma-toast-description";
844
+ node.textContent = toastItem.description;
845
+ card.querySelector(".luma-toast-content")?.appendChild(node);
846
+ }
847
+ } else {
848
+ description?.remove();
849
+ }
850
+ const close = card.querySelector(".luma-toast-close");
851
+ if (close) {
852
+ close.style.display = toastItem.dismissible ? "" : "none";
853
+ }
854
+ const existingProgress = card.querySelector(".luma-progress");
855
+ if (Number.isFinite(toastItem.duration)) {
856
+ if (!existingProgress) {
857
+ const progress = document.createElement("div");
858
+ progress.className = "luma-progress";
859
+ progress.style.animationDuration = `${toastItem.duration}ms`;
860
+ if (toastItem.progressPosition === "top") {
861
+ progress.classList.add("luma-progress-top");
862
+ card.insertBefore(progress, card.firstChild);
863
+ } else {
864
+ card.appendChild(progress);
865
+ }
866
+ }
867
+ } else {
868
+ existingProgress?.remove();
869
+ }
870
+ }
871
+ function resolveIcon(type) {
872
+ switch (type) {
873
+ case "success":
874
+ return successIcon;
875
+ case "error":
876
+ return errorIcon;
877
+ case "warning":
878
+ return warningIcon;
879
+ case "loading":
880
+ return loadingIcon;
881
+ case "info":
882
+ default:
883
+ return infoIcon;
884
+ }
885
+ }
886
+ function startTimer(toastItem, element) {
887
+ const { duration } = toastItem;
888
+ if (!Number.isFinite(duration)) {
889
+ timers.delete(toastItem.id);
890
+ return;
891
+ }
892
+ const timer = {
893
+ timeoutId: 0,
894
+ startedAt: Date.now(),
895
+ remaining: duration
896
+ };
897
+ const schedule = () => {
898
+ timer.startedAt = Date.now();
899
+ timer.timeoutId = window.setTimeout(() => {
900
+ toast.dismiss(toastItem.id);
901
+ }, timer.remaining);
902
+ };
903
+ schedule();
904
+ timers.set(toastItem.id, timer);
905
+ attachHoverEvents(element, toastItem.id);
906
+ attachSwipeGesture(
907
+ element,
908
+ toastItem.id,
909
+ toastItem.position,
910
+ () => pauseTimer(toastItem.id, element),
911
+ () => resumeTimer(toastItem.id, element)
912
+ );
913
+ }
914
+ function pauseTimer(id, element) {
915
+ const timer = timers.get(id);
916
+ if (!timer) return;
917
+ clearTimeout(timer.timeoutId);
918
+ timer.remaining -= Date.now() - timer.startedAt;
919
+ const progress = element.querySelector(".luma-progress");
920
+ if (progress) progress.style.animationPlayState = "paused";
921
+ }
922
+ function resumeTimer(id, element) {
923
+ const timer = timers.get(id);
924
+ if (!timer) return;
925
+ timer.startedAt = Date.now();
926
+ timer.timeoutId = window.setTimeout(() => {
927
+ toast.dismiss(id);
928
+ }, timer.remaining);
929
+ const progress = element.querySelector(".luma-progress");
930
+ if (progress) progress.style.animationPlayState = "running";
931
+ }
932
+ function attachHoverEvents(element, toastId) {
933
+ element.addEventListener("mouseenter", () => pauseTimer(toastId, element));
934
+ element.addEventListener("mouseleave", () => resumeTimer(toastId, element));
935
+ }
936
+ function removeToast(id, element) {
937
+ const timer = timers.get(id);
938
+ if (timer) {
939
+ clearTimeout(timer.timeoutId);
940
+ timers.delete(id);
941
+ }
942
+ if (element.dataset.removing === "true") return;
943
+ element.dataset.removing = "true";
944
+ requestAnimationFrame(() => {
945
+ element.classList.add("luma-toast-exit");
946
+ });
947
+ const cleanup = () => {
948
+ const container = element.parentElement;
949
+ element.remove();
950
+ renderedToasts.delete(id);
951
+ if (container && container.childElementCount === 0) {
952
+ const position = container.dataset.position;
953
+ removeContainer(position);
954
+ }
955
+ };
956
+ const handleTransitionEnd = (event) => {
957
+ if (event.target !== element) return;
958
+ element.removeEventListener("transitionend", handleTransitionEnd);
959
+ cleanup();
960
+ };
961
+ element.addEventListener("transitionend", handleTransitionEnd);
962
+ window.setTimeout(() => {
963
+ if (element.isConnected) {
964
+ element.removeEventListener("transitionend", handleTransitionEnd);
965
+ cleanup();
966
+ }
967
+ }, ANIMATION_EXIT_MS + 50);
968
+ }
969
+
970
+ export { ANIMATION_ENTER_MS, ANIMATION_EXIT_MS, ANIMATION_STACK_MS, CLASS_ACTION, CLASS_CARD, CLASS_CLOSE, CLASS_CONTAINER, CLASS_CONTENT, CLASS_DESCRIPTION, CLASS_EXIT, CLASS_ICON, CLASS_PROGRESS, CLASS_TITLE, CLASS_TOAST, CSS_VARS, DEFAULT_ANIMATION_ENTER, DEFAULT_ANIMATION_EXIT, DEFAULT_ANIMATION_SPEED, DEFAULT_CLOSE_ON_CLICK, DEFAULT_DISMISSIBLE, DEFAULT_DURATION, DEFAULT_POSITION, DEFAULT_PROGRESS_POS, DEFAULT_SHOW_ICON, DEFAULT_THEME, DURATION_DEFAULT, DURATION_INFINITE, DURATION_LONG, DURATION_SHORT, MAX_VISIBLE_TOASTS, STACK_GAP, TOAST_GAP_PX, TOAST_WIDTH_PX, Z_INDEX_BASE, configure, initializeRenderer, toast, toastManager };
971
+ //# sourceMappingURL=index.js.map
972
+ //# sourceMappingURL=index.js.map