goey-toast 0.2.0 → 0.2.2

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 CHANGED
@@ -2,21 +2,43 @@
2
2
 
3
3
  var react = require('react');
4
4
  var sonner = require('sonner');
5
- var jsxRuntime = require('react/jsx-runtime');
6
5
  var framerMotion = require('framer-motion');
6
+ var jsxRuntime = require('react/jsx-runtime');
7
7
 
8
8
  // src/components/GoeyToaster.tsx
9
9
 
10
+ // src/presets.ts
11
+ var animationPresets = {
12
+ smooth: { bounce: 0.1, spring: true },
13
+ bouncy: { bounce: 0.6, spring: true },
14
+ subtle: { bounce: 0.05, spring: true },
15
+ snappy: { bounce: 0.4, spring: true }
16
+ };
17
+
10
18
  // src/context.ts
11
19
  var _position = "bottom-right";
20
+ var _dir = "ltr";
12
21
  var _spring = true;
13
22
  var _bounce = void 0;
23
+ var _theme = "light";
24
+ function setGoeyTheme(theme) {
25
+ _theme = theme;
26
+ }
27
+ function getGoeyTheme() {
28
+ return _theme;
29
+ }
14
30
  function setGoeyPosition(position) {
15
31
  _position = position;
16
32
  }
17
33
  function getGoeyPosition() {
18
34
  return _position;
19
35
  }
36
+ function setGoeyDir(dir) {
37
+ _dir = dir;
38
+ }
39
+ function getGoeyDir() {
40
+ return _dir;
41
+ }
20
42
  function setGoeySpring(spring) {
21
43
  _spring = spring;
22
44
  }
