premium-ds 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,16 +1,14 @@
1
- 'use client';import { UIToast } from './chunk-PUPZ4HME.js';
1
+ 'use client';import { DEFAULT_TOASTER_CONFIG, UIToast } from './chunk-YTMZEJ7M.js';
2
2
  import { Icon } from './chunk-KBWNUUWM.js';
3
3
  import { UIMotion } from './chunk-37O2ZXD6.js';
4
4
  import * as React from 'react';
5
5
  import { createPortal } from 'react-dom';
6
- import { createRoot } from 'react-dom/client';
7
6
  import { AnimatePresence, useMotionValue, motion, animate } from 'motion/react';
8
7
  import { jsx, jsxs } from 'react/jsx-runtime';
9
8
 
10
9
  var SM = UIMotion;
11
10
  var store = UIToast;
12
11
  var { useRef, useEffect, useLayoutEffect, useState, useSyncExternalStore } = React;
13
- var VISIBLE = 3;
14
12
  var COLLAPSE_GRACE = 140;
15
13
  var SWIPE_X = 64;
16
14
  var SWIPE_V = 480;
@@ -60,6 +58,7 @@ function ToastItem({
60
58
  hidden,
61
59
  behind,
62
60
  frameHeight,
61
+ isTop,
63
62
  onHeight
64
63
  }) {
65
64
  const ref = useRef(null);
@@ -86,10 +85,10 @@ function ToastItem({
86
85
  role: t.tone === "error" ? "alert" : "status",
87
86
  "aria-atomic": "true",
88
87
  style: { x },
89
- initial: { opacity: 0, y: 24, scale: 0.97 },
88
+ initial: { opacity: 0, y: isTop ? -24 : 24, scale: 0.97 },
90
89
  animate: {
91
90
  opacity: hidden ? 0 : 1,
92
- y: -offset,
91
+ y: isTop ? offset : -offset,
93
92
  scale: 1 - depth * 0.05,
94
93
  /* behind-cards adopt the front card's frame height (content faded) so a short card never drowns behind a tall one */
95
94
  height: frameHeight || "auto",
@@ -201,11 +200,22 @@ function ToastBody({ t }) {
201
200
  )
202
201
  ] });
203
202
  }