@@ -29,64 +51,66 @@ function setGoeyBounce(bounce) {
29
51
  function getGoeyBounce() {
30
52
  return _bounce;
31
53
  }
32
- function GoeyToaster({
33
- position = "bottom-right",
34
- duration,
35
- gap = 14,
36
- offset = "24px",
37
- theme = "light",
38
- toastOptions,
39
- expand,
40
- closeButton,
41
- richColors,
42
- visibleToasts,
43
- dir,
44
- spring = true,
45
- bounce
46
- }) {
47
- react.useEffect(() => {
48
- setGoeyPosition(position);
49
- }, [position]);
50
- react.useEffect(() => {
51
- setGoeySpring(spring);
52
- }, [spring]);
53
- react.useEffect(() => {
54
- setGoeyBounce(bounce);
55
- }, [bounce]);
56
- react.useEffect(() => {
57
- if (process.env.NODE_ENV !== "development") return;
58
- const el = document.createElement("div");
59
- el.setAttribute("data-goey-toast-css", "");
60
- el.style.position = "absolute";
61
- el.style.width = "0";
62
- el.style.height = "0";
63
- el.style.overflow = "hidden";
64
- el.style.pointerEvents = "none";
65
- document.body.appendChild(el);
66
- const value = getComputedStyle(el).getPropertyValue("--goey-toast");
67
- document.body.removeChild(el);
68
- if (!value) {
69
- console.warn(
70
- '[goey-toast] Styles not found. Make sure to import the CSS:\n\n import "goey-toast/styles.css";\n'
71
- );
72
- }
73
- }, []);
74
- return /* @__PURE__ */ jsxRuntime.jsx(
75
- sonner.Toaster,
76
- {
77
- position,
78
- duration,
79
- gap,
80
- offset,
81
- theme,
82
- toastOptions: { unstyled: true, ...toastOptions },
83
- expand,
84
- closeButton,
85
- richColors,
86
- visibleToasts,
87
- dir
88
- }
89
- );
54
+ var _visibleToasts = 3;
55
+ function setGoeyVisibleToasts(n) {
56
+ _visibleToasts = n;
57
+ }
58
+ function getGoeyVisibleToasts() {
59
+ return _visibleToasts;
60
+ }
61
+ var _swipeToDismiss = true;
62
+ function setGoeySwipeToDismiss(enabled) {
63
+ _swipeToDismiss = enabled;
64
+ }
65
+ function getGoeySwipeToDismiss() {
66
+ return _swipeToDismiss;
67
+ }
68
+ var _maxQueue = Infinity;
69
+ function setGoeyMaxQueue(n) {
70
+ _maxQueue = n;
71
+ }
72
+ function getGoeyMaxQueue() {
73
+ return _maxQueue;
74
+ }
75
+ var _queueOverflow = "drop-oldest";
76
+ function setGoeyQueueOverflow(strategy) {
77
+ _queueOverflow = strategy;
78
+ }
79
+ function getGoeyQueueOverflow() {
80
+ return _queueOverflow;
81
+ }
82
+ var _showProgress = false;
83
+ function setGoeyShowProgress(show) {
84
+ _showProgress = show;
85
+ }
86
+ function getGoeyShowProgress() {
87
+ return _showProgress;
88
+ }
89
+ var _containerHovered = false;
90
+ var _hoverSubs = /* @__PURE__ */ new Set();
91
+ function setContainerHovered(hovered) {
92
+ if (_containerHovered === hovered) return;
93
+ _containerHovered = hovered;
94
+ _hoverSubs.forEach((cb) => cb(hovered));
95
+ }
96
+ function getContainerHovered() {
97
+ return _containerHovered;
98
+ }
99
+ function subscribeContainerHovered(cb) {
100
+ _hoverSubs.add(cb);
101
+ return () => {
102
+ _hoverSubs.delete(cb);
103
+ };
104
+ }
105
+ var _announceSubs = /* @__PURE__ */ new Set();
106
+ function announce(message, politeness = "polite") {
107
+ _announceSubs.forEach((cb) => cb({ message, politeness }));
108
+ }
109
+ function subscribeAnnouncements(cb) {
110
+ _announceSubs.add(cb);
111
+ return () => {
112
+ _announceSubs.delete(cb);
113
+ };
90
114
  }
91
115
  var DefaultIcon = ({ size = 20, className }) => /* @__PURE__ */ jsxRuntime.jsxs(
92
116
  "svg",
@@ -249,8 +273,18 @@ var styles = {
249
273
  actionSuccess: "goey-actionSuccess",
250
274
  actionError: "goey-actionError",
251
275
  actionWarning: "goey-actionWarning",
252
- actionInfo: "goey-actionInfo"
276
+ actionInfo: "goey-actionInfo",
277
+ progressWrapper: "goey-progressWrapper",
278
+ progressBar: "goey-progressBar",
279
+ progressDefault: "goey-progressDefault",
280
+ progressSuccess: "goey-progressSuccess",
281
+ progressError: "goey-progressError",
282
+ progressWarning: "goey-progressWarning",
283
+ progressInfo: "goey-progressInfo",
284
+ progressPaused: "goey-progressPaused",
285
+ timestamp: "goey-timestamp"
253
286
  };
287
+ var useIsomorphicLayoutEffect = typeof window !== "undefined" ? react.useLayoutEffect : react.useEffect;
254
288
  var phaseIconMap = {
255
289
  default: DefaultIcon,
256
290
  success: SuccessIcon,
@@ -274,6 +308,14 @@ var actionColorMap = {
274
308
  warning: styles.actionWarning,
275
309
  info: styles.actionInfo
276
310
  };
311
+ var progressColorMap = {
312
+ loading: styles.progressInfo,
313
+ default: styles.progressDefault,
314
+ success: styles.progressSuccess,
315
+ error: styles.progressError,
316
+ warning: styles.progressWarning,
317
+ info: styles.progressInfo
318
+ };
277
319
  var PH = 34;
278
320
  var DEFAULT_DISPLAY_DURATION = 4e3;
279
321
  var DEFAULT_EXPAND_DUR = 0.6;
@@ -303,7 +345,7 @@ function registerSonnerObserver(ol, callback) {
303
345
  });
304
346
  observer.observe(ol, {
305
347
  attributes: true,
306
- attributeFilter: ["style"],
348
+ attributeFilter: ["style", "data-visible"],
307
349
  subtree: true,
308
350
  childList: true
309
351
  });
@@ -319,7 +361,7 @@ function registerSonnerObserver(ol, callback) {
319
361
  }
320
362
  };
321
363
  }
322
- function syncSonnerHeights(wrapperEl) {
364
+ function syncSonnerHeights(wrapperEl, includeOffsets = false) {
323
365
  if (!wrapperEl) return;
324
366
  const li = wrapperEl.closest("[data-sonner-toast]");
325
367
  if (!li?.parentElement) return;
@@ -327,15 +369,57 @@ function syncSonnerHeights(wrapperEl) {
327
369
  const toasts = Array.from(
328
370
  ol.querySelectorAll(":scope > [data-sonner-toast]")
329
371
  );
330
- for (const t of toasts) {
372
+ if (toasts.length === 0) return;
373
+ const heights = toasts.map((t) => {
374
+ if (t.getAttribute("data-visible") === "false") return 0;
331
375
  const content = t.firstElementChild;
332
- const height = content ? content.getBoundingClientRect().height : 0;
333
- if (height > 0) {
334
- t.style.setProperty("--initial-height", `${height}px`);
376
+ const h = content ? content.getBoundingClientRect().height : 0;
377
+ return h > 0 ? h : PH;
378
+ });
379
+ const isExpanded = includeOffsets && toasts[0]?.getAttribute("data-expanded") === "true";
380
+ if (isExpanded) {
381
+ for (const t of toasts) t.style.setProperty("transition", "none", "important");
382
+ }
383
+ for (let i = 0; i < toasts.length; i++) {
384
+ toasts[i].style.setProperty("--initial-height", `${heights[i]}px`);
385
+ }
386
+ if (!includeOffsets) {
387
+ if (isExpanded) {
388
+ for (const t of toasts) t.style.removeProperty("transition");
389
+ }
390
+ return;
391
+ }
392
+ const gapStr = getComputedStyle(ol).getPropertyValue("--gap").trim();
393
+ const gap = parseFloat(gapStr) || 14;
394
+ let runningOffset = 0;
395
+ for (let i = toasts.length - 1; i >= 0; i--) {
396
+ if (toasts[i].getAttribute("data-visible") === "false") {
397
+ toasts[i].style.setProperty("--offset", "0px");
398
+ continue;
399
+ }
400
+ toasts[i].style.setProperty("--offset", `${runningOffset}px`);
401
+ if (i > 0) {
402
+ runningOffset += heights[i] + gap;
335
403
  }
336
404
  }
405
+ if (isExpanded) {
406
+ void ol.offsetHeight;
407
+ for (const t of toasts) t.style.removeProperty("transition");
408
+ }
409
+ }
410
+ function memoizePath(fn) {
411
+ let lastArgs = null;
412
+ let lastResult = "";
413
+ return (pw, bw, th, t) => {
414
+ if (lastArgs && lastArgs[0] === pw && lastArgs[1] === bw && lastArgs[2] === th && lastArgs[3] === t) {
415
+ return lastResult;
416
+ }
417
+ lastResult = fn(pw, bw, th, t);
418
+ lastArgs = [pw, bw, th, t];
419
+ return lastResult;
420
+ };
337
421
  }
338
- function morphPath(pw, bw, th, t) {
422
+ function morphPathRaw(pw, bw, th, t) {
339
423
  const pr = PH / 2;
340
424
  const pillW = Math.min(pw, bw);
341
425
  const bodyH = PH + (th - PH) * t;
@@ -372,7 +456,7 @@ function morphPath(pw, bw, th, t) {
372
456
  `Z`
373
457
  ].join(" ");
374
458
  }
375
- function morphPathCenter(pw, bw, th, t) {
459
+ function morphPathCenterRaw(pw, bw, th, t) {
376
460
  const pr = PH / 2;
377
461
  const pillW = Math.min(pw, bw);
378
462
  const pillOffset = (bw - pillW) / 2;
@@ -435,6 +519,8 @@ function morphPathCenter(pw, bw, th, t) {
435
519
  `Z`
436
520
  ].join(" ");
437
521
  }
522
+ var morphPath = memoizePath(morphPathRaw);
523
+ var morphPathCenter = memoizePath(morphPathCenterRaw);
438
524
  var SMOOTH_EASE = [0.4, 0, 0.2, 1];
439
525
  var GoeyToast = ({
440
526
  title,
@@ -443,24 +529,35 @@ var GoeyToast = ({
443
529
  icon,
444
530
  phase,
445
531
  classNames,
446
- fillColor = "#ffffff",
532
+ fillColor: fillColorProp,
447
533
  borderColor,
448
534
  borderWidth,
449
535
  timing,
536
+ preset,
450
537
  spring: springProp,
451
538
  bounce: bounceProp,
539
+ showProgress: showProgressProp,
452
540
  toastId
453
541
  }) => {
542
+ const theme = getGoeyTheme();
543
+ const fillColor = fillColorProp ?? (theme === "dark" ? "#1a1a1a" : "#ffffff");
454
544
  const position = getGoeyPosition();
455
- const isRight = position?.includes("right") ?? false;
545
+ const dir = getGoeyDir();
546
+ const posIsRight = position?.includes("right") ?? false;
456
547
  const isCenter = position?.includes("center") ?? false;
548
+ const isRight = dir === "rtl" ? isCenter ? false : !posIsRight : posIsRight;
457
549
  const prefersReducedMotion = usePrefersReducedMotion();
458
- const useSpring = springProp ?? getGoeySpring();
459
- const bounceVal = bounceProp ?? getGoeyBounce() ?? 0.4;
550
+ const presetConfig = preset ? animationPresets[preset] : void 0;
551
+ const useSpring = springProp ?? presetConfig?.spring ?? getGoeySpring();
552
+ const bounceVal = bounceProp ?? presetConfig?.bounce ?? getGoeyBounce() ?? 0.4;
553
+ const showProgress = showProgressProp ?? getGoeyShowProgress();
460
554
  const [actionSuccess, setActionSuccess] = react.useState(null);
461
555
  const [dismissing, setDismissing] = react.useState(false);
556
+ const [progressKey, setProgressKey] = react.useState(0);
462
557
  const [hovered, setHovered] = react.useState(false);
463
558
  const hoveredRef = react.useRef(false);
559
+ const containerHoveredRef = react.useRef(getContainerHovered());
560
+ const [containerHovered, setContainerHoveredState] = react.useState(getContainerHovered());
464
561
  const collapsingRef = react.useRef(false);
465
562
  const preDismissRef = react.useRef(false);
466
563
  const collapseEndTime = react.useRef(0);
@@ -489,13 +586,21 @@ var GoeyToast = ({
489
586
  react.useEffect(() => {
490
587
  dimsRef.current = dims;
491
588
  }, [dims]);
589
+ react.useEffect(() => {
590
+ return subscribeContainerHovered((h) => {
591
+ containerHoveredRef.current = h;
592
+ setContainerHoveredState(h);
593
+ });
594
+ }, []);
492
595
  const flush = react.useCallback(() => {
493
596
  const { pw: p, bw: b, th: h } = aDims.current;
494
597
  if (p <= 0 || b <= 0 || h <= 0) return;
495
598
  const t = Math.max(0, Math.min(1, morphTRef.current));
496
599
  const pos = getGoeyPosition();
497
- const rightSide = pos?.includes("right") ?? false;
600
+ const d = getGoeyDir();
498
601
  const centerPos = pos?.includes("center") ?? false;
602
+ const posRight = pos?.includes("right") ?? false;
603
+ const rightSide = d === "rtl" ? centerPos ? false : !posRight : posRight;
499
604
  if (centerPos) {
500
605
  const centerBw = Math.max(dimsRef.current.bw, expandedDimsRef.current.bw, p);
501
606
  pathRef.current?.setAttribute("d", morphPathCenter(p, centerBw, h, t));
@@ -582,7 +687,7 @@ var GoeyToast = ({
582
687
  dimsRef.current = { pw: pw2, bw: bw2, th: th2 };
583
688
  setDims({ pw: pw2, bw: bw2, th: th2 });
584
689
  }, []);
585
- react.useLayoutEffect(() => {
690
+ useIsomorphicLayoutEffect(() => {
586
691
  measure();
587
692
  const t = setTimeout(measure, 100);
588
693
  return () => clearTimeout(t);
@@ -609,8 +714,8 @@ var GoeyToast = ({
609
714
  const el = wrapperRef.current;
610
715
  const springConfig = phase2 === "collapse" ? squishSpring(collapseDur, DEFAULT_COLLAPSE_DUR, bounceVal) : squishSpring(expandDur, DEFAULT_EXPAND_DUR, bounceVal);
611
716
  const bScale = bounceVal / 0.4;
612
- const compressY = (phase2 === "collapse" ? 0.07 : 0.12) * bScale;
613
- const expandX = (phase2 === "collapse" ? 0.035 : 0.06) * bScale;
717
+ const compressY = (phase2 === "collapse" ? 0.035 : 0.12) * bScale;
718
+ const expandX = (phase2 === "collapse" ? 0.018 : 0.06) * bScale;
614
719
  blobSquishCtrl.current = framerMotion.animate(0, 1, {
615
720
  ...springConfig,
616
721
  onUpdate: (v) => {
@@ -628,7 +733,7 @@ var GoeyToast = ({
628
733
  }
629
734
  });
630
735
  }, [prefersReducedMotion, expandDur, collapseDur, useSpring, bounceVal]);
631
- react.useLayoutEffect(() => {
736
+ useIsomorphicLayoutEffect(() => {
632
737
  if (!hasDims || collapsingRef.current) return;
633
738
  const prev = { ...aDims.current };
634
739
  const target = { pw, bw, th };
@@ -680,7 +785,7 @@ var GoeyToast = ({
680
785
  }
681
786
  }, [hasDims, squishDelayMs, triggerLandingSquish]);
682
787
  const prevShowBody = react.useRef(false);
683
- react.useLayoutEffect(() => {
788
+ useIsomorphicLayoutEffect(() => {
684
789
  if (!prevShowBody.current && showBody && !hoveredRef.current) {
685
790
  const t = setTimeout(() => triggerLandingSquish("expand"), 80);
686
791
  return () => clearTimeout(t);
@@ -749,6 +854,7 @@ var GoeyToast = ({
749
854
  th: targetDims.th + (savedDims.th - targetDims.th) * t
750
855
  };
751
856
  flush();
857
+ syncSonnerHeights(wrapperRef.current, true);
752
858
  },
753
859
  onComplete: () => {
754
860
  morphTRef.current = 0;
@@ -757,6 +863,7 @@ var GoeyToast = ({
757
863
  collapseEndTime.current = Date.now();
758
864
  aDims.current = { ...targetDims };
759
865
  flush();
866
+ syncSonnerHeights(wrapperRef.current, true);
760
867
  setShowBody(false);
761
868
  }
762
869
  });
@@ -770,17 +877,24 @@ var GoeyToast = ({
770
877
  }, [isExpanded, flush, prefersReducedMotion, useSpring, triggerLandingSquish]);
771
878
  const remainingRef = react.useRef(null);
772
879
  const timerStartRef = react.useRef(0);
880
+ const progressDelayRef = react.useRef(0);
773
881
  react.useEffect(() => {
774
882
  if (!showBody || actionSuccess || dismissing) return;
775
883
  const expandDelayMs = prefersReducedMotion ? 0 : 330;
776
884
  const collapseMs = prefersReducedMotion ? 10 : 0.9 * 1e3;
777
885
  const displayMs = timing?.displayDuration ?? DEFAULT_DISPLAY_DURATION;
778
886
  const fullDelay = displayMs - expandDelayMs - collapseMs;
887
+ progressDelayRef.current = Math.max(fullDelay, 0);
779
888
  if (fullDelay <= 0) return;
780
- if (hoveredRef.current) return;
889
+ if (hoveredRef.current || containerHoveredRef.current) return;
781
890
  const delay = remainingRef.current ?? fullDelay;
782
891
  timerStartRef.current = Date.now();
783
892
  const timer = setTimeout(() => {
893
+ if (hoveredRef.current || containerHoveredRef.current) {
894
+ const elapsed = Date.now() - timerStartRef.current;
895
+ remainingRef.current = Math.max(0, delay - elapsed);
896
+ return;
897
+ }
784
898
  remainingRef.current = null;
785
899
  expandedDimsRef.current = { ...aDims.current };
786
900
  collapsingRef.current = true;
@@ -792,15 +906,15 @@ var GoeyToast = ({
792
906
  clearTimeout(timer);
793
907
  const elapsed = Date.now() - timerStartRef.current;
794
908
  const remaining = delay - elapsed;
795
- if (remaining > 0 && hoveredRef.current) {
909
+ if (remaining > 0 && (hoveredRef.current || containerHoveredRef.current)) {
796
910
  remainingRef.current = remaining;
797
911
  }
798
912
  };
799
- }, [showBody, actionSuccess, dismissing, prefersReducedMotion, hovered]);
913
+ }, [showBody, actionSuccess, dismissing, prefersReducedMotion, hovered, containerHovered]);
800
914
  const canExpand = hasDescription || hasAction;
801
915
  const reExpandingRef = react.useRef(false);
802
916
  react.useEffect(() => {
803
- if (!hovered || !canExpand || !dismissing) return;
917
+ if (!hovered && !containerHovered || !canExpand || !dismissing) return;
804
918
  morphCtrl.current?.stop();
805
919
  collapsingRef.current = false;
806
920
  preDismissRef.current = false;
@@ -808,6 +922,7 @@ var GoeyToast = ({
808
922
  reExpandingRef.current = true;
809
923
  setDismissing(false);
810
924
  setShowBody(true);
925
+ if (showProgress) setProgressKey((k) => k + 1);
811
926
  const currentT = morphTRef.current;
812
927
  const startDims = { ...aDims.current };
813
928
  const morphExpandTransition = useSpring ? { type: "spring", duration: 0.9, bounce: bounceVal } : { duration: 0.6, ease: SMOOTH_EASE };
@@ -823,27 +938,30 @@ var GoeyToast = ({
823
938
  th: startDims.th + (target.th - startDims.th) * t
824
939
  };
825
940
  flush();
941
+ syncSonnerHeights(wrapperRef.current, true);
826
942
  },
827
943
  onComplete: () => {
828
944
  morphTRef.current = 1;
829
945
  aDims.current = { ...dimsRef.current };
830
946
  reExpandingRef.current = false;
831
947
  flush();
832
- syncSonnerHeights(wrapperRef.current);
948
+ syncSonnerHeights(wrapperRef.current, true);
833
949
  }
834
950
  });
835
951
  });
836
952
  return () => {
837
953
  morphCtrl.current?.stop();
838
954
  };
839
- }, [hovered, dismissing, canExpand]);
955
+ }, [hovered, containerHovered, dismissing, canExpand]);
840
956
  react.useEffect(() => {
841
- if (!toastId || !dismissing || showBody || hovered) return;
957
+ if (!toastId || !dismissing || showBody) return;
842
958
  const t = setTimeout(() => {
843
- if (!hoveredRef.current) sonner.toast.dismiss(toastId);
959
+ if (!hoveredRef.current && !containerHoveredRef.current) {
960
+ sonner.toast.dismiss(toastId);
961
+ }
844
962
  }, 800);
845
963
  return () => clearTimeout(t);
846
- }, [dismissing, showBody, hovered, toastId]);
964
+ }, [dismissing, showBody, toastId]);
847
965
  react.useEffect(() => {
848
966
  if (!toastId || !actionSuccess || showBody) return;
849
967
  const t = setTimeout(() => sonner.toast.dismiss(toastId), 1200);
@@ -863,7 +981,7 @@ var GoeyToast = ({
863
981
  morphTRef.current = 1;
864
982
  aDims.current = { ...dimsRef.current };
865
983
  flush();
866
- syncSonnerHeights(wrapperRef.current);
984
+ syncSonnerHeights(wrapperRef.current, true);
867
985
  return;
868
986
  }
869
987
  const raf = requestAnimationFrame(() => {
@@ -882,12 +1000,13 @@ var GoeyToast = ({
882
1000
  th: startDims.th + (target.th - startDims.th) * t
883
1001
  };
884
1002
  flush();
1003
+ syncSonnerHeights(wrapperRef.current, true);
885
1004
  },
886
1005
  onComplete: () => {
887
1006
  morphTRef.current = 1;
888
1007
  aDims.current = { ...dimsRef.current };
889
1008
  flush();
890
- syncSonnerHeights(wrapperRef.current);
1009
+ syncSonnerHeights(wrapperRef.current, true);
891
1010
  }
892
1011
  });
893
1012
  });
@@ -937,9 +1056,26 @@ var GoeyToast = ({
937
1056
  if (!wrapper) return;
938
1057
  const ol = wrapper.closest("[data-sonner-toast]")?.parentElement;
939
1058
  if (!ol) return;
940
- return registerSonnerObserver(ol, () => {
941
- syncSonnerHeights(wrapper);
1059
+ const unregister = registerSonnerObserver(ol, () => {
1060
+ syncSonnerHeights(wrapper, true);
1061
+ });
1062
+ const expandObs = new MutationObserver((mutations) => {
1063
+ for (const m of mutations) {
1064
+ if (m.type === "attributes" && m.attributeName === "data-expanded" && m.target.getAttribute("data-expanded") === "true") {
1065
+ syncSonnerHeights(wrapper, true);
1066
+ break;
1067
+ }
1068
+ }
1069
+ });
1070
+ expandObs.observe(ol, {
1071
+ attributes: true,
1072
+ attributeFilter: ["data-expanded"],
1073
+ subtree: true
942
1074
  });
1075
+ return () => {
1076
+ unregister();
1077
+ expandObs.disconnect();
1078
+ };
943
1079
  }, []);
944
1080
  const handleActionClick = react.useCallback(() => {
945
1081
  if (!effectiveAction) return;
@@ -953,13 +1089,56 @@ var GoeyToast = ({
953
1089
  } catch {
954
1090
  }
955
1091
  }, [effectiveAction]);
1092
+ const SWIPE_THRESHOLD = 100;
1093
+ const swipeStartRef = react.useRef(null);
1094
+ const [swipeOffsetX, setSwipeOffsetX] = react.useState(0);
1095
+ const isSwipingRef = react.useRef(false);
1096
+ const handleTouchStart = react.useCallback((e) => {
1097
+ if (!getGoeySwipeToDismiss()) return;
1098
+ const touch = e.touches[0];
1099
+ swipeStartRef.current = { x: touch.clientX, y: touch.clientY };
1100
+ isSwipingRef.current = false;
1101
+ }, []);
1102
+ const handleTouchMove = react.useCallback((e) => {
1103
+ if (!swipeStartRef.current || !getGoeySwipeToDismiss()) return;
1104
+ const touch = e.touches[0];
1105
+ const dx = touch.clientX - swipeStartRef.current.x;
1106
+ const dy = touch.clientY - swipeStartRef.current.y;
1107
+ if (!isSwipingRef.current && Math.abs(dy) > Math.abs(dx) && Math.abs(dy) > 10) {
1108
+ swipeStartRef.current = null;
1109
+ return;
1110
+ }
1111
+ if (!isSwipingRef.current && Math.abs(dx) > 10) {
1112
+ isSwipingRef.current = true;
1113
+ }
1114
+ if (isSwipingRef.current) {
1115
+ setSwipeOffsetX(dx);
1116
+ }
1117
+ }, []);
1118
+ const handleTouchEnd = react.useCallback(() => {
1119
+ if (!getGoeySwipeToDismiss()) {
1120
+ swipeStartRef.current = null;
1121
+ return;
1122
+ }
1123
+ if (isSwipingRef.current && Math.abs(swipeOffsetX) >= SWIPE_THRESHOLD && toastId) {
1124
+ sonner.toast.dismiss(toastId);
1125
+ }
1126
+ swipeStartRef.current = null;
1127
+ isSwipingRef.current = false;
1128
+ setSwipeOffsetX(0);
1129
+ }, [swipeOffsetX, toastId]);
1130
+ const swipeOpacity = swipeOffsetX !== 0 ? Math.max(0, 1 - Math.abs(swipeOffsetX) / (SWIPE_THRESHOLD * 1.5)) : 1;
1131
+ const swipeTranslate = swipeOffsetX !== 0 ? `translateX(${swipeOffsetX}px)` : "";
956
1132
  const renderIcon = () => {
957
1133
  if (!actionSuccess && icon) return icon;
958
1134
  if (isLoading) return /* @__PURE__ */ jsxRuntime.jsx(SpinnerIcon, { size: 18, className: styles.spinnerSpin });
959
1135
  const IconComponent = phaseIconMap[effectivePhase];
960
1136
  return /* @__PURE__ */ jsxRuntime.jsx(IconComponent, { size: 18 });
961
1137
  };
962
- const iconTransition = prefersReducedMotion ? { duration: 0.01 } : { duration: 0.2 };
1138
+ const iconTransition = react.useMemo(
1139
+ () => prefersReducedMotion ? { duration: 0.01 } : { duration: 0.2 },
1140
+ [prefersReducedMotion]
1141
+ );
963
1142
  const iconEl = /* @__PURE__ */ jsxRuntime.jsx("div", { className: `${styles.iconWrapper}${classNames?.icon ? ` ${classNames.icon}` : ""}`, children: /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { mode: "wait", children: /* @__PURE__ */ jsxRuntime.jsx(
964
1143
  framerMotion.motion.div,
965
1144
  {
@@ -972,17 +1151,43 @@ var GoeyToast = ({
972
1151
  isLoading ? "spinner" : effectivePhase
973
1152
  ) }) });
974
1153
  const titleEl = /* @__PURE__ */ jsxRuntime.jsx("span", { className: `${styles.title}${classNames?.title ? ` ${classNames.title}` : ""}`, children: effectiveTitle });
1154
+ const createdAtRef = react.useRef(/* @__PURE__ */ new Date());
1155
+ const timestampStr = react.useMemo(
1156
+ () => createdAtRef.current.toLocaleTimeString(void 0, { hour: "numeric", minute: "2-digit", second: "2-digit" }),
1157
+ []
1158
+ );
975
1159
  const iconAndTitle = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
976
1160
  iconEl,
977
1161
  titleEl
978
1162
  ] });
979
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: wrapperRef, className: `${styles.wrapper}${classNames?.wrapper ? ` ${classNames.wrapper}` : ""}`, style: isCenter ? { margin: "0 auto" } : isRight ? { marginLeft: "auto", transform: "scaleX(-1)" } : void 0, role: effectivePhase === "error" ? "alert" : "status", "aria-live": effectivePhase === "error" ? "assertive" : "polite", "aria-atomic": "true", onMouseEnter: () => {
1163
+ const basePositionStyle = react.useMemo(
1164
+ () => isCenter ? { margin: "0 auto" } : isRight ? { marginLeft: "auto", transform: "scaleX(-1)" } : {},
1165
+ [isCenter, isRight]
1166
+ );
1167
+ const wrapperStyle = react.useMemo(() => {
1168
+ if (swipeTranslate) {
1169
+ return {
1170
+ ...basePositionStyle,
1171
+ transform: (basePositionStyle.transform ? basePositionStyle.transform + " " : "") + swipeTranslate,
1172
+ opacity: swipeOpacity,
1173
+ transition: "none"
1174
+ };
1175
+ }
1176
+ return Object.keys(basePositionStyle).length > 0 ? basePositionStyle : void 0;
1177
+ }, [basePositionStyle, swipeTranslate, swipeOpacity]);
1178
+ const contentStyle = react.useMemo(
1179
+ () => isCenter ? { textAlign: "center" } : isRight ? { transform: "scaleX(-1)", textAlign: "right" } : { textAlign: "left" },
1180
+ [isCenter, isRight]
1181
+ );
1182
+ const handleMouseEnter = react.useCallback(() => {
980
1183
  hoveredRef.current = true;
981
1184
  setHovered(true);
982
- }, onMouseLeave: () => {
1185
+ }, []);
1186
+ const handleMouseLeave = react.useCallback(() => {
983
1187
  hoveredRef.current = false;
984
1188
  setHovered(false);
985
- }, "data-center": isCenter || void 0, children: [
1189
+ }, []);
1190
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: wrapperRef, className: `${styles.wrapper}${classNames?.wrapper ? ` ${classNames.wrapper}` : ""}`, style: wrapperStyle, role: effectivePhase === "error" || effectivePhase === "warning" ? "alert" : "status", "aria-live": effectivePhase === "error" || effectivePhase === "warning" ? "assertive" : "polite", "aria-atomic": "true", onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onTouchStart: handleTouchStart, onTouchMove: handleTouchMove, onTouchEnd: handleTouchEnd, "data-center": isCenter || void 0, "data-theme": theme, children: [
986
1191
  /* @__PURE__ */ jsxRuntime.jsx(
987
1192
  "svg",
988
1193
  {
@@ -1004,10 +1209,13 @@ var GoeyToast = ({
1004
1209
  {
1005
1210
  ref: contentRef,
1006
1211
  className: `${styles.content} ${showBody ? styles.contentExpanded : styles.contentCompact}${classNames?.content ? ` ${classNames.content}` : ""}`,
1007
- style: isCenter ? { textAlign: "center" } : isRight ? { transform: "scaleX(-1)", textAlign: "right" } : { textAlign: "left" },
1212
+ style: contentStyle,
1008
1213
  children: [
1009
- /* @__PURE__ */ jsxRuntime.jsx("div", { ref: headerRef, className: `${styles.header} ${titleColorMap[effectivePhase]}${classNames?.header ? ` ${classNames.header}` : ""}`, children: iconAndTitle }),
1010
- /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: showBody && hasDescription && !dismissing && /* @__PURE__ */ jsxRuntime.jsx(
1214
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: headerRef, className: `${styles.header} ${titleColorMap[effectivePhase]}${classNames?.header ? ` ${classNames.header}` : ""}`, children: [
1215
+ iconAndTitle,
1216
+ !hasDescription && !hasAction && !actionSuccess && /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles.timestamp, children: timestampStr })
1217
+ ] }),
1218
+ /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: showBody && hasDescription && !dismissing && /* @__PURE__ */ jsxRuntime.jsxs(
1011
1219
  framerMotion.motion.div,
1012
1220
  {
1013
1221
  className: `${styles.description}${classNames?.description ? ` ${classNames.description}` : ""}`,
@@ -1016,10 +1224,26 @@ var GoeyToast = ({
1016
1224
  animate: { opacity: 1 },
1017
1225
  exit: { opacity: 0 },
1018
1226
  transition: prefersReducedMotion ? { duration: 0.01 } : { duration: 0.35, ease: [0.4, 0, 0.2, 1] },
1019
- children: effectiveDescription
1227
+ children: [
1228
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles.timestamp, style: { float: "right", marginLeft: 10, marginTop: 3, paddingLeft: 0 }, children: timestampStr }),
1229
+ effectiveDescription
1230
+ ]
1020
1231
  },
1021
1232
  "description"
1022
1233
  ) }),
1234
+ /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: showBody && !hasDescription && hasAction && !dismissing && /* @__PURE__ */ jsxRuntime.jsx(
1235
+ framerMotion.motion.div,
1236
+ {
1237
+ className: styles.timestamp,
1238
+ style: { textAlign: "right", marginTop: 8, paddingLeft: 0 },
1239
+ initial: prefersReducedMotion ? false : { opacity: 0 },
1240
+ animate: { opacity: 1 },
1241
+ exit: { opacity: 0 },
1242
+ transition: prefersReducedMotion ? { duration: 0.01 } : { duration: 0.35, ease: [0.4, 0, 0.2, 1] },
1243
+ children: timestampStr
1244
+ },
1245
+ "timestamp-body"
1246
+ ) }),
1023
1247
  /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: showBody && hasAction && effectiveAction && !dismissing && /* @__PURE__ */ jsxRuntime.jsx(
1024
1248
  framerMotion.motion.div,
1025
1249
  {
@@ -1040,7 +1264,22 @@ var GoeyToast = ({
1040
1264
  )
1041
1265
  },
1042
1266
  "action"
1043
- ) })
1267
+ ) }),
1268
+ showProgress && /* @__PURE__ */ jsxRuntime.jsx(
1269
+ "div",
1270
+ {
1271
+ className: `${styles.progressWrapper}${hovered || containerHovered ? ` ${styles.progressPaused}` : ""}`,
1272
+ style: { opacity: showBody && !actionSuccess ? 1 : 0 },
1273
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1274
+ "div",
1275
+ {
1276
+ className: `${styles.progressBar} ${progressColorMap[effectivePhase]}`,
1277
+ style: { "--goey-progress-duration": `${progressDelayRef.current || (timing?.displayDuration ?? DEFAULT_DISPLAY_DURATION)}ms` }
1278
+ }
1279
+ )
1280
+ },
1281
+ progressKey
1282
+ )
1044
1283
  ]
1045
1284
  }
1046
1285
  )
@@ -1067,22 +1306,139 @@ var ToastErrorBoundary = class extends react.Component {
1067
1306
  }
1068
1307
  };
1069
1308
  var DEFAULT_EXPANDED_DURATION = 4e3;
1309
+ function getAnnouncePoliteness(type) {
1310
+ return type === "error" || type === "warning" ? "assertive" : "polite";
1311
+ }
1312
+ function buildAnnouncementMessage(title, description) {
1313
+ if (!description || typeof description !== "string") return title;
1314
+ return `${title}: ${description}`;
1315
+ }
1316
+ var _activeIds = /* @__PURE__ */ new Map();
1317
+ var _queue = [];
1318
+ var _toastCallbacks = /* @__PURE__ */ new Map();
1319
+ var _autoCloseFlags = /* @__PURE__ */ new Set();
1320
+ var _manualDismissFlags = /* @__PURE__ */ new Set();
1321
+ function _getMostRecentActiveId() {
1322
+ let last;
1323
+ for (const id of _activeIds.keys()) last = id;
1324
+ return last;
1325
+ }
1326
+ function _processQueue() {
1327
+ const max = getGoeyVisibleToasts();
1328
+ while (_queue.length > 0 && _activeIds.size < max) {
1329
+ const next = _queue.shift();
1330
+ _activeIds.set(next.id, next.type);
1331
+ next.create();
1332
+ }
1333
+ }
1334
+ function _enqueue(entry) {
1335
+ const maxQueue = getGoeyMaxQueue();
1336
+ const overflow = getGoeyQueueOverflow();
1337
+ if (_queue.length >= maxQueue) {
1338
+ if (overflow === "drop-newest") return false;
1339
+ _queue.shift();
1340
+ }
1341
+ _queue.push(entry);
1342
+ return true;
1343
+ }
1344
+ function _onToastDismissed(id) {
1345
+ if (!_activeIds.delete(id)) return;
1346
+ _toastUpdateListeners.delete(id);
1347
+ const cbs = _toastCallbacks.get(id);
1348
+ if (cbs) {
1349
+ const isAutoClose = _autoCloseFlags.has(id) || !_manualDismissFlags.has(id);
1350
+ if (isAutoClose && cbs.onAutoClose) {
1351
+ try {
1352
+ cbs.onAutoClose(id);
1353
+ } catch {
1354
+ }
1355
+ }
1356
+ if (cbs.onDismiss) {
1357
+ try {
1358
+ cbs.onDismiss(id);
1359
+ } catch {
1360
+ }
1361
+ }
1362
+ _toastCallbacks.delete(id);
1363
+ }
1364
+ _autoCloseFlags.delete(id);
1365
+ _manualDismissFlags.delete(id);
1366
+ _processQueue();
1367
+ }
1368
+ var _toastUpdateListeners = /* @__PURE__ */ new Map();
1369
+ function updateGoeyToast(id, options) {
1370
+ const listener = _toastUpdateListeners.get(id);
1371
+ if (listener) {
1372
+ listener(options);
1373
+ if (options.type !== void 0 && _activeIds.has(id)) {
1374
+ _activeIds.set(id, options.type);
1375
+ }
1376
+ if (options.title !== void 0) {
1377
+ announce(
1378
+ buildAnnouncementMessage(options.title, options.description),
1379
+ options.type ? getAnnouncePoliteness(options.type) : "polite"
1380
+ );
1381
+ }
1382
+ }
1383
+ }
1070
1384
  function GoeyToastWrapper({
1071
1385
  initialPhase,
1072
- title,
1073
- type,
1074
- description,
1075
- action,
1386
+ title: initialTitle,
1387
+ type: initialType,
1388
+ description: initialDescription,
1389
+ action: initialAction,
1076
1390
  icon,
1077
1391
  classNames,
1078
1392
  fillColor,
1079
1393
  borderColor,
1080
1394
  borderWidth,
1081
1395
  timing,
1396
+ preset,
1082
1397
  spring,
1083
1398
  bounce,
1084
- toastId
1399
+ showProgress,
1400
+ toastId,
1401
+ activeId,
1402
+ onDismiss,
1403
+ onAutoClose
1085
1404
  }) {
1405
+ react.useEffect(() => {
1406
+ if (onDismiss || onAutoClose) {
1407
+ _toastCallbacks.set(activeId, { onDismiss, onAutoClose });
1408
+ }
1409
+ }, [activeId, onDismiss, onAutoClose]);
1410
+ const [title, setTitle] = react.useState(initialTitle);
1411
+ const [type, setType] = react.useState(initialType);
1412
+ const [phase, setPhase] = react.useState(initialPhase);
1413
+ const [description, setDescription] = react.useState(initialDescription);
1414
+ const [action, setAction] = react.useState(initialAction);
1415
+ const [currentIcon, setCurrentIcon] = react.useState(icon);
1416
+ react.useEffect(() => {
1417
+ const handleUpdate = (opts) => {
1418
+ if (opts.title !== void 0) setTitle(opts.title);
1419
+ if (opts.description !== void 0) setDescription(opts.description);
1420
+ if (opts.type !== void 0) {
1421
+ setType(opts.type);
1422
+ setPhase(opts.type);
1423
+ }
1424
+ if (opts.action !== void 0) setAction(opts.action);
1425
+ if ("icon" in opts) setCurrentIcon(opts.icon ?? void 0);
1426
+ };
1427
+ _toastUpdateListeners.set(activeId, handleUpdate);
1428
+ return () => {
1429
+ _toastUpdateListeners.delete(activeId);
1430
+ };
1431
+ }, [activeId]);
1432
+ const mountedRef = react.useRef(true);
1433
+ react.useEffect(() => {
1434
+ mountedRef.current = true;
1435
+ return () => {
1436
+ mountedRef.current = false;
1437
+ setTimeout(() => {
1438
+ if (!mountedRef.current) _onToastDismissed(activeId);
1439
+ }, 100);
1440
+ };
1441
+ }, [activeId]);
1086
1442
  return /* @__PURE__ */ jsxRuntime.jsx(ToastErrorBoundary, { children: /* @__PURE__ */ jsxRuntime.jsx(
1087
1443
  GoeyToast,
1088
1444
  {
@@ -1090,15 +1446,17 @@ function GoeyToastWrapper({
1090
1446
  description,
1091
1447
  type,
1092
1448
  action,
1093
- icon,
1094
- phase: initialPhase,
1449
+ icon: currentIcon,
1450
+ phase,
1095
1451
  classNames,
1096
1452
  fillColor,
1097
1453
  borderColor,
1098
1454
  borderWidth,
1099
1455
  timing,
1456
+ preset,
1100
1457
  spring,
1101
1458
  bounce,
1459
+ showProgress,
1102
1460
  toastId
1103
1461
  }
1104
1462
  ) });
@@ -1112,6 +1470,21 @@ function PromiseToastWrapper({
1112
1470
  const [title, setTitle] = react.useState(data.loading);
1113
1471
  const [description, setDescription] = react.useState(data.description?.loading);
1114
1472
  const [action, setAction] = react.useState(void 0);
1473
+ react.useEffect(() => {
1474
+ if (data.onDismiss || data.onAutoClose) {
1475
+ _toastCallbacks.set(toastId, { onDismiss: data.onDismiss, onAutoClose: data.onAutoClose });
1476
+ }
1477
+ }, [toastId, data.onDismiss, data.onAutoClose]);
1478
+ const mountedRef = react.useRef(true);
1479
+ react.useEffect(() => {
1480
+ mountedRef.current = true;
1481
+ return () => {
1482
+ mountedRef.current = false;
1483
+ setTimeout(() => {
1484
+ if (!mountedRef.current) _onToastDismissed(toastId);
1485
+ }, 100);
1486
+ };
1487
+ }, [toastId]);
1115
1488
  react.useEffect(() => {
1116
1489
  const resetDuration = (hasExpandedContent) => {
1117
1490
  const baseDuration = data.timing?.displayDuration ?? (hasExpandedContent ? DEFAULT_EXPANDED_DURATION : void 0);
@@ -1123,22 +1496,22 @@ function PromiseToastWrapper({
1123
1496
  };
1124
1497
  promise.then((result) => {
1125
1498
  const desc = typeof data.description?.success === "function" ? data.description.success(result) : data.description?.success;
1126
- setTitle(
1127
- typeof data.success === "function" ? data.success(result) : data.success
1128
- );
1499
+ const resolvedTitle = typeof data.success === "function" ? data.success(result) : data.success;
1500
+ setTitle(resolvedTitle);
1129
1501
  setDescription(desc);
1130
1502
  setAction(data.action?.success);
1131
1503
  setPhase("success");
1132
1504
  resetDuration(Boolean(desc || data.action?.success));
1505
+ announce(buildAnnouncementMessage(resolvedTitle, desc), "polite");
1133
1506
  }).catch((err) => {
1134
1507
  const desc = typeof data.description?.error === "function" ? data.description.error(err) : data.description?.error;
1135
- setTitle(
1136
- typeof data.error === "function" ? data.error(err) : data.error
1137
- );
1508
+ const resolvedTitle = typeof data.error === "function" ? data.error(err) : data.error;
1509
+ setTitle(resolvedTitle);
1138
1510
  setDescription(desc);
1139
1511
  setAction(data.action?.error);
1140
1512
  setPhase("error");
1141
1513
  resetDuration(Boolean(desc || data.action?.error));
1514
+ announce(buildAnnouncementMessage(resolvedTitle, desc), "assertive");
1142
1515
  });
1143
1516
  }, []);
1144
1517
  return /* @__PURE__ */ jsxRuntime.jsx(ToastErrorBoundary, { children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -1154,6 +1527,7 @@ function PromiseToastWrapper({
1154
1527
  borderColor: data.borderColor,
1155
1528
  borderWidth: data.borderWidth,
1156
1529
  timing: data.timing,
1530
+ preset: data.preset,
1157
1531
  spring: data.spring,
1158
1532
  bounce: data.bounce
1159
1533
  }
@@ -1164,31 +1538,84 @@ function createGoeyToast(title, type, options) {
1164
1538
  const baseDuration = options?.timing?.displayDuration ?? options?.duration ?? (options?.description ? DEFAULT_EXPANDED_DURATION : void 0);
1165
1539
  const duration = hasExpandedContent ? Infinity : baseDuration;
1166
1540
  const toastId = options?.id ?? Math.random().toString(36).slice(2);
1167
- return sonner.toast.custom(
1168
- () => /* @__PURE__ */ jsxRuntime.jsx(
1169
- GoeyToastWrapper,
1541
+ const create = () => {
1542
+ sonner.toast.custom(
1543
+ () => /* @__PURE__ */ jsxRuntime.jsx(
1544
+ GoeyToastWrapper,
1545
+ {
1546
+ initialPhase: type,
1547
+ title,
1548
+ type,
1549
+ description: options?.description,
1550
+ action: options?.action,
1551
+ icon: options?.icon,
1552
+ classNames: options?.classNames,
1553
+ fillColor: options?.fillColor,
1554
+ borderColor: options?.borderColor,
1555
+ borderWidth: options?.borderWidth,
1556
+ timing: options?.timing,
1557
+ preset: options?.preset,
1558
+ spring: options?.spring,
1559
+ bounce: options?.bounce,
1560
+ showProgress: options?.showProgress,
1561
+ toastId: hasExpandedContent ? toastId : void 0,
1562
+ activeId: toastId,
1563
+ onDismiss: options?.onDismiss,
1564
+ onAutoClose: options?.onAutoClose
1565
+ }
1566
+ ),
1170
1567
  {
1171
- initialPhase: type,
1172
- title,
1173
- type,
1174
- description: options?.description,
1175
- action: options?.action,
1176
- icon: options?.icon,
1177
- classNames: options?.classNames,
1178
- fillColor: options?.fillColor,
1179
- borderColor: options?.borderColor,
1180
- borderWidth: options?.borderWidth,
1181
- timing: options?.timing,
1182
- spring: options?.spring,
1183
- bounce: options?.bounce,
1184
- toastId: hasExpandedContent ? toastId : void 0
1568
+ duration,
1569
+ id: toastId
1185
1570
  }
1186
- ),
1187
- {
1188
- duration,
1189
- id: toastId
1190
- }
1571
+ );
1572
+ };
1573
+ if (options?.onDismiss || options?.onAutoClose) {
1574
+ _toastCallbacks.set(toastId, { onDismiss: options.onDismiss, onAutoClose: options.onAutoClose });
1575
+ }
1576
+ announce(
1577
+ buildAnnouncementMessage(title, options?.description),
1578
+ getAnnouncePoliteness(type)
1191
1579
  );
1580
+ if (_activeIds.size < getGoeyVisibleToasts()) {
1581
+ _activeIds.set(toastId, type);
1582
+ create();
1583
+ } else {
1584
+ _enqueue({ id: toastId, type, create });
1585
+ }
1586
+ return toastId;
1587
+ }
1588
+ function dismissGoeyToast(idOrFilter) {
1589
+ if (idOrFilter != null && typeof idOrFilter === "object") {
1590
+ const filterTypes = Array.isArray(idOrFilter.type) ? idOrFilter.type : [idOrFilter.type];
1591
+ const typesSet = new Set(filterTypes);
1592
+ for (let i = _queue.length - 1; i >= 0; i--) {
1593
+ if (typesSet.has(_queue[i].type)) {
1594
+ _queue.splice(i, 1);
1595
+ }
1596
+ }
1597
+ for (const [id, toastType] of _activeIds) {
1598
+ if (typesSet.has(toastType)) {
1599
+ _manualDismissFlags.add(id);
1600
+ sonner.toast.dismiss(id);
1601
+ }
1602
+ }
1603
+ } else if (idOrFilter != null) {
1604
+ const idx = _queue.findIndex((q) => q.id === idOrFilter);
1605
+ if (idx !== -1) {
1606
+ _queue.splice(idx, 1);
1607
+ return;
1608
+ }
1609
+ _manualDismissFlags.add(idOrFilter);
1610
+ sonner.toast.dismiss(idOrFilter);
1611
+ } else {
1612
+ for (const id of _activeIds.keys()) {
1613
+ _manualDismissFlags.add(id);
1614
+ }
1615
+ _queue.length = 0;
1616
+ _activeIds.clear();
1617
+ sonner.toast.dismiss();
1618
+ }
1192
1619
  }
1193
1620
  var goeyToast = Object.assign(
1194
1621
  (title, options) => createGoeyToast(title, "default", options),
@@ -1199,16 +1626,237 @@ var goeyToast = Object.assign(
1199
1626
  info: (title, options) => createGoeyToast(title, "info", options),
1200
1627
  promise: (promise, data) => {
1201
1628
  const id = Math.random().toString(36).slice(2);
1202
- return sonner.toast.custom(() => /* @__PURE__ */ jsxRuntime.jsx(PromiseToastWrapper, { promise, data, toastId: id }), {
1203
- id,
1204
- duration: data.timing?.displayDuration != null || data.description ? Infinity : void 0
1205
- });
1629
+ announce(buildAnnouncementMessage(data.loading, data.description?.loading), "polite");
1630
+ if (data.onDismiss || data.onAutoClose) {
1631
+ _toastCallbacks.set(id, { onDismiss: data.onDismiss, onAutoClose: data.onAutoClose });
1632
+ }
1633
+ const create = () => {
1634
+ sonner.toast.custom(() => /* @__PURE__ */ jsxRuntime.jsx(PromiseToastWrapper, { promise, data, toastId: id }), {
1635
+ id,
1636
+ duration: data.timing?.displayDuration != null || data.description ? Infinity : void 0
1637
+ });
1638
+ };
1639
+ if (_activeIds.size < getGoeyVisibleToasts()) {
1640
+ _activeIds.set(id, "info");
1641
+ create();
1642
+ } else {
1643
+ _enqueue({ id, type: "info", create });
1644
+ }
1645
+ return id;
1206
1646
  },
1207
- dismiss: sonner.toast.dismiss
1647
+ dismiss: dismissGoeyToast,
1648
+ update: updateGoeyToast
1208
1649
  }
1209
1650
  );
1651
+ function AriaLiveAnnouncer() {
1652
+ const [politeMessage, setPoliteMessage] = react.useState("");
1653
+ const [assertiveMessage, setAssertiveMessage] = react.useState("");
1654
+ const handleAnnouncement = react.useCallback(({ message, politeness }) => {
1655
+ if (politeness === "assertive") {
1656
+ setAssertiveMessage("");
1657
+ requestAnimationFrame(() => setAssertiveMessage(message));
1658
+ } else {
1659
+ setPoliteMessage("");
1660
+ requestAnimationFrame(() => setPoliteMessage(message));
1661
+ }
1662
+ }, []);
1663
+ react.useEffect(() => {
1664
+ return subscribeAnnouncements(handleAnnouncement);
1665
+ }, [handleAnnouncement]);
1666
+ react.useEffect(() => {
1667
+ if (!politeMessage) return;
1668
+ const t = setTimeout(() => setPoliteMessage(""), 7e3);
1669
+ return () => clearTimeout(t);
1670
+ }, [politeMessage]);
1671
+ react.useEffect(() => {
1672
+ if (!assertiveMessage) return;
1673
+ const t = setTimeout(() => setAssertiveMessage(""), 7e3);
1674
+ return () => clearTimeout(t);
1675
+ }, [assertiveMessage]);
1676
+ const visuallyHidden = {
1677
+ position: "absolute",
1678
+ width: "1px",
1679
+ height: "1px",
1680
+ padding: 0,
1681
+ margin: "-1px",
1682
+ overflow: "hidden",
1683
+ clip: "rect(0, 0, 0, 0)",
1684
+ whiteSpace: "nowrap",
1685
+ border: 0
1686
+ };
1687
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1688
+ /* @__PURE__ */ jsxRuntime.jsx(
1689
+ "div",
1690
+ {
1691
+ role: "status",
1692
+ "aria-live": "polite",
1693
+ "aria-atomic": "true",
1694
+ style: visuallyHidden,
1695
+ children: politeMessage
1696
+ }
1697
+ ),
1698
+ /* @__PURE__ */ jsxRuntime.jsx(
1699
+ "div",
1700
+ {
1701
+ role: "alert",
1702
+ "aria-live": "assertive",
1703
+ "aria-atomic": "true",
1704
+ style: visuallyHidden,
1705
+ children: assertiveMessage
1706
+ }
1707
+ )
1708
+ ] });
1709
+ }
1710
+ function GoeyToaster({
1711
+ position = "bottom-right",
1712
+ duration,
1713
+ gap = 14,
1714
+ offset = "24px",
1715
+ theme = "light",
1716
+ toastOptions,
1717
+ expand,
1718
+ closeButton,
1719
+ richColors,
1720
+ visibleToasts,
1721
+ dir,
1722
+ preset,
1723
+ spring,
1724
+ bounce,
1725
+ swipeToDismiss = true,
1726
+ closeOnEscape = true,
1727
+ maxQueue = Infinity,
1728
+ queueOverflow = "drop-oldest",
1729
+ showProgress = false
1730
+ }) {
1731
+ const presetConfig = preset ? animationPresets[preset] : void 0;
1732
+ const resolvedSpring = spring ?? presetConfig?.spring ?? true;
1733
+ const resolvedBounce = bounce ?? presetConfig?.bounce;
1734
+ react.useEffect(() => {
1735
+ setGoeyPosition(position);
1736
+ }, [position]);
1737
+ react.useEffect(() => {
1738
+ setGoeyDir(dir ?? "ltr");
1739
+ }, [dir]);
1740
+ react.useEffect(() => {
1741
+ setGoeyTheme(theme);
1742
+ }, [theme]);
1743
+ react.useEffect(() => {
1744
+ setGoeySpring(resolvedSpring);
1745
+ }, [resolvedSpring]);
1746
+ react.useEffect(() => {
1747
+ setGoeyBounce(resolvedBounce);
1748
+ }, [resolvedBounce]);
1749
+ react.useEffect(() => {
1750
+ setGoeySwipeToDismiss(swipeToDismiss);
1751
+ }, [swipeToDismiss]);
1752
+ react.useEffect(() => {
1753
+ }, [closeOnEscape]);
1754
+ react.useEffect(() => {
1755
+ if (!closeOnEscape) return;
1756
+ const handleKeyDown = (e) => {
1757
+ if (e.key === "Escape") {
1758
+ const recentId = _getMostRecentActiveId();
1759
+ if (recentId != null) {
1760
+ goeyToast.dismiss(recentId);
1761
+ }
1762
+ }
1763
+ };
1764
+ document.addEventListener("keydown", handleKeyDown);
1765
+ return () => document.removeEventListener("keydown", handleKeyDown);
1766
+ }, [closeOnEscape]);
1767
+ react.useEffect(() => {
1768
+ setGoeyVisibleToasts(visibleToasts ?? 3);
1769
+ }, [visibleToasts]);
1770
+ react.useEffect(() => {
1771
+ setGoeyMaxQueue(maxQueue);
1772
+ }, [maxQueue]);
1773
+ react.useEffect(() => {
1774
+ setGoeyQueueOverflow(queueOverflow);
1775
+ }, [queueOverflow]);
1776
+ react.useEffect(() => {
1777
+ setGoeyShowProgress(showProgress);
1778
+ }, [showProgress]);
1779
+ react.useEffect(() => {
1780
+ let expandObs = null;
1781
+ let currentOl = null;
1782
+ const syncFromExpanded = (ol) => {
1783
+ const anyExpanded = ol.querySelector('[data-sonner-toast][data-expanded="true"]') !== null;
1784
+ setContainerHovered(anyExpanded);
1785
+ };
1786
+ const attach = (ol) => {
1787
+ if (ol === currentOl) return;
1788
+ expandObs?.disconnect();
1789
+ currentOl = ol;
1790
+ expandObs = new MutationObserver(() => syncFromExpanded(ol));
1791
+ expandObs.observe(ol, { attributes: true, attributeFilter: ["data-expanded"], subtree: true });
1792
+ syncFromExpanded(ol);
1793
+ };
1794
+ const el = document.querySelector("[data-sonner-toaster]");
1795
+ if (el) attach(el);
1796
+ let bodyRafId = 0;
1797
+ const bodyObs = new MutationObserver(() => {
1798
+ if (bodyRafId) return;
1799
+ bodyRafId = requestAnimationFrame(() => {
1800
+ bodyRafId = 0;
1801
+ const found = document.querySelector("[data-sonner-toaster]");
1802
+ if (found) {
1803
+ attach(found);
1804
+ } else if (currentOl) {
1805
+ expandObs?.disconnect();
1806
+ currentOl = null;
1807
+ setContainerHovered(false);
1808
+ }
1809
+ });
1810
+ });
1811
+ bodyObs.observe(document.body, { childList: true, subtree: true });
1812
+ return () => {
1813
+ if (bodyRafId) cancelAnimationFrame(bodyRafId);
1814
+ bodyObs.disconnect();
1815
+ expandObs?.disconnect();
1816
+ setContainerHovered(false);
1817
+ };
1818
+ }, []);
1819
+ react.useEffect(() => {
1820
+ if (process.env.NODE_ENV !== "development") return;
1821
+ const el = document.createElement("div");
1822
+ el.setAttribute("data-goey-toast-css", "");
1823
+ el.style.position = "absolute";
1824
+ el.style.width = "0";
1825
+ el.style.height = "0";
1826
+ el.style.overflow = "hidden";
1827
+ el.style.pointerEvents = "none";
1828
+ document.body.appendChild(el);
1829
+ const value = getComputedStyle(el).getPropertyValue("--goey-toast");
1830
+ document.body.removeChild(el);
1831
+ if (!value) {
1832
+ console.warn(
1833
+ '[goey-toast] Styles not found. Make sure to import the CSS:\n\n import "goey-toast/styles.css";\n'
1834
+ );
1835
+ }
1836
+ }, []);
1837
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1838
+ /* @__PURE__ */ jsxRuntime.jsx(
1839
+ sonner.Toaster,
1840
+ {
1841
+ position,
1842
+ duration,
1843
+ gap,
1844
+ offset,
1845
+ theme,
1846
+ toastOptions: { unstyled: true, ...toastOptions },
1847
+ expand,
1848
+ closeButton,
1849
+ richColors,
1850
+ visibleToasts: 99,
1851
+ dir
1852
+ }
1853
+ ),
1854
+ /* @__PURE__ */ jsxRuntime.jsx(AriaLiveAnnouncer, {})
1855
+ ] });
1856
+ }
1210
1857
 
1211
1858
  exports.GoeyToaster = GoeyToaster;
1859
+ exports.animationPresets = animationPresets;
1212
1860
  exports.goeyToast = goeyToast;
1213
1861
  //# sourceMappingURL=index.cjs.map
1214
1862
  //# sourceMappingURL=index.cjs.map