204
- function ToastHost() {
205
- const { toasts, paused, expanded } = useSyncExternalStore(store.subscribe, store.get);
203
+ function ToastHost({ config }) {
204
+ const {
205
+ toasts,
206
+ paused,
207
+ expanded: hoverExpanded
208
+ } = useSyncExternalStore(
209
+ store.subscribe,
210
+ store.get,
211
+ store.get
212
+ // server snapshot - the host renders nothing until mounted anyway
213
+ );
206
214
  const [heights, setHeights] = useState({});
215
+ const [ready, setReady] = useState(false);
207
216
  const collapseTimer = useRef(0);
208
217
  const hovering = useRef(false);
218
+ useEffect(() => setReady(true), []);
209
219
  const onHeight = (id, h) => setHeights((prev) => prev[id] === h ? prev : { ...prev, [id]: h });
210
220
  const onHold = () => {
211
221
  hovering.current = true;
@@ -240,15 +250,22 @@ function ToastHost() {
240
250
  store.resume();
241
251
  }
242
252
  }, [toasts.length]);
243
- const slice = toasts.slice(-6);
253
+ if (!ready) return null;
254
+ const isTop = config.position.startsWith("top");
255
+ const visible = config.visibleToasts;
256
+ const rendered = visible * 2;
257
+ const gap = config.gap || stackGap();
258
+ const expanded = config.expand || hoverExpanded;
259
+ const slice = toasts.slice(-rendered);
244
260
  const n = slice.length;
245
261
  const frontH = n ? heights[slice[n - 1].id] || 0 : 0;
246
- const gap = stackGap();
247
262
  return createPortal(
248
263
  /* @__PURE__ */ jsx(
249
264
  "ol",
250
265
  {
251
266
  className: "toast-viewport" + (paused ? " is-paused" : ""),
267
+ "data-position": config.position,
268
+ style: config.offset ? { "--toast-offset": `${config.offset}px` } : void 0,
252
269
  "aria-label": "Notifications",
253
270
  onPointerOver: (e) => {
254
271
  if (e.pointerType !== "touch") onHold();
@@ -274,9 +291,10 @@ function ToastHost() {
274
291
  t,
275
292
  depth: expanded ? 0 : depth,
276
293
  offset,
277
- hidden: !expanded && depth >= VISIBLE,
294
+ hidden: !expanded && depth >= visible,
278
295
  behind: !expanded && depth > 0,
279
296
  frameHeight: !expanded && depth > 0 && frontH ? frontH : null,
297
+ isTop,
280
298
  onHeight
281
299
  },
282
300
  t.id
@@ -287,24 +305,33 @@ function ToastHost() {
287
305
  document.body
288
306
  );
289
307
  }
290
- var hostMounted = false;
291
- function ensureToastHost() {
292
- if (hostMounted) return;
293
- hostMounted = true;
294
- const el = document.createElement("div");
295
- el.setAttribute("data-toast-host", "");
296
- document.body.appendChild(el);
297
- createRoot(el).render(/* @__PURE__ */ jsx(ToastHost, {}));
308
+ function stripUndefined(o) {
309
+ const out = {};
310
+ Object.keys(o).forEach((k) => {
311
+ if (o[k] !== void 0) out[k] = o[k];
312
+ });
313
+ return out;
298
314
  }
299
- store.host = ensureToastHost;
300
- if (store.toasts.length) ensureToastHost();
301
- function Toaster() {
315
+ function Toaster(props) {
316
+ const config = { ...DEFAULT_TOASTER_CONFIG, ...stripUndefined(props) };
302
317
  useEffect(() => {
303
- ensureToastHost();
304
- }, []);
305
- return null;
318
+ store.config = config;
319
+ store.mounted = true;
320
+ if (config.expand) store.setExpanded(true);
321
+ return () => {
322
+ store.mounted = false;
323
+ };
324
+ }, [
325
+ config.position,
326
+ config.duration,
327
+ config.visibleToasts,
328
+ config.gap,
329
+ config.offset,
330
+ config.expand
331
+ ]);
332
+ return /* @__PURE__ */ jsx(ToastHost, { config });
306
333
  }
307
334
 
308
335
  export { Toaster };
309
- //# sourceMappingURL=chunk-NKGMQL6I.js.map
310
- //# sourceMappingURL=chunk-NKGMQL6I.js.map
336
+ //# sourceMappingURL=chunk-NIZLUYLW.js.map
337
+ //# sourceMappingURL=chunk-NIZLUYLW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/toast/Toast.tsx"],"names":[],"mappings":"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","file":"chunk-NIZLUYLW.js","sourcesContent":["'use client';\n\n/* Toast - the React render layer (queue + clocks live in toast-store.ts). */\nimport * as React from 'react';\nimport { createPortal } from 'react-dom';\nimport { motion, AnimatePresence, animate, useMotionValue, type PanInfo } from 'motion/react';\nimport { UIMotion } from '../../tokens/motion-tokens';\nimport { Icon, type IconName } from '../icon/Icon';\nimport {\n UIToast,\n DEFAULT_TOASTER_CONFIG,\n type ToastRecord,\n type ToastTone,\n type ToasterConfig,\n} from './toast-store';\n\nconst SM = UIMotion;\nconst store = UIToast;\nconst { useRef, useEffect, useLayoutEffect, useState, useSyncExternalStore } = React;\n\nconst COLLAPSE_GRACE = 140;\nconst SWIPE_X = 64;\nconst SWIPE_V = 480;\n\n// expanded gap AND collapsed peek - read lazily (post-stylesheet), once\nlet GAP = 0;\nfunction stackGap() {\n if (GAP) return GAP;\n const cs = getComputedStyle(document.documentElement);\n const raw = cs.getPropertyValue('--space-3').trim();\n const n = parseFloat(raw) || 0;\n GAP = (raw.endsWith('rem') ? n * (parseFloat(cs.fontSize) || 16) : n) || 12;\n return GAP;\n}\n\nconst TONE_ICON: Record<string, IconName> = {\n success: 'check-circle',\n error: 'warning-circle',\n warning: 'warning',\n info: 'info',\n};\n\n/* useToneGesture - success glint, error headshake; the glint class is toggled with a reflow (remove - offsetWidth - add) to restart the one-shot. */\nfunction useToneGesture(\n tone: ToastTone,\n ref: React.RefObject<HTMLElement>,\n x: ReturnType<typeof useMotionValue<number>>,\n) {\n const prevTone = useRef<ToastTone | null>(null);\n useEffect(() => {\n const prev = prevTone.current;\n prevTone.current = tone;\n if (tone === prev) return;\n if (tone === 'success') {\n const el = ref.current;\n el.classList.remove('glass-glint');\n void el.offsetWidth;\n el.classList.add('glass-glint');\n const id = setTimeout(() => el.classList.remove('glass-glint'), SM.dur.slow * 1000 + 80);\n return () => clearTimeout(id);\n }\n if (tone === 'error') {\n const fall = animate(x, [0, -7, 5, -2, 0], {\n duration: SM.dur.slow,\n ease: SM.ease.standard,\n delay: SM.dur.base,\n });\n return () => fall.stop();\n }\n }, [tone]);\n}\n\nfunction ToastItem({\n t,\n depth,\n offset,\n hidden,\n behind,\n frameHeight,\n isTop,\n onHeight,\n}: {\n t: ToastRecord;\n depth: number;\n offset: number;\n hidden: boolean;\n behind: boolean;\n frameHeight: number | null;\n isTop: boolean;\n onHeight: (id: string, h: number) => void;\n}) {\n const ref = useRef<HTMLLIElement>(null);\n const x = useMotionValue(0); // swipe travel - ours, so dismissal can finish it\n useToneGesture(t.tone, ref, x);\n\n // report the CONTENT height (firstChild - the li's own height is animated, so it'd read mid-tween)\n useLayoutEffect(() => {\n if (ref.current && ref.current.firstElementChild)\n onHeight(t.id, (ref.current.firstElementChild as HTMLElement).offsetHeight);\n });\n\n const swipe = (_e: PointerEvent | MouseEvent | TouchEvent, info: PanInfo) => {\n if (info.offset.x > SWIPE_X || info.velocity.x > SWIPE_V) {\n // fling out the right edge, THEN remove - the exit fade plays where it landed, no snap-back\n animate(x, 420, { duration: SM.dur.fast, ease: SM.ease.exit }).then(() =>\n store.dismiss(t.id),\n );\n }\n };\n\n return (\n <motion.li\n ref={ref}\n className=\"toast glass\"\n data-tone={t.tone}\n data-behind={behind ? '' : undefined}\n role={t.tone === 'error' ? 'alert' : 'status'}\n aria-atomic=\"true\"\n style={{ x }}\n initial={{ opacity: 0, y: isTop ? -24 : 24, scale: 0.97 }}\n animate={{\n opacity: hidden ? 0 : 1,\n y: isTop ? offset : -offset,\n scale: 1 - depth * 0.05,\n /* behind-cards adopt the front card's frame height (content faded) so a short card never drowns behind a tall one */\n height: frameHeight || 'auto',\n pointerEvents: hidden ? 'none' : 'auto',\n }}\n exit={{ opacity: 0, scale: 0.96, transition: { duration: SM.dur.fast, ease: SM.ease.exit } }}\n transition={{ y: SM.t.settle, scale: SM.t.settle, height: SM.t.settle, opacity: SM.t.enter }}\n drag={t.dismissible ? 'x' : false}\n dragConstraints={{ left: 0, right: 0 }}\n dragElastic={{ left: 0.04, right: 0.9 }}\n dragMomentum={false}\n onDragEnd={swipe}\n onKeyDown={(e) => {\n if (e.key === 'Escape' && t.dismissible) store.dismiss(t.id);\n }}\n >\n {t.node ? (\n <div className=\"toast__custom\">{t.node as React.ReactNode}</div>\n ) : (\n <ToastBody t={t} />\n )}\n </motion.li>\n );\n}\n\nfunction ToastBody({ t }: { t: ToastRecord }) {\n return (\n <div className=\"toast__inner\">\n {(t.tone !== 'default' || isFinite(t.duration)) && (\n <span className=\"toast__icon\">\n {t.tone !== 'default' && (\n /* keyed remount (not nested AnimatePresence): old glyph cuts, new springs in */\n <motion.span\n key={t.tone}\n className=\"toast__icon-glyph\"\n initial={{ scale: 0.5, opacity: 0 }}\n animate={{ scale: 1, opacity: 1 }}\n transition={{ scale: SM.t.settle, opacity: SM.t.enter }}\n >\n {t.tone === 'loading' ? (\n <span className=\"toast__spinner\" aria-hidden=\"true\"></span>\n ) : (\n <Icon name={TONE_ICON[t.tone]} weight=\"fill\" />\n )}\n </motion.span>\n )}\n {isFinite(t.duration) && (\n /* the ring - keyed to the timer so a restart re-fills it; pathLength=1 makes dashoffset a fraction */\n <svg\n key={'ring-' + t.timerKey}\n className=\"toast__ring\"\n viewBox=\"0 0 36 36\"\n aria-hidden=\"true\"\n >\n <circle className=\"toast__ring-track\" cx=\"18\" cy=\"18\" r=\"16.5\"></circle>\n <circle\n className=\"toast__ring-arc\"\n cx=\"18\"\n cy=\"18\"\n r=\"16.5\"\n pathLength={1}\n style={{ animationDuration: t.duration + 'ms' }}\n ></circle>\n </svg>\n )}\n </span>\n )}\n <motion.div\n key={String(t.message) + '-' + String(t.description)}\n className=\"toast__text\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={SM.t.enter}\n >\n <p className=\"toast__message\">{t.message}</p>\n {t.description != null && <p className=\"toast__desc\">{t.description}</p>}\n </motion.div>\n {t.count > 1 && (\n <motion.span\n key={t.count}\n className=\"toast__count\"\n initial={{ scale: 0.6 }}\n animate={{ scale: 1 }}\n transition={SM.t.settle}\n >\n ×{t.count}\n </motion.span>\n )}\n {t.action && (\n <button\n type=\"button\"\n className=\"btn btn--secondary btn--sm toast__action\"\n onClick={() => {\n if (t.action.onClick) t.action.onClick();\n store.dismiss(t.id);\n }}\n >\n {t.action.label}\n </button>\n )}\n {t.dismissible && (\n <button\n type=\"button\"\n className=\"toast__close\"\n aria-label=\"Dismiss\"\n onClick={() => store.dismiss(t.id)}\n >\n <Icon name=\"close\" size=\"sm\" />\n </button>\n )}\n </div>\n );\n}\n\nfunction ToastHost({ config }: { config: ToasterConfig }) {\n const {\n toasts,\n paused,\n expanded: hoverExpanded,\n } = useSyncExternalStore(\n store.subscribe,\n store.get,\n store.get, // server snapshot - the host renders nothing until mounted anyway\n );\n const [heights, setHeights] = useState<Record<string, number>>({});\n const [ready, setReady] = useState(false);\n const collapseTimer = useRef<ReturnType<typeof setTimeout> | 0>(0);\n const hovering = useRef(false);\n\n // Portals into document.body, so render nothing until mounted (SSR-safe, no hydration mismatch).\n useEffect(() => setReady(true), []);\n\n const onHeight = (id: string, h: number) =>\n setHeights((prev) => (prev[id] === h ? prev : { ...prev, [id]: h }));\n\n // hold = fan open + freeze clocks; release = graced fold + resume\n const onHold = () => {\n hovering.current = true;\n clearTimeout(collapseTimer.current);\n store.setExpanded(true);\n store.pause();\n };\n const onRelease = (graced: boolean) => {\n hovering.current = false;\n clearTimeout(collapseTimer.current);\n collapseTimer.current = setTimeout(\n () => {\n store.setExpanded(false);\n store.resume();\n },\n graced ? COLLAPSE_GRACE : 0,\n );\n };\n\n // tab hidden - freeze clocks, visible - resume (visibility, not window focus: an embedded preview blurs on outside clicks).\n useEffect(() => {\n const onVis = () => {\n if (document.hidden) store.pause();\n else if (!hovering.current) store.resume();\n };\n document.addEventListener('visibilitychange', onVis);\n return () => document.removeEventListener('visibilitychange', onVis);\n }, []);\n\n // safety net: last card removed under the cursor fires no pointerout, so release here\n useEffect(() => {\n if (toasts.length === 0) {\n hovering.current = false;\n clearTimeout(collapseTimer.current);\n store.setExpanded(false);\n store.resume();\n }\n }, [toasts.length]);\n\n if (!ready) return null;\n\n const isTop = config.position.startsWith('top');\n const visible = config.visibleToasts;\n const rendered = visible * 2;\n const gap = config.gap || stackGap();\n const expanded = config.expand || hoverExpanded;\n\n const slice = toasts.slice(-rendered);\n const n = slice.length;\n const frontH = n ? heights[slice[n - 1].id] || 0 : 0;\n\n return createPortal(\n <ol\n className={'toast-viewport' + (paused ? ' is-paused' : '')}\n data-position={config.position}\n style={\n config.offset\n ? ({ '--toast-offset': `${config.offset}px` } as React.CSSProperties)\n : undefined\n }\n aria-label=\"Notifications\"\n onPointerOver={(e) => {\n if (e.pointerType !== 'touch') onHold();\n }}\n onPointerOut={(e) => {\n if (!e.relatedTarget || !e.currentTarget.contains(e.relatedTarget as Node)) onRelease(true);\n }}\n onFocus={onHold}\n onBlur={(e) => {\n if (!e.relatedTarget || !e.currentTarget.contains(e.relatedTarget as Node))\n onRelease(false);\n }}\n >\n <AnimatePresence>\n {slice.map((t, i) => {\n const depth = n - 1 - i; // newest = 0, at the front\n let offset = depth * gap; // collapsed: peek per depth\n if (expanded) {\n // expanded: real heights\n offset = 0;\n for (let j = i + 1; j < n; j++) offset += (heights[slice[j].id] || 0) + gap;\n }\n return (\n <ToastItem\n key={t.id}\n t={t}\n depth={expanded ? 0 : depth}\n offset={offset}\n hidden={!expanded && depth >= visible}\n behind={!expanded && depth > 0}\n frameHeight={!expanded && depth > 0 && frontH ? frontH : null}\n isTop={isTop}\n onHeight={onHeight}\n />\n );\n })}\n </AnimatePresence>\n </ol>,\n document.body,\n );\n}\n\nfunction stripUndefined<T extends object>(o: T): Partial<T> {\n const out: Partial<T> = {};\n (Object.keys(o) as (keyof T)[]).forEach((k) => {\n if (o[k] !== undefined) out[k] = o[k];\n });\n return out;\n}\n\nexport interface ToasterProps extends Partial<ToasterConfig> {}\n\n/* Toaster - mount once near the app root. It owns the toast viewport and registers the queue;\n without it, toast() renders nothing (and warns once in the browser). */\nexport function Toaster(props: ToasterProps): React.ReactElement {\n const config: ToasterConfig = { ...DEFAULT_TOASTER_CONFIG, ...stripUndefined(props) };\n\n useEffect(() => {\n store.config = config;\n store.mounted = true;\n if (config.expand) store.setExpanded(true);\n return () => {\n store.mounted = false;\n };\n }, [\n config.position,\n config.duration,\n config.visibleToasts,\n config.gap,\n config.offset,\n config.expand,\n ]);\n\n return <ToastHost config={config} />;\n}\n"]}
@@ -1,4 +1,12 @@
1
1
  // src/components/toast/toast-store.ts
2
+ var DEFAULT_TOASTER_CONFIG = {
3
+ position: "bottom-right",
4
+ duration: 0,
5
+ visibleToasts: 3,
6
+ gap: 0,
7
+ offset: 0,
8
+ expand: false
9
+ };
2
10
  var DURATION = {
3
11
  default: 5e3,
4
12
  success: 5e3,
@@ -14,7 +22,8 @@ var store = {
14
22
  toasts: [],
15
23
  paused: false,
16
24
  expanded: false,
17
- host: null,
25
+ mounted: false,
26
+ config: { ...DEFAULT_TOASTER_CONFIG },
18
27
  snap: { toasts: [], paused: false, expanded: false },
19
28
  listeners: /* @__PURE__ */ new Set(),
20
29
  subscribe(l) {
@@ -92,10 +101,30 @@ var store = {
92
101
  store.emit();
93
102
  }
94
103
  };
104
+ var warned = false;
105
+ var warnScheduled = false;
106
+ function warnIfDetached() {
107
+ if (store.mounted || warned || warnScheduled || typeof window === "undefined") return;
108
+ warnScheduled = true;
109
+ setTimeout(() => {
110
+ warnScheduled = false;
111
+ if (store.mounted || warned) return;
112
+ warned = true;
113
+ console.warn(
114
+ "premium-ds: a toast was triggered but no <Toaster /> is mounted, so nothing will render. Mount <Toaster /> once near your app root."
115
+ );
116
+ });
117
+ }
118
+ function resolveDuration(tone, opt) {
119
+ if (opt != null) return opt;
120
+ const toneDefault = DURATION[tone];
121
+ if (toneDefault === Infinity || store.config.duration <= 0) return toneDefault;
122
+ return store.config.duration;
123
+ }
95
124
  function make(tone, message, opts = {}) {
96
- if (store.host) store.host();
125
+ warnIfDetached();
97
126
  const id = opts.id != null ? String(opts.id) : "toast-" + ++uid;
98
- const duration = opts.duration != null ? opts.duration : DURATION[tone];
127
+ const duration = resolveDuration(tone, opts.duration);
99
128
  if (store.toasts.some((t) => t.id === id)) {
100
129
  return store.update(id, {
101
130
  tone,
@@ -140,7 +169,7 @@ toast.promise = function(promise, msgs = {}) {
140
169
  return promise;
141
170
  };
142
171
  toast.custom = (node, opts = {}) => {
143
- if (store.host) store.host();
172
+ warnIfDetached();
144
173
  const id = opts.id != null ? String(opts.id) : "toast-" + ++uid;
145
174
  const duration = opts.duration != null ? opts.duration : Infinity;
146
175
  if (store.toasts.some((t) => t.id === id)) return store.update(id, { node });
@@ -160,6 +189,6 @@ toast.custom = (node, opts = {}) => {
160
189
  };
161
190
  var UIToast = store;
162
191
 
163
- export { UIToast, toast };
164
- //# sourceMappingURL=chunk-PUPZ4HME.js.map
165
- //# sourceMappingURL=chunk-PUPZ4HME.js.map
192
+ export { DEFAULT_TOASTER_CONFIG, UIToast, toast };
193
+ //# sourceMappingURL=chunk-YTMZEJ7M.js.map
194
+ //# sourceMappingURL=chunk-YTMZEJ7M.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/toast/toast-store.ts"],"names":[],"mappings":"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","file":"chunk-YTMZEJ7M.js","sourcesContent":["// toast-store - the Toast machinery: queue, clocks, coalescing, imperative API (zero React).\n\nexport type ToastTone = 'default' | 'success' | 'error' | 'warning' | 'info' | 'loading' | 'custom';\n\nexport interface ToastAction {\n label: string;\n onClick: () => void;\n}\n\nexport interface ToastOptions {\n id?: string | number;\n duration?: number;\n description?: string | null;\n action?: ToastAction | null;\n}\n\nexport interface ToastRecord {\n id: string;\n tone: ToastTone;\n message: string | null;\n description: string | null;\n action: ToastAction | null;\n duration: number;\n remaining: number;\n count: number;\n timerKey: number;\n node: unknown; // a React node, kept opaque so this module stays React-free\n dismissible: boolean;\n expiresAt?: number;\n}\n\nexport interface ToastSnapshot {\n toasts: ToastRecord[];\n paused: boolean;\n expanded: boolean;\n}\n\nexport type ToastPosition =\n 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';\n\n/* Root-level options - set once on <Toaster /> and read by the queue + host. */\nexport interface ToasterConfig {\n position: ToastPosition;\n duration: number; // default auto-dismiss ms for timed tones; 0 keeps the per-tone defaults\n visibleToasts: number; // cards shown before the rest recede behind\n gap: number; // px between cards; 0 reads --space-3 from the stylesheet\n offset: number; // px inset from the viewport edge; 0 reads --space-6\n expand: boolean; // start the stack fanned open instead of collapsed\n}\n\nexport const DEFAULT_TOASTER_CONFIG: ToasterConfig = {\n position: 'bottom-right',\n duration: 0,\n visibleToasts: 3,\n gap: 0,\n offset: 0,\n expand: false,\n};\n\nconst DURATION: Record<string, number> = {\n default: 5000,\n success: 5000,\n info: 5000,\n warning: 8000,\n error: 8000,\n loading: Infinity, // sticky until updated/dismissed\n};\n\nlet uid = 0;\nconst timers = new Map<string, ReturnType<typeof setTimeout>>();\n\ninterface ToastStore {\n toasts: ToastRecord[];\n paused: boolean;\n expanded: boolean;\n mounted: boolean; // a <Toaster /> is live; toasts no-op until it is\n config: ToasterConfig;\n snap: ToastSnapshot;\n listeners: Set<() => void>;\n subscribe(l: () => void): () => void;\n get(): ToastSnapshot;\n emit(): void;\n add(t: ToastRecord): string;\n update(id: string, patch: Partial<ToastRecord>): string;\n dismiss(id?: string | null): void;\n schedule(t: ToastRecord): void;\n pause(): void;\n resume(): void;\n setExpanded(v: boolean): void;\n}\n\n/* Store - the queue; the API writes, the host renders. */\nconst store: ToastStore = {\n toasts: [],\n paused: false,\n expanded: false,\n mounted: false,\n config: { ...DEFAULT_TOASTER_CONFIG },\n snap: { toasts: [], paused: false, expanded: false },\n listeners: new Set(),\n subscribe(l) {\n store.listeners.add(l);\n return () => store.listeners.delete(l);\n },\n get: () => store.snap,\n emit() {\n store.snap = { toasts: store.toasts.slice(), paused: store.paused, expanded: store.expanded };\n store.listeners.forEach((l) => l());\n },\n\n add(t) {\n // coalesce: an identical visible toast becomes a xN counter instead of a duplicate\n const twin = store.toasts.find(\n (x) =>\n !x.node &&\n !t.node &&\n x.tone === t.tone &&\n x.message === t.message &&\n x.description === t.description,\n );\n if (twin) return store.update(twin.id, { count: twin.count + 1 });\n store.toasts.push(t);\n store.schedule(t);\n store.emit();\n return t.id;\n },\n\n update(id, patch) {\n const t = store.toasts.find((x) => x.id === id);\n if (!t) return id;\n Object.assign(t, patch);\n if ('tone' in patch || 'duration' in patch || 'count' in patch) {\n t.remaining = t.duration;\n t.timerKey++; // re-key the ring so it re-fills\n store.schedule(t);\n }\n store.emit();\n return id;\n },\n\n dismiss(id) {\n const gone = id == null ? store.toasts : store.toasts.filter((t) => t.id === id);\n gone.forEach((t) => {\n clearTimeout(timers.get(t.id));\n timers.delete(t.id);\n });\n store.toasts = id == null ? [] : store.toasts.filter((t) => t.id !== id);\n store.emit();\n },\n\n schedule(t) {\n clearTimeout(timers.get(t.id));\n timers.delete(t.id);\n if (!isFinite(t.duration)) return;\n if (store.paused) return; // resume() picks it up with t.remaining\n t.expiresAt = performance.now() + t.remaining;\n timers.set(\n t.id,\n setTimeout(() => store.dismiss(t.id), t.remaining),\n );\n },\n\n pause() {\n // freeze every clock, remember what's left\n if (store.paused) return;\n store.paused = true;\n store.toasts.forEach((t) => {\n if (timers.has(t.id)) {\n clearTimeout(timers.get(t.id));\n timers.delete(t.id);\n t.remaining = Math.max(0, (t.expiresAt ?? performance.now()) - performance.now());\n }\n });\n store.emit();\n },\n\n resume() {\n if (!store.paused) return;\n store.paused = false;\n store.toasts.forEach((t) => store.schedule(t));\n store.emit();\n },\n\n setExpanded(v) {\n if (v === store.expanded) return;\n store.expanded = v;\n store.emit();\n },\n};\n\nlet warned = false;\nlet warnScheduled = false;\n\n/* A toast with no <Toaster /> mounted renders nothing. Warn once, deferred a tick so a\n toast fired during the same render that mounts <Toaster /> is not a false positive. */\nfunction warnIfDetached(): void {\n if (store.mounted || warned || warnScheduled || typeof window === 'undefined') return;\n warnScheduled = true;\n setTimeout(() => {\n warnScheduled = false;\n if (store.mounted || warned) return;\n warned = true;\n console.warn(\n 'premium-ds: a toast was triggered but no <Toaster /> is mounted, so nothing will render. Mount <Toaster /> once near your app root.',\n );\n });\n}\n\n/* Per-tone default unless the root sets a global duration; loading stays sticky (Infinity). */\nfunction resolveDuration(tone: ToastTone, opt?: number): number {\n if (opt != null) return opt;\n const toneDefault = DURATION[tone];\n if (toneDefault === Infinity || store.config.duration <= 0) return toneDefault;\n return store.config.duration;\n}\n\nfunction make(tone: ToastTone, message: string | null, opts: ToastOptions = {}): string {\n warnIfDetached();\n const id = opts.id != null ? String(opts.id) : 'toast-' + ++uid;\n const duration = resolveDuration(tone, opts.duration);\n if (store.toasts.some((t) => t.id === id)) {\n return store.update(id, {\n tone,\n message,\n duration,\n description: opts.description != null ? opts.description : null,\n action: opts.action != null ? opts.action : null,\n });\n }\n return store.add({\n id,\n tone,\n message,\n description: opts.description != null ? opts.description : null,\n action: opts.action != null ? opts.action : null,\n duration,\n remaining: duration,\n count: 1,\n timerKey: 0,\n node: null,\n dismissible: true,\n });\n}\n\ntype PromiseMsg<V> = string | ((v: V) => string);\nexport interface ToastPromiseMsgs<T> {\n loading?: string;\n success?: PromiseMsg<T>;\n error?: PromiseMsg<unknown>;\n}\n\nexport interface ToastApi {\n (message: string, opts?: ToastOptions): string;\n success(m: string, o?: ToastOptions): string;\n error(m: string, o?: ToastOptions): string;\n warning(m: string, o?: ToastOptions): string;\n info(m: string, o?: ToastOptions): string;\n loading(m: string, o?: ToastOptions): string;\n dismiss(id?: string | null): void;\n update(id: string, patch?: Partial<ToastRecord>): string;\n promise<T>(promise: Promise<T>, msgs?: ToastPromiseMsgs<T>): Promise<T>;\n custom(\n node: unknown,\n opts?: { id?: string | number; duration?: number; dismissible?: boolean },\n ): string;\n}\n\nconst toast = ((message: string, opts?: ToastOptions) =>\n make('default', message, opts)) as ToastApi;\ntoast.success = (m, o) => make('success', m, o);\ntoast.error = (m, o) => make('error', m, o);\ntoast.warning = (m, o) => make('warning', m, o);\ntoast.info = (m, o) => make('info', m, o);\ntoast.loading = (m, o) => make('loading', m, o);\ntoast.dismiss = (id) => store.dismiss(id);\n\ntoast.update = (id, patch = {}) => {\n // a tone change without an explicit duration adopts that tone's clock\n if (patch.tone && patch.duration == null) patch = { ...patch, duration: DURATION[patch.tone] };\n return store.update(id, patch);\n};\n\ntoast.promise = function <T>(promise: Promise<T>, msgs: ToastPromiseMsgs<T> = {}): Promise<T> {\n const id = make('loading', msgs.loading != null ? msgs.loading : 'Working...');\n const text = <V>(m: PromiseMsg<V> | undefined, fallback: string, v: V): string =>\n typeof m === 'function' ? (m as (x: V) => string)(v) : m != null ? m : fallback;\n promise.then(\n (v) => toast.update(id, { tone: 'success', message: text(msgs.success, 'Done', v) }),\n (e) =>\n toast.update(id, { tone: 'error', message: text(msgs.error, 'Something went wrong', e) }),\n );\n return promise;\n};\n\ntoast.custom = (node, opts = {}) => {\n warnIfDetached();\n const id = opts.id != null ? String(opts.id) : 'toast-' + ++uid;\n const duration = opts.duration != null ? opts.duration : Infinity;\n if (store.toasts.some((t) => t.id === id)) return store.update(id, { node });\n return store.add({\n id,\n tone: 'custom',\n node,\n message: null,\n description: null,\n action: null,\n duration,\n remaining: duration,\n count: 1,\n timerKey: 0,\n dismissible: opts.dismissible === true,\n });\n};\n\nexport { toast };\nexport const UIToast = store;\n"]}
package/dist/index.d.ts CHANGED
@@ -28,8 +28,8 @@ export { Overlay, OverlayProps } from './overlay.js';
28
28
  export { Dialog, DialogProps } from './dialog.js';
29
29
  export { Tooltip, TooltipHost, TooltipProps } from './tooltip.js';
30
30
  export { Alert, AlertAction, AlertProps, AlertTone } from './alert.js';
31
- export { Toaster } from './toast.js';
32
- export { ToastAction, ToastApi, ToastOptions, ToastPromiseMsgs, ToastRecord, ToastSnapshot, ToastTone, UIToast, toast } from './toast-store.js';
31
+ export { Toaster, ToasterProps } from './toast.js';
32
+ export { DEFAULT_TOASTER_CONFIG, ToastAction, ToastApi, ToastOptions, ToastPosition, ToastPromiseMsgs, ToastRecord, ToastSnapshot, ToastTone, ToasterConfig, UIToast, toast } from './toast-store.js';
33
33
  export { Bezier, MotionTokens, UIMotion } from './motion-tokens.js';
34
34
  import 'react';
35
35
  import 'motion/react';
package/dist/index.js CHANGED
@@ -3,8 +3,8 @@ export { TabPanel, Tabs } from './chunk-EZ2CWTBE.js';
3
3
  export { Dialog } from './chunk-FPP2XLKX.js';
4
4
  export { Tooltip, TooltipHost } from './chunk-BXXS7YRC.js';
5
5
  export { Alert } from './chunk-H6KWJNOE.js';
6
- export { Toaster } from './chunk-NKGMQL6I.js';
7
- export { UIToast, toast } from './chunk-PUPZ4HME.js';
6
+ export { Toaster } from './chunk-NIZLUYLW.js';
7
+ export { DEFAULT_TOASTER_CONFIG, UIToast, toast } from './chunk-YTMZEJ7M.js';
8
8
  export { Checkbox } from './chunk-XA3T5KWA.js';
9
9
  export { Toggle } from './chunk-5PQMQBQC.js';
10
10
  export { RadioGroup } from './chunk-KN7JFAZ6.js';
@@ -28,11 +28,22 @@ interface ToastSnapshot {
28
28
  paused: boolean;
29
29
  expanded: boolean;
30
30
  }
31
+ type ToastPosition = 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';
32
+ interface ToasterConfig {
33
+ position: ToastPosition;
34
+ duration: number;
35
+ visibleToasts: number;
36
+ gap: number;
37
+ offset: number;
38
+ expand: boolean;
39
+ }
40
+ declare const DEFAULT_TOASTER_CONFIG: ToasterConfig;
31
41
  interface ToastStore {
32
42
  toasts: ToastRecord[];
33
43
  paused: boolean;
34
44
  expanded: boolean;
35
- host: null | (() => void);
45
+ mounted: boolean;
46
+ config: ToasterConfig;
36
47
  snap: ToastSnapshot;
37
48
  listeners: Set<() => void>;
38
49
  subscribe(l: () => void): () => void;
@@ -72,4 +83,4 @@ declare const toast: ToastApi;
72
83
 
73
84
  declare const UIToast: ToastStore;
74
85
 
75
- export { type ToastAction, type ToastApi, type ToastOptions, type ToastPromiseMsgs, type ToastRecord, type ToastSnapshot, type ToastTone, UIToast, toast };
86
+ export { DEFAULT_TOASTER_CONFIG, type ToastAction, type ToastApi, type ToastOptions, type ToastPosition, type ToastPromiseMsgs, type ToastRecord, type ToastSnapshot, type ToastTone, type ToasterConfig, UIToast, toast };
@@ -1,3 +1,3 @@
1
- export { UIToast, toast } from './chunk-PUPZ4HME.js';
1
+ export { DEFAULT_TOASTER_CONFIG, UIToast, toast } from './chunk-YTMZEJ7M.js';
2
2
  //# sourceMappingURL=toast-store.js.map
3
3
  //# sourceMappingURL=toast-store.js.map
package/dist/toast.d.ts CHANGED
@@ -1,3 +1,8 @@
1
- declare function Toaster(): null;
1
+ import * as React from 'react';
2
+ import { ToasterConfig } from './toast-store.js';
2
3
 
3
- export { Toaster };
4
+ interface ToasterProps extends Partial<ToasterConfig> {
5
+ }
6
+ declare function Toaster(props: ToasterProps): React.ReactElement;
7
+
8
+ export { Toaster, type ToasterProps };
package/dist/toast.js CHANGED
@@ -1,5 +1,5 @@
1
- 'use client';export { Toaster } from './chunk-NKGMQL6I.js';
2
- import './chunk-PUPZ4HME.js';
1
+ 'use client';export { Toaster } from './chunk-NIZLUYLW.js';
2
+ import './chunk-YTMZEJ7M.js';
3
3
  import './chunk-KBWNUUWM.js';
4
4
  import './chunk-37O2ZXD6.js';
5
5
  //# sourceMappingURL=toast.js.map
package/llms.txt CHANGED
@@ -139,9 +139,11 @@ Alert - premium-ds/alert
139
139
  Persistent, in-flow status. tone, title, children (body), action { label, onClick }, dismissible, banner, open/onDismiss (controlled).
140
140
  <Alert tone="warning" title="Your trial ends in 5 days" action={{ label: 'Upgrade', onClick: fn }}>Add a plan...</Alert>
141
141
 
142
- Toast - premium-ds/toast (imperative; the host self-mounts - no <Toaster> needed)
142
+ Toast - premium-ds/toast (imperative; mount <Toaster /> once at the app root, then call toast() from anywhere)
143
+ <Toaster /> props: position ('bottom-right' default), duration, visibleToasts, gap, offset, expand. Without it, toast() no-ops.
143
144
  toast(msg) - toast.success/error/warning/info(msg, { description, action }) - toast.promise(p, { loading, success, error }) - toast.loading - toast.dismiss().
144
- import { toast } from 'premium-ds';
145
+ import { Toaster, toast } from 'premium-ds';
146
+ // once at the root: <Toaster position="bottom-right" />
145
147
  toast.success('Changes saved', { description: 'Synced to the cloud' });
146
148
  toast.promise(save(), { loading: 'Saving...', success: (v) => `Saved - ${v.count}`, error: (e) => `Failed - ${e.message}` });
147
149
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "premium-ds",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "A React 19 design system: accessible, animated UI components built on a small, closed CSS design-token vocabulary. No Tailwind, no CSS-in-JS.",
5
5
  "keywords": [
6
6
  "react",
@@ -85,7 +85,7 @@
85
85
  --btn-shadow: inset 0 1px 0 0 rgb(255 255 255 / 0.16), var(--shadow-sm);
86
86
  }
87
87
  .btn--primary:hover:not(:disabled):not(.is-loading) {
88
- background: linear-gradient(180deg, var(--teal-400), var(--teal-500));
88
+ background: linear-gradient(180deg, var(--accent-lift), var(--accent));
89
89
  --btn-shadow: inset 0 1px 0 0 rgb(255 255 255 / 0.22), var(--shadow-md);
90
90
  }
91
91
  .btn--primary:active:not(:disabled):not(.is-loading) {
@@ -3,18 +3,21 @@
3
3
  /* Toast - the React render layer (queue + clocks live in toast-store.ts). */
4
4
  import * as React from 'react';
5
5
  import { createPortal } from 'react-dom';
6
- import { createRoot } from 'react-dom/client';
7
6
  import { motion, AnimatePresence, animate, useMotionValue, type PanInfo } from 'motion/react';
8
7
  import { UIMotion } from '../../tokens/motion-tokens';
9
8
  import { Icon, type IconName } from '../icon/Icon';
10
- import { UIToast, type ToastRecord, type ToastTone } from './toast-store';
9
+ import {
10
+ UIToast,
11
+ DEFAULT_TOASTER_CONFIG,
12
+ type ToastRecord,
13
+ type ToastTone,
14
+ type ToasterConfig,
15
+ } from './toast-store';
11
16
 
12
17
  const SM = UIMotion;
13
18
  const store = UIToast;
14
19
  const { useRef, useEffect, useLayoutEffect, useState, useSyncExternalStore } = React;
15
20
 
16
- const VISIBLE = 3;
17
- const RENDERED = 6;
18
21
  const COLLAPSE_GRACE = 140;
19
22
  const SWIPE_X = 64;
20
23
  const SWIPE_V = 480;
@@ -74,6 +77,7 @@ function ToastItem({
74
77
  hidden,
75
78
  behind,
76
79
  frameHeight,
80
+ isTop,
77
81
  onHeight,
78
82
  }: {
79
83
  t: ToastRecord;
@@ -82,6 +86,7 @@ function ToastItem({
82
86
  hidden: boolean;
83
87
  behind: boolean;
84
88
  frameHeight: number | null;
89
+ isTop: boolean;
85
90
  onHeight: (id: string, h: number) => void;
86
91
  }) {
87
92
  const ref = useRef<HTMLLIElement>(null);
@@ -112,10 +117,10 @@ function ToastItem({
112
117
  role={t.tone === 'error' ? 'alert' : 'status'}
113
118
  aria-atomic="true"
114
119
  style={{ x }}
115
- initial={{ opacity: 0, y: 24, scale: 0.97 }}
120
+ initial={{ opacity: 0, y: isTop ? -24 : 24, scale: 0.97 }}
116
121
  animate={{
117
122
  opacity: hidden ? 0 : 1,
118
- y: -offset,
123
+ y: isTop ? offset : -offset,
119
124
  scale: 1 - depth * 0.05,
120
125
  /* behind-cards adopt the front card's frame height (content faded) so a short card never drowns behind a tall one */
121
126
  height: frameHeight || 'auto',
@@ -230,12 +235,24 @@ function ToastBody({ t }: { t: ToastRecord }) {
230
235
  );
231
236
  }
232
237
 
233
- function ToastHost() {
234
- const { toasts, paused, expanded } = useSyncExternalStore(store.subscribe, store.get);
238
+ function ToastHost({ config }: { config: ToasterConfig }) {
239
+ const {
240
+ toasts,
241
+ paused,
242
+ expanded: hoverExpanded,
243
+ } = useSyncExternalStore(
244
+ store.subscribe,
245
+ store.get,
246
+ store.get, // server snapshot - the host renders nothing until mounted anyway
247
+ );
235
248
  const [heights, setHeights] = useState<Record<string, number>>({});
249
+ const [ready, setReady] = useState(false);
236
250
  const collapseTimer = useRef<ReturnType<typeof setTimeout> | 0>(0);
237
251
  const hovering = useRef(false);
238
252
 
253
+ // Portals into document.body, so render nothing until mounted (SSR-safe, no hydration mismatch).
254
+ useEffect(() => setReady(true), []);
255
+
239
256
  const onHeight = (id: string, h: number) =>
240
257
  setHeights((prev) => (prev[id] === h ? prev : { ...prev, [id]: h }));
241
258
 
@@ -278,14 +295,27 @@ function ToastHost() {
278
295
  }
279
296
  }, [toasts.length]);
280
297
 
281
- const slice = toasts.slice(-RENDERED);
298
+ if (!ready) return null;
299
+
300
+ const isTop = config.position.startsWith('top');
301
+ const visible = config.visibleToasts;
302
+ const rendered = visible * 2;
303
+ const gap = config.gap || stackGap();
304
+ const expanded = config.expand || hoverExpanded;
305
+
306
+ const slice = toasts.slice(-rendered);
282
307
  const n = slice.length;
283
308
  const frontH = n ? heights[slice[n - 1].id] || 0 : 0;
284
- const gap = stackGap();
285
309
 
286
310
  return createPortal(
287
311
  <ol
288
312
  className={'toast-viewport' + (paused ? ' is-paused' : '')}
313
+ data-position={config.position}
314
+ style={
315
+ config.offset
316
+ ? ({ '--toast-offset': `${config.offset}px` } as React.CSSProperties)
317
+ : undefined
318
+ }
289
319
  aria-label="Notifications"
290
320
  onPointerOver={(e) => {
291
321
  if (e.pointerType !== 'touch') onHold();
@@ -299,7 +329,6 @@ function ToastHost() {
299
329
  onRelease(false);
300
330
  }}
301
331
  >
302
- {/* initial stays true - the host mounts with the first toast, so initial={false} would skip its entrance */}
303
332
  <AnimatePresence>
304
333
  {slice.map((t, i) => {
305
334
  const depth = n - 1 - i; // newest = 0, at the front
@@ -315,9 +344,10 @@ function ToastHost() {
315
344
  t={t}
316
345
  depth={expanded ? 0 : depth}
317
346
  offset={offset}
318
- hidden={!expanded && depth >= VISIBLE}
347
+ hidden={!expanded && depth >= visible}
319
348
  behind={!expanded && depth > 0}
320
349
  frameHeight={!expanded && depth > 0 && frontH ? frontH : null}
350
+ isTop={isTop}
321
351
  onHeight={onHeight}
322
352
  />
323
353
  );
@@ -328,22 +358,36 @@ function ToastHost() {
328
358
  );
329
359
  }
330
360
 
331
- let hostMounted = false;
332
- function ensureToastHost() {
333
- if (hostMounted) return;
334
- hostMounted = true;
335
- const el = document.createElement('div');
336
- el.setAttribute('data-toast-host', '');
337
- document.body.appendChild(el);
338
- createRoot(el).render(<ToastHost />);
361
+ function stripUndefined<T extends object>(o: T): Partial<T> {
362
+ const out: Partial<T> = {};
363
+ (Object.keys(o) as (keyof T)[]).forEach((k) => {
364
+ if (o[k] !== undefined) out[k] = o[k];
365
+ });
366
+ return out;
339
367
  }
340
- store.host = ensureToastHost;
341
- if (store.toasts.length) ensureToastHost(); // toasts fired before we loaded
342
368
 
343
- /* Toaster - mount once near the app root so this module loads client-side and the lazy host is registered (toast() also auto-mounts). */
344
- export function Toaster(): null {
369
+ export interface ToasterProps extends Partial<ToasterConfig> {}
370
+
371
+ /* Toaster - mount once near the app root. It owns the toast viewport and registers the queue;
372
+ without it, toast() renders nothing (and warns once in the browser). */
373
+ export function Toaster(props: ToasterProps): React.ReactElement {
374
+ const config: ToasterConfig = { ...DEFAULT_TOASTER_CONFIG, ...stripUndefined(props) };
375
+
345
376
  useEffect(() => {
346
- ensureToastHost();
347
- }, []);
348
- return null;
377
+ store.config = config;
378
+ store.mounted = true;
379
+ if (config.expand) store.setExpanded(true);
380
+ return () => {
381
+ store.mounted = false;
382
+ };
383
+ }, [
384
+ config.position,
385
+ config.duration,
386
+ config.visibleToasts,
387
+ config.gap,
388
+ config.offset,
389
+ config.expand,
390
+ ]);
391
+
392
+ return <ToastHost config={config} />;
349
393
  }
@@ -35,6 +35,28 @@ export interface ToastSnapshot {
35
35
  expanded: boolean;
36
36
  }
37
37
 
38
+ export type ToastPosition =
39
+ 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';
40
+
41
+ /* Root-level options - set once on <Toaster /> and read by the queue + host. */
42
+ export interface ToasterConfig {
43
+ position: ToastPosition;
44
+ duration: number; // default auto-dismiss ms for timed tones; 0 keeps the per-tone defaults
45
+ visibleToasts: number; // cards shown before the rest recede behind
46
+ gap: number; // px between cards; 0 reads --space-3 from the stylesheet
47
+ offset: number; // px inset from the viewport edge; 0 reads --space-6
48
+ expand: boolean; // start the stack fanned open instead of collapsed
49
+ }
50
+
51
+ export const DEFAULT_TOASTER_CONFIG: ToasterConfig = {
52
+ position: 'bottom-right',
53
+ duration: 0,
54
+ visibleToasts: 3,
55
+ gap: 0,
56
+ offset: 0,
57
+ expand: false,
58
+ };
59
+
38
60
  const DURATION: Record<string, number> = {
39
61
  default: 5000,
40
62
  success: 5000,
@@ -51,7 +73,8 @@ interface ToastStore {
51
73
  toasts: ToastRecord[];
52
74
  paused: boolean;
53
75
  expanded: boolean;
54
- host: null | (() => void);
76
+ mounted: boolean; // a <Toaster /> is live; toasts no-op until it is
77
+ config: ToasterConfig;
55
78
  snap: ToastSnapshot;
56
79
  listeners: Set<() => void>;
57
80
  subscribe(l: () => void): () => void;
@@ -71,7 +94,8 @@ const store: ToastStore = {
71
94
  toasts: [],
72
95
  paused: false,
73
96
  expanded: false,
74
- host: null,
97
+ mounted: false,
98
+ config: { ...DEFAULT_TOASTER_CONFIG },
75
99
  snap: { toasts: [], paused: false, expanded: false },
76
100
  listeners: new Set(),
77
101
  subscribe(l) {
@@ -164,10 +188,36 @@ const store: ToastStore = {
164
188
  },
165
189
  };
166
190
 
191
+ let warned = false;
192
+ let warnScheduled = false;
193
+
194
+ /* A toast with no <Toaster /> mounted renders nothing. Warn once, deferred a tick so a
195
+ toast fired during the same render that mounts <Toaster /> is not a false positive. */
196
+ function warnIfDetached(): void {
197
+ if (store.mounted || warned || warnScheduled || typeof window === 'undefined') return;
198
+ warnScheduled = true;
199
+ setTimeout(() => {
200
+ warnScheduled = false;
201
+ if (store.mounted || warned) return;
202
+ warned = true;
203
+ console.warn(
204
+ 'premium-ds: a toast was triggered but no <Toaster /> is mounted, so nothing will render. Mount <Toaster /> once near your app root.',
205
+ );
206
+ });
207
+ }
208
+
209
+ /* Per-tone default unless the root sets a global duration; loading stays sticky (Infinity). */
210
+ function resolveDuration(tone: ToastTone, opt?: number): number {
211
+ if (opt != null) return opt;
212
+ const toneDefault = DURATION[tone];
213
+ if (toneDefault === Infinity || store.config.duration <= 0) return toneDefault;
214
+ return store.config.duration;
215
+ }
216
+
167
217
  function make(tone: ToastTone, message: string | null, opts: ToastOptions = {}): string {
168
- if (store.host) store.host();
218
+ warnIfDetached();
169
219
  const id = opts.id != null ? String(opts.id) : 'toast-' + ++uid;
170
- const duration = opts.duration != null ? opts.duration : DURATION[tone];
220
+ const duration = resolveDuration(tone, opts.duration);
171
221
  if (store.toasts.some((t) => t.id === id)) {
172
222
  return store.update(id, {
173
223
  tone,
@@ -243,7 +293,7 @@ toast.promise = function <T>(promise: Promise<T>, msgs: ToastPromiseMsgs<T> = {}
243
293
  };
244
294
 
245
295
  toast.custom = (node, opts = {}) => {
246
- if (store.host) store.host();
296
+ warnIfDetached();
247
297
  const id = opts.id != null ? String(opts.id) : 'toast-' + ++uid;
248
298
  const duration = opts.duration != null ? opts.duration : Infinity;
249
299
  if (store.toasts.some((t) => t.id === id)) return store.update(id, { node });
@@ -2,9 +2,7 @@
2
2
 
3
3
  .toast-viewport {
4
4
  position: fixed;
5
- bottom: var(--space-6);
6
- right: var(--space-6);
7
- width: min(22.5rem, calc(100vw - 2 * var(--space-6)));
5
+ width: min(22.5rem, calc(100vw - 2 * var(--toast-offset, var(--space-6))));
8
6
  z-index: var(--layer-toast);
9
7
  margin: 0;
10
8
  padding: 0;
@@ -12,6 +10,24 @@
12
10
  pointer-events: none; /* only the cards themselves are live */
13
11
  }
14
12
 
13
+ /* Anchored by [data-position]; --toast-offset (set by <Toaster offset>) overrides the edge inset. */
14
+ .toast-viewport[data-position^='top'] {
15
+ top: var(--toast-offset, var(--space-6));
16
+ }
17
+ .toast-viewport[data-position^='bottom'] {
18
+ bottom: var(--toast-offset, var(--space-6));
19
+ }
20
+ .toast-viewport[data-position$='right'] {
21
+ right: var(--toast-offset, var(--space-6));
22
+ }
23
+ .toast-viewport[data-position$='left'] {
24
+ left: var(--toast-offset, var(--space-6));
25
+ }
26
+ .toast-viewport[data-position$='center'] {
27
+ left: 50%;
28
+ transform: translateX(-50%);
29
+ }
30
+
15
31
  /* The card - a glass consumer (.glass is set in JSX). */
16
32
  .toast {
17
33
  /* tone hooks - set by [data-tone] below */
@@ -21,13 +37,11 @@
21
37
  --glass-sheen-rest: 0.45;
22
38
 
23
39
  position: absolute;
24
- bottom: 0;
25
- right: 0;
40
+ left: 0;
26
41
  width: 100%;
27
42
  box-sizing: border-box;
28
43
  overflow: hidden; /* custom content stays in the pane */
29
44
  pointer-events: auto;
30
- transform-origin: center bottom; /* the stack recedes upward */
31
45
  border-radius: var(--radius-lg);
32
46
 
33
47
  /* Motion owns transform - keep only the color/shadow transitions (never smooth Motion's writes) */
@@ -36,6 +50,16 @@
36
50
  box-shadow var(--duration-base) var(--ease-standard);
37
51
  }
38
52
 
53
+ /* Cards pin to the stack's edge; Motion offsets each one away from it (up from bottom, down from top). */
54
+ .toast-viewport[data-position^='bottom'] .toast {
55
+ bottom: 0;
56
+ transform-origin: center bottom;
57
+ }
58
+ .toast-viewport[data-position^='top'] .toast {
59
+ top: 0;
60
+ transform-origin: center top;
61
+ }
62
+
39
63
  /* rich panes for genuine status, diluted to stay glass; neutral otherwise */
40
64
  .toast[data-tone='success'] {
41
65
  --toast-hue: var(--success);
@@ -12,9 +12,12 @@
12
12
  --border-emphasis: 1.5px;
13
13
 
14
14
  /* Focus & selection rings - box-shadows (not outlines) so they follow border-radius. */
15
- --ring-accent: 0 0 0 3px oklch(0.63 0.118 198 / 0.32);
15
+
16
+ /* Derive from --accent so the ring tracks the active accent and theme, never a fixed hue. */
17
+ --ring-accent: 0 0 0 3px color-mix(in oklab, var(--accent) 32%, transparent);
16
18
  --ring-danger: 0 0 0 3px oklch(0.602 0.196 27 / 0.3);
17
19
  --ring-warning: 0 0 0 3px color-mix(in oklab, var(--warning) 30%, transparent);
18
20
  --ring-success: 0 0 0 3px color-mix(in oklab, var(--success) 30%, transparent);
19
- --ring-offset: 0 0 0 2px var(--bg-surface), 0 0 0 4px oklch(0.63 0.118 198 / 0.4);
21
+ --ring-offset:
22
+ 0 0 0 2px var(--bg-surface), 0 0 0 4px color-mix(in oklab, var(--accent) 40%, transparent);
20
23
  }
@@ -22,6 +22,7 @@
22
22
  --border-default: var(--gray-200);
23
23
  --border-strong: var(--gray-300);
24
24
 
25
+ --accent-lift: var(--teal-400);
25
26
  --accent: var(--teal-500);
26
27
  --accent-hover: var(--teal-600);
27
28
  --accent-active: var(--teal-700);
@@ -58,18 +59,3 @@
58
59
 
59
60
  --focus-ring: var(--ring-accent);
60
61
  }
61
-
62
- /* -- Dark theme - scaffold (light-first; build this next pass) --------------
63
- [data-theme="dark"] {
64
- --bg-app: var(--gray-950);
65
- --bg-surface: var(--gray-900);
66
- --bg-subtle: var(--gray-800);
67
- --text-strong: var(--gray-25);
68
- --text-body: var(--gray-200);
69
- --text-muted: var(--gray-400);
70
- --border-default: var(--gray-800);
71
- --accent: var(--teal-400);
72
- --text-accent: var(--teal-300);
73
- ...
74
- }
75
- */
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/toast/Toast.tsx"],"names":[],"mappings":"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","file":"chunk-NKGMQL6I.js","sourcesContent":["'use client';\n\n/* Toast - the React render layer (queue + clocks live in toast-store.ts). */\nimport * as React from 'react';\nimport { createPortal } from 'react-dom';\nimport { createRoot } from 'react-dom/client';\nimport { motion, AnimatePresence, animate, useMotionValue, type PanInfo } from 'motion/react';\nimport { UIMotion } from '../../tokens/motion-tokens';\nimport { Icon, type IconName } from '../icon/Icon';\nimport { UIToast, type ToastRecord, type ToastTone } from './toast-store';\n\nconst SM = UIMotion;\nconst store = UIToast;\nconst { useRef, useEffect, useLayoutEffect, useState, useSyncExternalStore } = React;\n\nconst VISIBLE = 3;\nconst RENDERED = 6;\nconst COLLAPSE_GRACE = 140;\nconst SWIPE_X = 64;\nconst SWIPE_V = 480;\n\n// expanded gap AND collapsed peek - read lazily (post-stylesheet), once\nlet GAP = 0;\nfunction stackGap() {\n if (GAP) return GAP;\n const cs = getComputedStyle(document.documentElement);\n const raw = cs.getPropertyValue('--space-3').trim();\n const n = parseFloat(raw) || 0;\n GAP = (raw.endsWith('rem') ? n * (parseFloat(cs.fontSize) || 16) : n) || 12;\n return GAP;\n}\n\nconst TONE_ICON: Record<string, IconName> = {\n success: 'check-circle',\n error: 'warning-circle',\n warning: 'warning',\n info: 'info',\n};\n\n/* useToneGesture - success glint, error headshake; the glint class is toggled with a reflow (remove - offsetWidth - add) to restart the one-shot. */\nfunction useToneGesture(\n tone: ToastTone,\n ref: React.RefObject<HTMLElement>,\n x: ReturnType<typeof useMotionValue<number>>,\n) {\n const prevTone = useRef<ToastTone | null>(null);\n useEffect(() => {\n const prev = prevTone.current;\n prevTone.current = tone;\n if (tone === prev) return;\n if (tone === 'success') {\n const el = ref.current;\n el.classList.remove('glass-glint');\n void el.offsetWidth;\n el.classList.add('glass-glint');\n const id = setTimeout(() => el.classList.remove('glass-glint'), SM.dur.slow * 1000 + 80);\n return () => clearTimeout(id);\n }\n if (tone === 'error') {\n const fall = animate(x, [0, -7, 5, -2, 0], {\n duration: SM.dur.slow,\n ease: SM.ease.standard,\n delay: SM.dur.base,\n });\n return () => fall.stop();\n }\n }, [tone]);\n}\n\nfunction ToastItem({\n t,\n depth,\n offset,\n hidden,\n behind,\n frameHeight,\n onHeight,\n}: {\n t: ToastRecord;\n depth: number;\n offset: number;\n hidden: boolean;\n behind: boolean;\n frameHeight: number | null;\n onHeight: (id: string, h: number) => void;\n}) {\n const ref = useRef<HTMLLIElement>(null);\n const x = useMotionValue(0); // swipe travel - ours, so dismissal can finish it\n useToneGesture(t.tone, ref, x);\n\n // report the CONTENT height (firstChild - the li's own height is animated, so it'd read mid-tween)\n useLayoutEffect(() => {\n if (ref.current && ref.current.firstElementChild)\n onHeight(t.id, (ref.current.firstElementChild as HTMLElement).offsetHeight);\n });\n\n const swipe = (_e: PointerEvent | MouseEvent | TouchEvent, info: PanInfo) => {\n if (info.offset.x > SWIPE_X || info.velocity.x > SWIPE_V) {\n // fling out the right edge, THEN remove - the exit fade plays where it landed, no snap-back\n animate(x, 420, { duration: SM.dur.fast, ease: SM.ease.exit }).then(() =>\n store.dismiss(t.id),\n );\n }\n };\n\n return (\n <motion.li\n ref={ref}\n className=\"toast glass\"\n data-tone={t.tone}\n data-behind={behind ? '' : undefined}\n role={t.tone === 'error' ? 'alert' : 'status'}\n aria-atomic=\"true\"\n style={{ x }}\n initial={{ opacity: 0, y: 24, scale: 0.97 }}\n animate={{\n opacity: hidden ? 0 : 1,\n y: -offset,\n scale: 1 - depth * 0.05,\n /* behind-cards adopt the front card's frame height (content faded) so a short card never drowns behind a tall one */\n height: frameHeight || 'auto',\n pointerEvents: hidden ? 'none' : 'auto',\n }}\n exit={{ opacity: 0, scale: 0.96, transition: { duration: SM.dur.fast, ease: SM.ease.exit } }}\n transition={{ y: SM.t.settle, scale: SM.t.settle, height: SM.t.settle, opacity: SM.t.enter }}\n drag={t.dismissible ? 'x' : false}\n dragConstraints={{ left: 0, right: 0 }}\n dragElastic={{ left: 0.04, right: 0.9 }}\n dragMomentum={false}\n onDragEnd={swipe}\n onKeyDown={(e) => {\n if (e.key === 'Escape' && t.dismissible) store.dismiss(t.id);\n }}\n >\n {t.node ? (\n <div className=\"toast__custom\">{t.node as React.ReactNode}</div>\n ) : (\n <ToastBody t={t} />\n )}\n </motion.li>\n );\n}\n\nfunction ToastBody({ t }: { t: ToastRecord }) {\n return (\n <div className=\"toast__inner\">\n {(t.tone !== 'default' || isFinite(t.duration)) && (\n <span className=\"toast__icon\">\n {t.tone !== 'default' && (\n /* keyed remount (not nested AnimatePresence): old glyph cuts, new springs in */\n <motion.span\n key={t.tone}\n className=\"toast__icon-glyph\"\n initial={{ scale: 0.5, opacity: 0 }}\n animate={{ scale: 1, opacity: 1 }}\n transition={{ scale: SM.t.settle, opacity: SM.t.enter }}\n >\n {t.tone === 'loading' ? (\n <span className=\"toast__spinner\" aria-hidden=\"true\"></span>\n ) : (\n <Icon name={TONE_ICON[t.tone]} weight=\"fill\" />\n )}\n </motion.span>\n )}\n {isFinite(t.duration) && (\n /* the ring - keyed to the timer so a restart re-fills it; pathLength=1 makes dashoffset a fraction */\n <svg\n key={'ring-' + t.timerKey}\n className=\"toast__ring\"\n viewBox=\"0 0 36 36\"\n aria-hidden=\"true\"\n >\n <circle className=\"toast__ring-track\" cx=\"18\" cy=\"18\" r=\"16.5\"></circle>\n <circle\n className=\"toast__ring-arc\"\n cx=\"18\"\n cy=\"18\"\n r=\"16.5\"\n pathLength={1}\n style={{ animationDuration: t.duration + 'ms' }}\n ></circle>\n </svg>\n )}\n </span>\n )}\n <motion.div\n key={String(t.message) + '-' + String(t.description)}\n className=\"toast__text\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={SM.t.enter}\n >\n <p className=\"toast__message\">{t.message}</p>\n {t.description != null && <p className=\"toast__desc\">{t.description}</p>}\n </motion.div>\n {t.count > 1 && (\n <motion.span\n key={t.count}\n className=\"toast__count\"\n initial={{ scale: 0.6 }}\n animate={{ scale: 1 }}\n transition={SM.t.settle}\n >\n ×{t.count}\n </motion.span>\n )}\n {t.action && (\n <button\n type=\"button\"\n className=\"btn btn--secondary btn--sm toast__action\"\n onClick={() => {\n if (t.action.onClick) t.action.onClick();\n store.dismiss(t.id);\n }}\n >\n {t.action.label}\n </button>\n )}\n {t.dismissible && (\n <button\n type=\"button\"\n className=\"toast__close\"\n aria-label=\"Dismiss\"\n onClick={() => store.dismiss(t.id)}\n >\n <Icon name=\"close\" size=\"sm\" />\n </button>\n )}\n </div>\n );\n}\n\nfunction ToastHost() {\n const { toasts, paused, expanded } = useSyncExternalStore(store.subscribe, store.get);\n const [heights, setHeights] = useState<Record<string, number>>({});\n const collapseTimer = useRef<ReturnType<typeof setTimeout> | 0>(0);\n const hovering = useRef(false);\n\n const onHeight = (id: string, h: number) =>\n setHeights((prev) => (prev[id] === h ? prev : { ...prev, [id]: h }));\n\n // hold = fan open + freeze clocks; release = graced fold + resume\n const onHold = () => {\n hovering.current = true;\n clearTimeout(collapseTimer.current);\n store.setExpanded(true);\n store.pause();\n };\n const onRelease = (graced: boolean) => {\n hovering.current = false;\n clearTimeout(collapseTimer.current);\n collapseTimer.current = setTimeout(\n () => {\n store.setExpanded(false);\n store.resume();\n },\n graced ? COLLAPSE_GRACE : 0,\n );\n };\n\n // tab hidden - freeze clocks, visible - resume (visibility, not window focus: an embedded preview blurs on outside clicks).\n useEffect(() => {\n const onVis = () => {\n if (document.hidden) store.pause();\n else if (!hovering.current) store.resume();\n };\n document.addEventListener('visibilitychange', onVis);\n return () => document.removeEventListener('visibilitychange', onVis);\n }, []);\n\n // safety net: last card removed under the cursor fires no pointerout, so release here\n useEffect(() => {\n if (toasts.length === 0) {\n hovering.current = false;\n clearTimeout(collapseTimer.current);\n store.setExpanded(false);\n store.resume();\n }\n }, [toasts.length]);\n\n const slice = toasts.slice(-RENDERED);\n const n = slice.length;\n const frontH = n ? heights[slice[n - 1].id] || 0 : 0;\n const gap = stackGap();\n\n return createPortal(\n <ol\n className={'toast-viewport' + (paused ? ' is-paused' : '')}\n aria-label=\"Notifications\"\n onPointerOver={(e) => {\n if (e.pointerType !== 'touch') onHold();\n }}\n onPointerOut={(e) => {\n if (!e.relatedTarget || !e.currentTarget.contains(e.relatedTarget as Node)) onRelease(true);\n }}\n onFocus={onHold}\n onBlur={(e) => {\n if (!e.relatedTarget || !e.currentTarget.contains(e.relatedTarget as Node))\n onRelease(false);\n }}\n >\n {/* initial stays true - the host mounts with the first toast, so initial={false} would skip its entrance */}\n <AnimatePresence>\n {slice.map((t, i) => {\n const depth = n - 1 - i; // newest = 0, at the front\n let offset = depth * gap; // collapsed: peek per depth\n if (expanded) {\n // expanded: real heights\n offset = 0;\n for (let j = i + 1; j < n; j++) offset += (heights[slice[j].id] || 0) + gap;\n }\n return (\n <ToastItem\n key={t.id}\n t={t}\n depth={expanded ? 0 : depth}\n offset={offset}\n hidden={!expanded && depth >= VISIBLE}\n behind={!expanded && depth > 0}\n frameHeight={!expanded && depth > 0 && frontH ? frontH : null}\n onHeight={onHeight}\n />\n );\n })}\n </AnimatePresence>\n </ol>,\n document.body,\n );\n}\n\nlet hostMounted = false;\nfunction ensureToastHost() {\n if (hostMounted) return;\n hostMounted = true;\n const el = document.createElement('div');\n el.setAttribute('data-toast-host', '');\n document.body.appendChild(el);\n createRoot(el).render(<ToastHost />);\n}\nstore.host = ensureToastHost;\nif (store.toasts.length) ensureToastHost(); // toasts fired before we loaded\n\n/* Toaster - mount once near the app root so this module loads client-side and the lazy host is registered (toast() also auto-mounts). */\nexport function Toaster(): null {\n useEffect(() => {\n ensureToastHost();\n }, []);\n return null;\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/toast/toast-store.ts"],"names":[],"mappings":"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","file":"chunk-PUPZ4HME.js","sourcesContent":["// toast-store - the Toast machinery: queue, clocks, coalescing, imperative API (zero React).\n\nexport type ToastTone = 'default' | 'success' | 'error' | 'warning' | 'info' | 'loading' | 'custom';\n\nexport interface ToastAction {\n label: string;\n onClick: () => void;\n}\n\nexport interface ToastOptions {\n id?: string | number;\n duration?: number;\n description?: string | null;\n action?: ToastAction | null;\n}\n\nexport interface ToastRecord {\n id: string;\n tone: ToastTone;\n message: string | null;\n description: string | null;\n action: ToastAction | null;\n duration: number;\n remaining: number;\n count: number;\n timerKey: number;\n node: unknown; // a React node, kept opaque so this module stays React-free\n dismissible: boolean;\n expiresAt?: number;\n}\n\nexport interface ToastSnapshot {\n toasts: ToastRecord[];\n paused: boolean;\n expanded: boolean;\n}\n\nconst DURATION: Record<string, number> = {\n default: 5000,\n success: 5000,\n info: 5000,\n warning: 8000,\n error: 8000,\n loading: Infinity, // sticky until updated/dismissed\n};\n\nlet uid = 0;\nconst timers = new Map<string, ReturnType<typeof setTimeout>>();\n\ninterface ToastStore {\n toasts: ToastRecord[];\n paused: boolean;\n expanded: boolean;\n host: null | (() => void);\n snap: ToastSnapshot;\n listeners: Set<() => void>;\n subscribe(l: () => void): () => void;\n get(): ToastSnapshot;\n emit(): void;\n add(t: ToastRecord): string;\n update(id: string, patch: Partial<ToastRecord>): string;\n dismiss(id?: string | null): void;\n schedule(t: ToastRecord): void;\n pause(): void;\n resume(): void;\n setExpanded(v: boolean): void;\n}\n\n/* Store - the queue; the API writes, the host renders. */\nconst store: ToastStore = {\n toasts: [],\n paused: false,\n expanded: false,\n host: null,\n snap: { toasts: [], paused: false, expanded: false },\n listeners: new Set(),\n subscribe(l) {\n store.listeners.add(l);\n return () => store.listeners.delete(l);\n },\n get: () => store.snap,\n emit() {\n store.snap = { toasts: store.toasts.slice(), paused: store.paused, expanded: store.expanded };\n store.listeners.forEach((l) => l());\n },\n\n add(t) {\n // coalesce: an identical visible toast becomes a xN counter instead of a duplicate\n const twin = store.toasts.find(\n (x) =>\n !x.node &&\n !t.node &&\n x.tone === t.tone &&\n x.message === t.message &&\n x.description === t.description,\n );\n if (twin) return store.update(twin.id, { count: twin.count + 1 });\n store.toasts.push(t);\n store.schedule(t);\n store.emit();\n return t.id;\n },\n\n update(id, patch) {\n const t = store.toasts.find((x) => x.id === id);\n if (!t) return id;\n Object.assign(t, patch);\n if ('tone' in patch || 'duration' in patch || 'count' in patch) {\n t.remaining = t.duration;\n t.timerKey++; // re-key the ring so it re-fills\n store.schedule(t);\n }\n store.emit();\n return id;\n },\n\n dismiss(id) {\n const gone = id == null ? store.toasts : store.toasts.filter((t) => t.id === id);\n gone.forEach((t) => {\n clearTimeout(timers.get(t.id));\n timers.delete(t.id);\n });\n store.toasts = id == null ? [] : store.toasts.filter((t) => t.id !== id);\n store.emit();\n },\n\n schedule(t) {\n clearTimeout(timers.get(t.id));\n timers.delete(t.id);\n if (!isFinite(t.duration)) return;\n if (store.paused) return; // resume() picks it up with t.remaining\n t.expiresAt = performance.now() + t.remaining;\n timers.set(\n t.id,\n setTimeout(() => store.dismiss(t.id), t.remaining),\n );\n },\n\n pause() {\n // freeze every clock, remember what's left\n if (store.paused) return;\n store.paused = true;\n store.toasts.forEach((t) => {\n if (timers.has(t.id)) {\n clearTimeout(timers.get(t.id));\n timers.delete(t.id);\n t.remaining = Math.max(0, (t.expiresAt ?? performance.now()) - performance.now());\n }\n });\n store.emit();\n },\n\n resume() {\n if (!store.paused) return;\n store.paused = false;\n store.toasts.forEach((t) => store.schedule(t));\n store.emit();\n },\n\n setExpanded(v) {\n if (v === store.expanded) return;\n store.expanded = v;\n store.emit();\n },\n};\n\nfunction make(tone: ToastTone, message: string | null, opts: ToastOptions = {}): string {\n if (store.host) store.host();\n const id = opts.id != null ? String(opts.id) : 'toast-' + ++uid;\n const duration = opts.duration != null ? opts.duration : DURATION[tone];\n if (store.toasts.some((t) => t.id === id)) {\n return store.update(id, {\n tone,\n message,\n duration,\n description: opts.description != null ? opts.description : null,\n action: opts.action != null ? opts.action : null,\n });\n }\n return store.add({\n id,\n tone,\n message,\n description: opts.description != null ? opts.description : null,\n action: opts.action != null ? opts.action : null,\n duration,\n remaining: duration,\n count: 1,\n timerKey: 0,\n node: null,\n dismissible: true,\n });\n}\n\ntype PromiseMsg<V> = string | ((v: V) => string);\nexport interface ToastPromiseMsgs<T> {\n loading?: string;\n success?: PromiseMsg<T>;\n error?: PromiseMsg<unknown>;\n}\n\nexport interface ToastApi {\n (message: string, opts?: ToastOptions): string;\n success(m: string, o?: ToastOptions): string;\n error(m: string, o?: ToastOptions): string;\n warning(m: string, o?: ToastOptions): string;\n info(m: string, o?: ToastOptions): string;\n loading(m: string, o?: ToastOptions): string;\n dismiss(id?: string | null): void;\n update(id: string, patch?: Partial<ToastRecord>): string;\n promise<T>(promise: Promise<T>, msgs?: ToastPromiseMsgs<T>): Promise<T>;\n custom(\n node: unknown,\n opts?: { id?: string | number; duration?: number; dismissible?: boolean },\n ): string;\n}\n\nconst toast = ((message: string, opts?: ToastOptions) =>\n make('default', message, opts)) as ToastApi;\ntoast.success = (m, o) => make('success', m, o);\ntoast.error = (m, o) => make('error', m, o);\ntoast.warning = (m, o) => make('warning', m, o);\ntoast.info = (m, o) => make('info', m, o);\ntoast.loading = (m, o) => make('loading', m, o);\ntoast.dismiss = (id) => store.dismiss(id);\n\ntoast.update = (id, patch = {}) => {\n // a tone change without an explicit duration adopts that tone's clock\n if (patch.tone && patch.duration == null) patch = { ...patch, duration: DURATION[patch.tone] };\n return store.update(id, patch);\n};\n\ntoast.promise = function <T>(promise: Promise<T>, msgs: ToastPromiseMsgs<T> = {}): Promise<T> {\n const id = make('loading', msgs.loading != null ? msgs.loading : 'Working...');\n const text = <V>(m: PromiseMsg<V> | undefined, fallback: string, v: V): string =>\n typeof m === 'function' ? (m as (x: V) => string)(v) : m != null ? m : fallback;\n promise.then(\n (v) => toast.update(id, { tone: 'success', message: text(msgs.success, 'Done', v) }),\n (e) =>\n toast.update(id, { tone: 'error', message: text(msgs.error, 'Something went wrong', e) }),\n );\n return promise;\n};\n\ntoast.custom = (node, opts = {}) => {\n if (store.host) store.host();\n const id = opts.id != null ? String(opts.id) : 'toast-' + ++uid;\n const duration = opts.duration != null ? opts.duration : Infinity;\n if (store.toasts.some((t) => t.id === id)) return store.update(id, { node });\n return store.add({\n id,\n tone: 'custom',\n node,\n message: null,\n description: null,\n action: null,\n duration,\n remaining: duration,\n count: 1,\n timerKey: 0,\n dismissible: opts.dismissible === true,\n });\n};\n\nexport { toast };\nexport const UIToast = store;\n"]}