ga-toasts 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1508 @@
1
+ // src/styles.generated.ts
2
+ var css = `/**
3
+ * GA Toasts 2.x \u2014 stylesheet.
4
+ *
5
+ * Self-contained: all design tokens are namespaced (--gat-*) and scoped to the
6
+ * toast elements so they never collide with a host app's own variables.
7
+ * Vertical position + stacking is driven by inline transforms from the engine;
8
+ * this file only styles the cards and provides the transition timing.
9
+ */
10
+
11
+ /* ---------------- Design tokens ---------------- */
12
+ .ga-toast-container {
13
+ --gat-width: 380px;
14
+ --gat-radius: 14px;
15
+ --gat-gap: 14px;
16
+ --gat-edge: 22px;
17
+
18
+ --gat-font: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
19
+ Roboto, "Helvetica Neue", Arial, sans-serif;
20
+
21
+ --gat-surface: rgba(255, 255, 255, 0.82);
22
+ --gat-surface-solid: #ffffff;
23
+ --gat-border: rgba(15, 23, 42, 0.07);
24
+ --gat-ring: rgba(15, 23, 42, 0.05); /* hairline definition ring */
25
+ --gat-highlight: rgba(255, 255, 255, 0.75); /* inset top edge highlight */
26
+ --gat-text: #0f172a;
27
+ --gat-text-soft: #475569;
28
+ --gat-text-muted: #64748b; /* meets WCAG AA on the surface */
29
+ --gat-shadow: 0 1px 2px rgba(15, 23, 42, 0.05),
30
+ 0 8px 20px -12px rgba(15, 23, 42, 0.16);
31
+ --gat-shadow-hover: 0 2px 4px -2px rgba(15, 23, 42, 0.12),
32
+ 0 14px 32px -12px rgba(15, 23, 42, 0.26);
33
+ --gat-chip: rgba(15, 23, 42, 0.05);
34
+
35
+ --gat-success: #16a34a;
36
+ --gat-error: #e11d48;
37
+ --gat-warning: #d97706;
38
+ --gat-info: #2563eb;
39
+ --gat-primary: #6366f1;
40
+ --gat-secondary: #64748b;
41
+ --gat-loading: #2563eb;
42
+
43
+ --gat-ease: cubic-bezier(0.22, 1, 0.36, 1);
44
+ }
45
+
46
+ /* Dark theme \u2014 via explicit attribute/class or the OS preference. */
47
+ [data-ga-theme="dark"] .ga-toast-container,
48
+ .ga-theme-dark .ga-toast-container,
49
+ .ga-toast-container.ga-theme-dark {
50
+ --gat-surface: rgba(24, 27, 34, 0.82);
51
+ --gat-surface-solid: #191c22;
52
+ --gat-border: rgba(255, 255, 255, 0.09);
53
+ --gat-ring: rgba(255, 255, 255, 0.06);
54
+ --gat-highlight: rgba(255, 255, 255, 0.07);
55
+ --gat-text: #f8fafc;
56
+ --gat-text-soft: #cbd5e1;
57
+ --gat-text-muted: #9aa7ba;
58
+ --gat-shadow: 0 1px 2px rgba(0, 0, 0, 0.35),
59
+ 0 10px 28px -14px rgba(0, 0, 0, 0.5);
60
+ --gat-shadow-hover: 0 2px 6px -2px rgba(0, 0, 0, 0.5),
61
+ 0 16px 38px -12px rgba(0, 0, 0, 0.64);
62
+ --gat-chip: rgba(255, 255, 255, 0.08);
63
+ }
64
+
65
+ /* Auto (OS) dark mode \u2014 applies ONLY when no explicit theme is set on the root,
66
+ so an app that pins data-ga-theme="light" (like the demo) is never overridden. */
67
+ @media (prefers-color-scheme: dark) {
68
+ :root:not([data-ga-theme]):not(.ga-theme-light):not(.ga-theme-dark) .ga-toast-container {
69
+ --gat-surface: rgba(24, 27, 34, 0.82);
70
+ --gat-surface-solid: #191c22;
71
+ --gat-border: rgba(255, 255, 255, 0.09);
72
+ --gat-ring: rgba(255, 255, 255, 0.06);
73
+ --gat-highlight: rgba(255, 255, 255, 0.07);
74
+ --gat-text: #f8fafc;
75
+ --gat-text-soft: #cbd5e1;
76
+ --gat-text-muted: #9aa7ba;
77
+ --gat-shadow: 0 1px 2px rgba(0, 0, 0, 0.35),
78
+ 0 10px 28px -14px rgba(0, 0, 0, 0.5);
79
+ --gat-chip: rgba(255, 255, 255, 0.08);
80
+ }
81
+ }
82
+
83
+ /* ---------------- Modal backdrop (confirm) ---------------- */
84
+ .ga-toast-backdrop {
85
+ position: fixed;
86
+ inset: 0;
87
+ z-index: 2147482990; /* just under the toast containers */
88
+ background: rgba(10, 12, 20, 0.42);
89
+ backdrop-filter: blur(2px);
90
+ -webkit-backdrop-filter: blur(2px);
91
+ opacity: 0;
92
+ transition: opacity 0.22s ease;
93
+ }
94
+ .ga-toast-backdrop-show { opacity: 1; }
95
+ @media (prefers-reduced-transparency: reduce) {
96
+ .ga-toast-backdrop { backdrop-filter: none; -webkit-backdrop-filter: none; }
97
+ }
98
+
99
+ /* ---------------- Container + positioning ---------------- */
100
+ .ga-toast-container {
101
+ position: fixed;
102
+ z-index: 2147483000;
103
+ width: var(--gat-width);
104
+ max-width: calc(100vw - 2 * var(--gat-edge));
105
+ pointer-events: none;
106
+ font-family: var(--gat-font);
107
+ }
108
+
109
+ /* Logical inline positioning so -start/-end mirror automatically under RTL. */
110
+ .ga-toast-container-top-start { inset-block-start: var(--gat-edge); inset-inline-start: var(--gat-edge); }
111
+ .ga-toast-container-top-end { inset-block-start: var(--gat-edge); inset-inline-end: var(--gat-edge); }
112
+ .ga-toast-container-top-center {
113
+ inset-block-start: var(--gat-edge); left: 50%; transform: translateX(-50%);
114
+ }
115
+ .ga-toast-container-bottom-start { inset-block-end: var(--gat-edge); inset-inline-start: var(--gat-edge); }
116
+ .ga-toast-container-bottom-end { inset-block-end: var(--gat-edge); inset-inline-end: var(--gat-edge); }
117
+ .ga-toast-container-bottom-center {
118
+ inset-block-end: var(--gat-edge); left: 50%; transform: translateX(-50%);
119
+ }
120
+ .ga-toast-container-middle-start { top: 50%; inset-inline-start: var(--gat-edge); }
121
+ .ga-toast-container-middle-end { top: 50%; inset-inline-end: var(--gat-edge); }
122
+ .ga-toast-container-middle-center { top: 50%; left: 50%; transform: translateX(-50%); }
123
+
124
+ /* ---------------- Toast card ---------------- */
125
+ .ga-toast {
126
+ position: absolute;
127
+ inset-inline: 0;
128
+ width: 100%;
129
+ box-sizing: border-box;
130
+ display: flex;
131
+ flex-direction: row;
132
+ align-items: flex-start;
133
+ gap: 11px;
134
+ padding: 14px 16px;
135
+ color: var(--gat-text);
136
+ background: var(--gat-surface-solid);
137
+ border: none;
138
+ border-radius: var(--gat-radius);
139
+ box-shadow:
140
+ var(--gat-shadow),
141
+ inset 0 1px 0 var(--gat-highlight);
142
+ pointer-events: auto;
143
+ opacity: 0;
144
+ transform: translateY(-140%);
145
+ transition:
146
+ transform 0.5s var(--gat-ease),
147
+ opacity 0.35s ease,
148
+ box-shadow 0.2s ease;
149
+ will-change: transform, opacity;
150
+ overflow: hidden;
151
+ font-size: 0.875rem;
152
+ line-height: 1.5;
153
+ -webkit-font-smoothing: antialiased;
154
+ text-align: start;
155
+ touch-action: pan-y;
156
+ overscroll-behavior: contain;
157
+ }
158
+
159
+ /* Subtle lift on hover (shadow only \u2014 transforms are owned by the engine, so
160
+ changing them here would fight the stack layout). */
161
+ @media (hover: hover) {
162
+ .ga-toast:hover {
163
+ box-shadow:
164
+ var(--gat-shadow-hover),
165
+ inset 0 1px 0 var(--gat-highlight);
166
+ }
167
+ }
168
+
169
+ [data-ga-position^="top"] .ga-toast,
170
+ [data-ga-position^="middle"] .ga-toast { top: 0; transform-origin: top center; }
171
+ [data-ga-position^="bottom"] .ga-toast { bottom: 0; transform-origin: bottom center; }
172
+
173
+ .ga-toast-glass {
174
+ background: var(--gat-surface);
175
+ backdrop-filter: blur(16px) saturate(160%);
176
+ -webkit-backdrop-filter: blur(16px) saturate(160%);
177
+ }
178
+
179
+ /* No leading accent stripe \u2014 the colored icon carries the type identity, for a
180
+ cleaner, more minimal card (\xE0 la Sonner/Linear). */
181
+
182
+ .ga-toast-hide { pointer-events: none; }
183
+
184
+ /* Per-type accent colors. */
185
+ .ga-toast-success { --gat-accent: var(--gat-success); }
186
+ .ga-toast-error { --gat-accent: var(--gat-error); }
187
+ .ga-toast-warning { --gat-accent: var(--gat-warning); }
188
+ .ga-toast-info { --gat-accent: var(--gat-info); }
189
+ .ga-toast-primary { --gat-accent: var(--gat-primary); }
190
+ .ga-toast-secondary { --gat-accent: var(--gat-secondary); }
191
+ .ga-toast-loading { --gat-accent: var(--gat-loading); }
192
+
193
+ /* ---------------- Content column ---------------- */
194
+ /* The content column sits between the leading icon and the trailing close. */
195
+ .ga-toast-content {
196
+ display: flex;
197
+ flex-direction: column;
198
+ gap: 4px;
199
+ flex: 1;
200
+ min-width: 0;
201
+ }
202
+
203
+ .ga-toast-header {
204
+ display: flex;
205
+ align-items: center;
206
+ gap: 8px;
207
+ min-width: 0;
208
+ }
209
+
210
+ /* Small inline colored glyph \u2014 no chunky chip (Sonner/Linear style). Leads the
211
+ row and aligns with the first line of text, so title-less toasts read well. */
212
+ .ga-toast-icon {
213
+ flex-shrink: 0;
214
+ display: inline-flex;
215
+ align-items: center;
216
+ justify-content: center;
217
+ margin-top: 1px;
218
+ color: var(--gat-accent, var(--gat-info));
219
+ }
220
+ .ga-toast-icon svg { width: 1.25rem; height: 1.25rem; display: block; overflow: visible; }
221
+
222
+ .ga-toast-spinner { animation: ga-toast-spin 0.8s linear infinite; }
223
+
224
+ /* Icons draw themselves in on mount (skipped under reduced motion). */
225
+ @media (prefers-reduced-motion: no-preference) {
226
+ .ga-toast-icon .ga-ic-ring {
227
+ stroke-dasharray: 64;
228
+ stroke-dashoffset: 64;
229
+ animation: ga-toast-ic-draw 0.5s var(--gat-ease) 0.02s forwards;
230
+ }
231
+ .ga-toast-icon .ga-ic-draw {
232
+ stroke-dasharray: 28;
233
+ stroke-dashoffset: 28;
234
+ animation: ga-toast-ic-draw 0.4s var(--gat-ease) 0.16s forwards;
235
+ }
236
+ }
237
+ @keyframes ga-toast-ic-draw {
238
+ to { stroke-dashoffset: 0; }
239
+ }
240
+
241
+ .ga-toast-avatar {
242
+ flex-shrink: 0;
243
+ width: 34px;
244
+ height: 34px;
245
+ border-radius: 50%;
246
+ object-fit: cover;
247
+ border: 1px solid var(--gat-border);
248
+ }
249
+
250
+ .ga-toast-unread-dot {
251
+ flex-shrink: 0;
252
+ width: 8px;
253
+ height: 8px;
254
+ border-radius: 50%;
255
+ background: var(--gat-accent, var(--gat-info));
256
+ }
257
+
258
+ .ga-toast-title {
259
+ font-weight: 600;
260
+ font-size: 0.9rem;
261
+ color: var(--gat-text);
262
+ flex: 1;
263
+ min-width: 0;
264
+ letter-spacing: -0.011em;
265
+ }
266
+ .ga-toast-title-truncate {
267
+ white-space: nowrap;
268
+ overflow: hidden;
269
+ text-overflow: ellipsis;
270
+ }
271
+
272
+ .ga-toast-status {
273
+ flex-shrink: 0;
274
+ padding: 2px 9px;
275
+ border-radius: 999px;
276
+ font-size: 0.6875rem;
277
+ font-weight: 600;
278
+ text-transform: uppercase;
279
+ letter-spacing: 0.04em;
280
+ /* Darkened accent ink on a stronger tint so the small label clears AA. */
281
+ color: var(--gat-accent, var(--gat-info));
282
+ color: color-mix(in srgb, var(--gat-accent, var(--gat-info)) 58%, var(--gat-text));
283
+ background: var(--gat-chip); /* fallback if color-mix is unsupported */
284
+ background: color-mix(in srgb, var(--gat-accent, var(--gat-info)) 18%, transparent);
285
+ }
286
+
287
+ /* ---------------- Close button ---------------- */
288
+ .ga-toast-close {
289
+ flex-shrink: 0;
290
+ align-self: flex-start;
291
+ margin-top: -1px;
292
+ margin-inline-end: -4px; /* pull toward the corner without losing hit area */
293
+ display: inline-flex;
294
+ align-items: center;
295
+ justify-content: center;
296
+ width: 26px;
297
+ height: 26px;
298
+ padding: 0;
299
+ border: none;
300
+ border-radius: 7px;
301
+ color: var(--gat-text-muted);
302
+ background: transparent;
303
+ cursor: pointer;
304
+ opacity: 0.9; /* stays >=3:1 at rest; full on hover/focus */
305
+ transition: background 0.15s ease, color 0.15s ease, opacity 0.15s ease;
306
+ }
307
+ .ga-toast:hover .ga-toast-close,
308
+ .ga-toast:focus-within .ga-toast-close { opacity: 1; }
309
+ .ga-toast-close svg { width: 15px; height: 15px; display: block; }
310
+ .ga-toast-close:hover {
311
+ background: var(--gat-chip);
312
+ color: var(--gat-text);
313
+ }
314
+ /* currentColor is guaranteed to contrast with its own surface (white on filled,
315
+ muted on default, etc.), so the focus ring is never accent-on-accent. */
316
+ .ga-toast-close:focus-visible {
317
+ outline: 2px solid currentColor;
318
+ outline-offset: 2px;
319
+ }
320
+
321
+ /* ---------------- Body ---------------- */
322
+ .ga-toast-body { min-width: 0; }
323
+ .ga-toast-meta {
324
+ font-size: 0.75rem;
325
+ color: var(--gat-text-muted);
326
+ margin-bottom: 2px;
327
+ }
328
+ .ga-toast-message {
329
+ color: var(--gat-text-soft);
330
+ font-size: 0.85rem;
331
+ word-break: break-word;
332
+ /* Clamp very long content so a toast can't fill the viewport. */
333
+ max-height: 40vh;
334
+ overflow-y: auto;
335
+ overscroll-behavior: contain;
336
+ }
337
+ .ga-toast-message a {
338
+ color: var(--gat-accent, var(--gat-info));
339
+ font-weight: 500;
340
+ text-decoration: underline; /* affordance beyond color (WCAG 1.4.1) */
341
+ }
342
+
343
+ /* When there's no header, let the message carry the toast on its own \u2014 but not
344
+ on filled toasts, whose message must stay light (specificity guard). */
345
+ .ga-toast:not([class*="-filled"]) .ga-toast-content > .ga-toast-body:only-child .ga-toast-message {
346
+ color: var(--gat-text);
347
+ }
348
+
349
+ /* ---------------- Actions ---------------- */
350
+ .ga-toast-actions {
351
+ display: flex;
352
+ gap: 8px;
353
+ margin-top: 2px;
354
+ }
355
+ .ga-toast-btn {
356
+ flex: 1;
357
+ padding: 8px 14px;
358
+ font: inherit;
359
+ font-size: 0.8125rem;
360
+ font-weight: 600;
361
+ border-radius: 9px;
362
+ border: 1px solid var(--gat-border);
363
+ background: var(--gat-chip);
364
+ color: var(--gat-text);
365
+ cursor: pointer;
366
+ transition: filter 0.15s ease, background 0.15s ease;
367
+ }
368
+ .ga-toast-btn:hover { filter: brightness(0.97); }
369
+ .ga-toast-btn:focus-visible {
370
+ outline: 2px solid currentColor;
371
+ outline-offset: 2px;
372
+ }
373
+ .ga-toast-btn-primary {
374
+ background: var(--gat-accent, var(--gat-primary));
375
+ border-color: transparent;
376
+ color: #fff;
377
+ }
378
+ .ga-toast-btn-primary:hover { filter: brightness(1.06); }
379
+
380
+ /* ---------------- Progress bar ---------------- */
381
+ .ga-toast-progress {
382
+ position: absolute;
383
+ left: 0;
384
+ bottom: 0;
385
+ height: 2px;
386
+ width: 100%;
387
+ transform: scaleX(1);
388
+ transform-origin: left center;
389
+ background: var(--gat-accent, var(--gat-info));
390
+ opacity: 0.4; /* a quiet hint, not a slab */
391
+ }
392
+ .ga-toast-progress-top { top: 0; bottom: auto; }
393
+
394
+ /* ---------------- Segmented steps ---------------- */
395
+ .ga-toast-steps { display: flex; gap: 4px; margin-top: 4px; }
396
+ .ga-toast-step {
397
+ flex: 1;
398
+ height: 3px;
399
+ border-radius: 999px;
400
+ background: var(--gat-chip);
401
+ }
402
+ .ga-toast-step-active { background: var(--gat-accent, var(--gat-info)); }
403
+
404
+ /* ---------------- Variants ---------------- */
405
+ /* Filled: solid accent, white ink, white stroke glyph directly on the fill.
406
+ Per-type fills are darkened where needed so white body text clears WCAG AA
407
+ (4.5:1); no top sheen (it dragged the top region under AA). */
408
+ .ga-toast[class*="-filled"] {
409
+ background: var(--gat-accent, var(--gat-info));
410
+ border-color: transparent;
411
+ color: #fff;
412
+ }
413
+ .ga-toast-success[class*="-filled"] { background: #15803d; } /* white \u2248 5.0:1 */
414
+ .ga-toast-primary[class*="-filled"] { background: #4f46e5; } /* white \u2248 6.3:1 */
415
+ .ga-toast[class*="-filled"] .ga-toast-title,
416
+ .ga-toast[class*="-filled"] .ga-toast-message,
417
+ .ga-toast[class*="-filled"] .ga-toast-meta,
418
+ .ga-toast[class*="-filled"] .ga-toast-icon,
419
+ .ga-toast[class*="-filled"] .ga-toast-close { color: #fff; }
420
+ .ga-toast[class*="-filled"] .ga-toast-close:hover { background: rgba(255, 255, 255, 0.2); }
421
+ /* Accent-colored bits would vanish on the accent fill \u2014 force them light. */
422
+ .ga-toast[class*="-filled"] .ga-toast-progress { background: #fff; opacity: 0.55; }
423
+ .ga-toast[class*="-filled"] .ga-toast-step { background: rgba(255, 255, 255, 0.28); }
424
+ .ga-toast[class*="-filled"] .ga-toast-step-active { background: #fff; }
425
+ .ga-toast[class*="-filled"] .ga-toast-status {
426
+ color: #fff;
427
+ background: rgba(255, 255, 255, 0.22);
428
+ }
429
+ .ga-toast[class*="-filled"] .ga-toast-message a { color: #fff; text-decoration: underline; }
430
+
431
+ /* Amber is too light for white text \u2014 filled warnings use dark ink throughout. */
432
+ .ga-toast-warning[class*="-filled"],
433
+ .ga-toast-warning[class*="-filled"] .ga-toast-title,
434
+ .ga-toast-warning[class*="-filled"] .ga-toast-message,
435
+ .ga-toast-warning[class*="-filled"] .ga-toast-meta,
436
+ .ga-toast-warning[class*="-filled"] .ga-toast-icon,
437
+ .ga-toast-warning[class*="-filled"] .ga-toast-close,
438
+ .ga-toast-warning[class*="-filled"] .ga-toast-message a {
439
+ color: #2e2100; /* \u2248 5.2:1 on the amber fill */
440
+ }
441
+ .ga-toast-warning[class*="-filled"] .ga-toast-progress { background: #2e2100; opacity: 0.6; }
442
+ .ga-toast-warning[class*="-filled"] .ga-toast-step { background: rgba(0, 0, 0, 0.18); }
443
+ .ga-toast-warning[class*="-filled"] .ga-toast-step-active { background: #2e2100; }
444
+ .ga-toast-warning[class*="-filled"] .ga-toast-status {
445
+ color: #2e2100;
446
+ background: rgba(0, 0, 0, 0.16);
447
+ }
448
+ .ga-toast-warning[class*="-filled"] .ga-toast-close:hover { background: rgba(0, 0, 0, 0.14); }
449
+
450
+ /* Light: a translucent accent wash over the (translucent) surface so glass still
451
+ shows through, plus an accent-tinted border. Secondary text is darkened to
452
+ keep AA over the tint. */
453
+ .ga-toast[class*="-light"] {
454
+ background: var(--gat-surface); /* stays translucent \u2192 glass blur still works */
455
+ background-image: linear-gradient(
456
+ 0deg,
457
+ color-mix(in srgb, var(--gat-accent, var(--gat-info)) 14%, transparent),
458
+ color-mix(in srgb, var(--gat-accent, var(--gat-info)) 14%, transparent)
459
+ );
460
+ border-color: color-mix(in srgb, var(--gat-accent, var(--gat-info)) 38%, var(--gat-border));
461
+ }
462
+ .ga-toast[class*="-light"] .ga-toast-meta { color: var(--gat-text-soft); }
463
+ .ga-toast[class*="-light"] .ga-toast-message a {
464
+ color: color-mix(in srgb, var(--gat-accent, var(--gat-info)) 55%, var(--gat-text));
465
+ text-decoration: underline;
466
+ }
467
+
468
+ /* ---------------- Sizes (density) ---------------- */
469
+ .ga-toast-xs { padding: 10px 13px; font-size: 0.78rem; }
470
+ .ga-toast-xs .ga-toast-icon svg { width: 1.05rem; height: 1.05rem; }
471
+ .ga-toast-xs .ga-toast-message { font-size: 0.78rem; }
472
+ .ga-toast-sm { padding: 12px 15px; }
473
+ .ga-toast-lg { padding: 18px 20px; }
474
+ .ga-toast-lg .ga-toast-title { font-size: 1rem; }
475
+ .ga-toast-lg .ga-toast-message { font-size: 0.95rem; }
476
+ .ga-toast-lg .ga-toast-icon svg { width: 1.4rem; height: 1.4rem; }
477
+ .ga-toast-xl { padding: 22px 24px; }
478
+ .ga-toast-xl .ga-toast-title { font-size: 1.125rem; }
479
+ .ga-toast-xl .ga-toast-message { font-size: 1.05rem; }
480
+ .ga-toast-xl .ga-toast-icon svg { width: 1.6rem; height: 1.6rem; }
481
+
482
+ /* ---------------- Compact ---------------- */
483
+ /* The card is already an icon \xB7 content \xB7 close row, so compact just tightens
484
+ the padding and vertically centres the single line. */
485
+ .ga-toast-compact {
486
+ align-items: center;
487
+ gap: 10px;
488
+ padding: 10px 14px;
489
+ }
490
+ .ga-toast-compact .ga-toast-content { gap: 2px; }
491
+ .ga-toast-compact .ga-toast-icon { margin-top: 0; }
492
+ .ga-toast-compact .ga-toast-close { align-self: center; margin-top: 0; }
493
+ .ga-toast-compact .ga-toast-steps { display: none; }
494
+ .ga-toast-compact .ga-toast-icon svg { width: 1.05rem; height: 1.05rem; }
495
+
496
+ /* ---------------- Live region (screen-reader only) ---------------- */
497
+ .ga-toast-live-region {
498
+ position: fixed;
499
+ width: 1px;
500
+ height: 1px;
501
+ padding: 0;
502
+ margin: -1px;
503
+ overflow: hidden;
504
+ clip: rect(0 0 0 0);
505
+ clip-path: inset(50%);
506
+ white-space: nowrap;
507
+ border: 0;
508
+ }
509
+
510
+ /* ---------------- Responsive ---------------- */
511
+ @media (max-width: 520px) {
512
+ .ga-toast-container {
513
+ --gat-width: auto;
514
+ left: var(--gat-edge);
515
+ right: var(--gat-edge);
516
+ transform: none;
517
+ }
518
+ .ga-toast-container-top-center,
519
+ .ga-toast-container-bottom-center,
520
+ .ga-toast-container-middle-center { transform: none; }
521
+ }
522
+
523
+ /* ---------------- RTL ---------------- */
524
+ [dir="rtl"] .ga-toast-progress { transform-origin: right center; }
525
+
526
+ /* ---------------- Reduced motion ---------------- */
527
+ @media (prefers-reduced-motion: reduce) {
528
+ .ga-toast {
529
+ transition: opacity 0.15s ease;
530
+ }
531
+ .ga-toast-spinner { animation-duration: 1.4s; }
532
+ }
533
+
534
+ /* ---------------- Reduced transparency ---------------- */
535
+ @media (prefers-reduced-transparency: reduce) {
536
+ .ga-toast-glass {
537
+ background: var(--gat-surface-solid);
538
+ backdrop-filter: none;
539
+ -webkit-backdrop-filter: none;
540
+ }
541
+ }
542
+
543
+ /* ---------------- Custom content (toast.custom) ---------------- */
544
+ .ga-toast-custom { min-width: 0; }
545
+ .ga-toast-has-custom { padding: 0; }
546
+ .ga-toast-has-custom .ga-toast-close {
547
+ position: absolute;
548
+ inset-block-start: 8px;
549
+ inset-inline-end: 8px;
550
+ z-index: 1;
551
+ }
552
+
553
+ @keyframes ga-toast-spin {
554
+ to { transform: rotate(360deg); }
555
+ }
556
+ `;
557
+
558
+ // src/index.ts
559
+ var canUseDOM = typeof window !== "undefined" && typeof document !== "undefined";
560
+ var PREFIX = "ga-toast";
561
+ var STYLE_ID = "ga-toasts-styles";
562
+ var PEEK = 14;
563
+ var STACK_GAP = 14;
564
+ var MAX_VISIBLE = 3;
565
+ var REMOVE_FALLBACK = 450;
566
+ var registry = /* @__PURE__ */ new Map();
567
+ var containers = /* @__PURE__ */ new Map();
568
+ var globalDefaults = {};
569
+ var logger = null;
570
+ var liveRegions = null;
571
+ var escapeBound = false;
572
+ var visibilityBound = false;
573
+ var idCounter = 0;
574
+ var activeModalId = null;
575
+ var backdropEl = null;
576
+ var S = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">';
577
+ var ICONS = {
578
+ success: `${S}<circle cx="12" cy="12" r="9" class="ga-ic-ring"/><path d="M7.75 12.5l2.75 2.75L16.5 9" class="ga-ic-draw"/></svg>`,
579
+ error: `${S}<circle cx="12" cy="12" r="9" class="ga-ic-ring"/><path d="M15 9l-6 6M9 9l6 6" class="ga-ic-draw"/></svg>`,
580
+ warning: `${S}<path d="M12 3.6 2.4 20.4h19.2L12 3.6z" class="ga-ic-ring"/><path d="M12 10v4" class="ga-ic-draw"/><path d="M12 17.4v.01"/></svg>`,
581
+ info: `${S}<circle cx="12" cy="12" r="9" class="ga-ic-ring"/><path d="M12 11v5" class="ga-ic-draw"/><path d="M12 7.6v.01"/></svg>`,
582
+ primary: `${S}<path d="M12 3.5l2.6 5.27 5.82.85-4.21 4.1.99 5.79L12 16.77 6.8 19.5l.99-5.79-4.21-4.1 5.82-.85L12 3.5z" class="ga-ic-draw"/></svg>`,
583
+ secondary: `${S}<path d="M6 9a6 6 0 0112 0c0 4.5 1.8 5.6 1.8 5.6H4.2S6 13.5 6 9z" class="ga-ic-draw"/><path d="M10 19a2 2 0 004 0"/></svg>`,
584
+ loading: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" aria-hidden="true" class="ga-toast-spinner"><circle cx="12" cy="12" r="9" opacity="0.25"/><path d="M12 3a9 9 0 019 9"/></svg>'
585
+ };
586
+ var CLOSE_ICON = '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2.25" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6 6 18"/></svg>';
587
+ var ASSERTIVE_TYPES = /* @__PURE__ */ new Set(["error", "warning"]);
588
+ function now() {
589
+ return typeof performance !== "undefined" ? performance.now() : Date.now();
590
+ }
591
+ function nextId() {
592
+ idCounter += 1;
593
+ const rand = Math.random().toString(36).slice(2, 8);
594
+ return `${PREFIX}-${idCounter}-${rand}`;
595
+ }
596
+ function log(event, payload) {
597
+ if (logger) {
598
+ try {
599
+ logger(event, payload);
600
+ } catch {
601
+ }
602
+ }
603
+ }
604
+ function prefersReducedMotion() {
605
+ return canUseDOM && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
606
+ }
607
+ function injectStyles() {
608
+ if (!canUseDOM) return;
609
+ if (document.getElementById(STYLE_ID)) return;
610
+ const el2 = document.createElement("style");
611
+ el2.id = STYLE_ID;
612
+ el2.textContent = css;
613
+ document.head.appendChild(el2);
614
+ }
615
+ function ensureLiveRegions() {
616
+ if (!canUseDOM) return;
617
+ if (liveRegions && liveRegions.polite.isConnected && liveRegions.assertive.isConnected)
618
+ return;
619
+ const make = (politeness) => {
620
+ const region = document.createElement("div");
621
+ region.className = "ga-toast-live-region";
622
+ region.setAttribute("aria-live", politeness);
623
+ region.setAttribute("aria-atomic", "true");
624
+ region.setAttribute("role", politeness === "assertive" ? "alert" : "status");
625
+ document.body.appendChild(region);
626
+ return region;
627
+ };
628
+ liveRegions = { polite: make("polite"), assertive: make("assertive") };
629
+ }
630
+ function announce(text, assertive) {
631
+ if (!text) return;
632
+ ensureLiveRegions();
633
+ if (!liveRegions) return;
634
+ const region = assertive ? liveRegions.assertive : liveRegions.polite;
635
+ region.textContent = "";
636
+ window.setTimeout(() => {
637
+ region.textContent = text;
638
+ }, 50);
639
+ }
640
+ var POSITIONS = [
641
+ "top-start",
642
+ "top-center",
643
+ "top-end",
644
+ "middle-start",
645
+ "middle-center",
646
+ "middle-end",
647
+ "bottom-start",
648
+ "bottom-center",
649
+ "bottom-end"
650
+ ];
651
+ function verticalDir(position) {
652
+ return position.startsWith("bottom") ? -1 : 1;
653
+ }
654
+ function isRTL() {
655
+ if (!canUseDOM) return false;
656
+ const dir = document.documentElement.getAttribute("dir") || document.body && document.body.getAttribute("dir");
657
+ return dir === "rtl";
658
+ }
659
+ function offscreenTransform(position) {
660
+ const inFromLeft = "translateX(-115%)";
661
+ const inFromRight = "translateX(115%)";
662
+ const rtl = isRTL();
663
+ if (position.endsWith("start")) return rtl ? inFromRight : inFromLeft;
664
+ if (position.endsWith("end")) return rtl ? inFromLeft : inFromRight;
665
+ return verticalDir(position) === 1 ? "translateY(-140%)" : "translateY(140%)";
666
+ }
667
+ function getContainer(position) {
668
+ const existing = containers.get(position);
669
+ if (existing && existing.isConnected) return existing;
670
+ const container = document.createElement("div");
671
+ container.className = `ga-toast-container ga-toast-container-${position}`;
672
+ container.setAttribute("data-ga-position", position);
673
+ container.setAttribute("role", "region");
674
+ container.setAttribute("aria-label", "Notifications");
675
+ const expand = () => setExpanded(container, true);
676
+ const collapse = () => setExpanded(container, false);
677
+ container.addEventListener("pointerenter", expand);
678
+ container.addEventListener("pointerleave", collapse);
679
+ container.addEventListener("focusin", expand);
680
+ container.addEventListener("focusout", (e) => {
681
+ if (!container.contains(e.relatedTarget)) collapse();
682
+ });
683
+ document.body.appendChild(container);
684
+ containers.set(position, container);
685
+ return container;
686
+ }
687
+ function setExpanded(container, expanded) {
688
+ if (container.dataset.gaExpanded === "true" === expanded) return;
689
+ container.dataset.gaExpanded = expanded ? "true" : "false";
690
+ layout(container);
691
+ }
692
+ function instancesIn(container) {
693
+ return Array.from(container.children).map((child) => registry.get(child.id)).filter((inst) => !!inst);
694
+ }
695
+ function layout(container) {
696
+ const all = instancesIn(container);
697
+ const position = container.dataset.gaPosition || "top-end";
698
+ const dir = verticalDir(position);
699
+ const expanded = container.dataset.gaExpanded === "true";
700
+ const reduce = prefersReducedMotion();
701
+ const front = all.slice().reverse();
702
+ container.style.setProperty("--ga-toasts-count", String(front.length));
703
+ let cumulative = 0;
704
+ front.forEach((inst, frontIndex) => {
705
+ if (inst.closing) return;
706
+ if (!inst.el.dataset.gaMounted) {
707
+ cumulative += inst.height + STACK_GAP;
708
+ return;
709
+ }
710
+ let translateY;
711
+ let scale;
712
+ let opacity;
713
+ if (expanded || reduce) {
714
+ translateY = dir * cumulative;
715
+ scale = 1;
716
+ opacity = 1;
717
+ } else {
718
+ translateY = dir * frontIndex * PEEK;
719
+ scale = Math.max(1 - frontIndex * 0.05, 0.85);
720
+ opacity = frontIndex >= MAX_VISIBLE ? 0 : 1;
721
+ }
722
+ const swipe = inst.swipeX ? ` translateX(${inst.swipeX}px)` : "";
723
+ const ty = position.startsWith("middle") ? `calc(-50% + ${translateY}px)` : `${translateY}px`;
724
+ inst.el.style.transform = `translateY(${ty})${swipe} scale(${scale})`;
725
+ inst.el.style.opacity = inst.swipeX ? String(swipeOpacity(inst.swipeX)) : String(opacity);
726
+ inst.el.style.zIndex = String(1e3 + front.length - frontIndex);
727
+ inst.el.style.pointerEvents = opacity === 0 ? "none" : "auto";
728
+ inst.el.setAttribute("aria-hidden", opacity === 0 ? "true" : "false");
729
+ cumulative += inst.height + STACK_GAP;
730
+ });
731
+ }
732
+ function swipeOpacity(dx) {
733
+ return Math.max(0.15, 1 - Math.abs(dx) / 220);
734
+ }
735
+ function measure(inst) {
736
+ const h = inst.el.offsetHeight;
737
+ if (h > 0) inst.height = h;
738
+ }
739
+ function resolveOptions(options) {
740
+ const merged = {
741
+ type: "info",
742
+ duration: 5e3,
743
+ closable: true,
744
+ position: "top-end",
745
+ animation: "slide",
746
+ swipeToClose: true,
747
+ closeOnEscape: true,
748
+ progress: true,
749
+ progressPosition: "bottom",
750
+ pauseOnHover: true,
751
+ pauseOnPageHidden: true,
752
+ autoIcon: true,
753
+ glassmorphism: true,
754
+ html: false,
755
+ ...globalDefaults,
756
+ ...options
757
+ };
758
+ merged.position = merged.position || "top-end";
759
+ if (!POSITIONS.includes(merged.position)) merged.position = "top-end";
760
+ return merged;
761
+ }
762
+ function createCloseButton() {
763
+ const close2 = document.createElement("button");
764
+ close2.type = "button";
765
+ close2.className = "ga-toast-close";
766
+ close2.setAttribute("aria-label", "Close notification");
767
+ close2.setAttribute("data-ga-close", "");
768
+ close2.innerHTML = CLOSE_ICON;
769
+ return close2;
770
+ }
771
+ function appendProgress(el2, opts, type) {
772
+ if (!opts.progress || (opts.duration || 0) <= 0 || opts.progressPosition === "none")
773
+ return null;
774
+ const p = document.createElement("div");
775
+ p.className = `ga-toast-progress ga-toast-progress-${type}` + (opts.progressPosition === "top" ? " ga-toast-progress-top" : "");
776
+ el2.appendChild(p);
777
+ return p;
778
+ }
779
+ function buildToastElement(opts, id) {
780
+ const type = opts.type || "info";
781
+ const el2 = document.createElement("div");
782
+ el2.id = id;
783
+ el2.className = [
784
+ "ga-toast",
785
+ `ga-toast-${type}`,
786
+ opts.size ? `ga-toast-${opts.size}` : "",
787
+ opts.variant ? `ga-toast-${type}-${opts.variant}` : "",
788
+ opts.animation || "",
789
+ opts.compact ? "ga-toast-compact" : "",
790
+ opts.glassmorphism ? "ga-toast-glass" : "",
791
+ type === "loading" ? "ga-toast-loading" : "",
792
+ opts.className || ""
793
+ ].filter(Boolean).join(" ");
794
+ const role = opts.role || (ASSERTIVE_TYPES.has(type) ? "alert" : "status");
795
+ el2.setAttribute("role", role);
796
+ el2.setAttribute("aria-live", "off");
797
+ el2.setAttribute("data-ga-type", type);
798
+ if (role === "alertdialog" || role === "dialog") {
799
+ const label = [opts.title, opts.message].filter(Boolean).join(". ");
800
+ if (label) el2.setAttribute("aria-label", label);
801
+ }
802
+ if (opts.content != null) {
803
+ el2.classList.add("ga-toast-has-custom");
804
+ const wrap = document.createElement("div");
805
+ wrap.className = "ga-toast-custom";
806
+ const c = typeof opts.content === "function" ? opts.content() : opts.content;
807
+ if (typeof c === "string") wrap.innerHTML = c;
808
+ else if (c instanceof HTMLElement) wrap.appendChild(c);
809
+ el2.appendChild(wrap);
810
+ if (opts.closable) el2.appendChild(createCloseButton());
811
+ return { el: el2, progressEl: appendProgress(el2, opts, type) };
812
+ }
813
+ if (opts.avatar) {
814
+ const avatar = document.createElement("img");
815
+ avatar.className = "ga-toast-avatar";
816
+ avatar.src = opts.avatar;
817
+ avatar.alt = opts.avatarAlt || opts.title || "Notification";
818
+ el2.appendChild(avatar);
819
+ }
820
+ let iconHtml = null;
821
+ if (opts.icon) iconHtml = String(opts.icon);
822
+ else if (opts.icon !== false && opts.autoIcon !== false && !opts.avatar)
823
+ iconHtml = ICONS[type] || ICONS.info;
824
+ if (iconHtml) {
825
+ const icon = document.createElement("div");
826
+ icon.className = "ga-toast-icon";
827
+ icon.innerHTML = iconHtml;
828
+ el2.appendChild(icon);
829
+ }
830
+ const content = document.createElement("div");
831
+ content.className = "ga-toast-content";
832
+ if (opts.title || opts.showStatus || opts.unread) {
833
+ const header = document.createElement("div");
834
+ header.className = "ga-toast-header";
835
+ if (opts.unread) {
836
+ const dot = document.createElement("span");
837
+ dot.className = "ga-toast-unread-dot";
838
+ header.appendChild(dot);
839
+ }
840
+ if (opts.title) {
841
+ const title = document.createElement("div");
842
+ title.className = "ga-toast-title" + (opts.truncateTitle ? " ga-toast-title-truncate" : "");
843
+ title.textContent = opts.title;
844
+ header.appendChild(title);
845
+ }
846
+ if (opts.showStatus) {
847
+ const status = document.createElement("span");
848
+ status.className = "ga-toast-status";
849
+ const label = String(opts.statusText || type);
850
+ status.textContent = label.charAt(0).toUpperCase() + label.slice(1);
851
+ header.appendChild(status);
852
+ }
853
+ content.appendChild(header);
854
+ }
855
+ if (opts.message || opts.meta) {
856
+ const body = document.createElement("div");
857
+ body.className = "ga-toast-body";
858
+ if (opts.meta) {
859
+ const meta = document.createElement("div");
860
+ meta.className = "ga-toast-meta";
861
+ meta.textContent = opts.meta;
862
+ body.appendChild(meta);
863
+ }
864
+ if (opts.message) {
865
+ const message = document.createElement("div");
866
+ message.className = "ga-toast-message";
867
+ if (opts.html) message.innerHTML = opts.message;
868
+ else message.textContent = opts.message;
869
+ body.appendChild(message);
870
+ }
871
+ content.appendChild(body);
872
+ }
873
+ if (opts.actions && opts.actions.length && !opts.compact) {
874
+ const actions = document.createElement("div");
875
+ actions.className = "ga-toast-actions";
876
+ for (const action of opts.actions) {
877
+ const btn = document.createElement("button");
878
+ btn.type = "button";
879
+ btn.className = [
880
+ "ga-toast-btn",
881
+ action.className || action.class || "ga-toast-btn-secondary"
882
+ ].join(" ");
883
+ btn.textContent = action.text;
884
+ btn.setAttribute("data-ga-action", "");
885
+ actions.appendChild(btn);
886
+ }
887
+ content.appendChild(actions);
888
+ }
889
+ if (opts.steps && opts.steps > 1) {
890
+ const steps = document.createElement("div");
891
+ steps.className = "ga-toast-steps";
892
+ const active = Math.min(opts.steps, Math.max(1, opts.currentStep || 1));
893
+ for (let i = 1; i <= opts.steps; i += 1) {
894
+ const step = document.createElement("div");
895
+ step.className = "ga-toast-step" + (i <= active ? " ga-toast-step-active" : "");
896
+ steps.appendChild(step);
897
+ }
898
+ content.appendChild(steps);
899
+ }
900
+ el2.appendChild(content);
901
+ if (opts.closable) el2.appendChild(createCloseButton());
902
+ return { el: el2, progressEl: appendProgress(el2, opts, type) };
903
+ }
904
+ function startTimer(inst) {
905
+ const { timer } = inst;
906
+ if (timer.duration <= 0 || timer.remaining <= 0) return;
907
+ timer.paused = false;
908
+ timer.startedAt = now();
909
+ timer.handle = setTimeout(() => close(inst.id), timer.remaining);
910
+ animateProgress(inst, timer.remaining);
911
+ }
912
+ function pauseTimer(inst) {
913
+ const { timer } = inst;
914
+ if (timer.paused || timer.handle == null) return;
915
+ clearTimeout(timer.handle);
916
+ timer.handle = null;
917
+ timer.paused = true;
918
+ const elapsed = now() - timer.startedAt;
919
+ timer.remaining = Math.max(0, timer.remaining - elapsed);
920
+ freezeProgress(inst);
921
+ }
922
+ function resumeTimer(inst) {
923
+ if (!inst.timer.paused) return;
924
+ startTimer(inst);
925
+ }
926
+ function animateProgress(inst, ms) {
927
+ const bar = inst.progressEl;
928
+ if (!bar) return;
929
+ const fraction = inst.timer.duration > 0 ? ms / inst.timer.duration : 0;
930
+ bar.style.transition = "none";
931
+ bar.style.transform = `scaleX(${fraction})`;
932
+ void bar.offsetWidth;
933
+ bar.style.transition = `transform ${ms}ms linear`;
934
+ bar.style.transform = "scaleX(0)";
935
+ }
936
+ function freezeProgress(inst) {
937
+ const bar = inst.progressEl;
938
+ if (!bar) return;
939
+ const fraction = inst.timer.duration > 0 ? inst.timer.remaining / inst.timer.duration : 0;
940
+ bar.style.transition = "none";
941
+ bar.style.transform = `scaleX(${fraction})`;
942
+ }
943
+ function attachHandlers(inst, opts) {
944
+ const { el: el2, controller } = inst;
945
+ const signal = controller.signal;
946
+ el2.addEventListener(
947
+ "click",
948
+ (e) => {
949
+ const target = e.target;
950
+ if (target.closest("[data-ga-close]")) {
951
+ e.preventDefault();
952
+ close(inst.id);
953
+ return;
954
+ }
955
+ const actionBtn = target.closest("[data-ga-action]");
956
+ if (actionBtn && opts.actions) {
957
+ const idx = Array.from(
958
+ actionBtn.parentElement?.children || []
959
+ ).indexOf(actionBtn);
960
+ const action = opts.actions[idx];
961
+ if (action) {
962
+ const handler = action.onClick || action.click;
963
+ if (handler) handler(e, inst.handle);
964
+ if (action.closeOnClick !== false) close(inst.id);
965
+ }
966
+ return;
967
+ }
968
+ if (opts.clickToClose && !target.closest(".ga-toast-actions")) {
969
+ close(inst.id);
970
+ }
971
+ },
972
+ { signal }
973
+ );
974
+ if (opts.pauseOnHover) {
975
+ el2.addEventListener("pointerenter", () => pauseTimer(inst), { signal });
976
+ el2.addEventListener("pointerleave", () => resumeTimer(inst), { signal });
977
+ el2.addEventListener("focusin", () => pauseTimer(inst), { signal });
978
+ el2.addEventListener("focusout", () => resumeTimer(inst), { signal });
979
+ }
980
+ if (opts.swipeToClose) attachSwipe(inst);
981
+ if (opts.modal) {
982
+ el2.addEventListener(
983
+ "keydown",
984
+ (e) => {
985
+ if (e.key !== "Tab") return;
986
+ const focusables = el2.querySelectorAll(
987
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
988
+ );
989
+ if (!focusables.length) return;
990
+ const first = focusables[0];
991
+ const last = focusables[focusables.length - 1];
992
+ const active = document.activeElement;
993
+ if (e.shiftKey && active === first) {
994
+ e.preventDefault();
995
+ last.focus();
996
+ } else if (!e.shiftKey && active === last) {
997
+ e.preventDefault();
998
+ first.focus();
999
+ }
1000
+ },
1001
+ { signal }
1002
+ );
1003
+ }
1004
+ }
1005
+ function attachSwipe(inst) {
1006
+ const { el: el2, controller } = inst;
1007
+ const signal = controller.signal;
1008
+ const THRESHOLD = 70;
1009
+ let dragging = false;
1010
+ let startX = 0;
1011
+ let startY = 0;
1012
+ el2.addEventListener(
1013
+ "pointerdown",
1014
+ (e) => {
1015
+ if (e.pointerType === "mouse" && e.button !== 0) return;
1016
+ if (e.target.closest("button, a, input, textarea, select"))
1017
+ return;
1018
+ dragging = true;
1019
+ startX = e.clientX;
1020
+ startY = e.clientY;
1021
+ pauseTimer(inst);
1022
+ try {
1023
+ el2.setPointerCapture(e.pointerId);
1024
+ } catch {
1025
+ }
1026
+ },
1027
+ { signal }
1028
+ );
1029
+ el2.addEventListener(
1030
+ "pointermove",
1031
+ (e) => {
1032
+ if (!dragging) return;
1033
+ const dx = e.clientX - startX;
1034
+ const dy = e.clientY - startY;
1035
+ if (Math.abs(dx) < 6 || Math.abs(dx) < Math.abs(dy)) return;
1036
+ e.preventDefault();
1037
+ inst.swipeX = dx;
1038
+ const container = el2.parentElement;
1039
+ if (container) layout(container);
1040
+ },
1041
+ { signal }
1042
+ );
1043
+ const end = (e) => {
1044
+ if (!dragging) return;
1045
+ dragging = false;
1046
+ const dx = e.clientX - startX;
1047
+ if (Math.abs(dx) > THRESHOLD) {
1048
+ const dir = dx > 0 ? 1 : -1;
1049
+ inst.el.style.transition = "transform 0.2s ease, opacity 0.2s ease";
1050
+ inst.el.style.transform = `translateX(${dir * 400}px)`;
1051
+ inst.el.style.opacity = "0";
1052
+ close(inst.id);
1053
+ } else {
1054
+ inst.swipeX = 0;
1055
+ const container = el2.parentElement;
1056
+ if (container) layout(container);
1057
+ resumeTimer(inst);
1058
+ }
1059
+ };
1060
+ el2.addEventListener("pointerup", end, { signal });
1061
+ el2.addEventListener("pointercancel", end, { signal });
1062
+ }
1063
+ function bindEscape() {
1064
+ if (escapeBound || !canUseDOM) return;
1065
+ escapeBound = true;
1066
+ document.addEventListener("keydown", (e) => {
1067
+ if (e.key !== "Escape") return;
1068
+ let newest = null;
1069
+ for (const inst of registry.values()) {
1070
+ if (inst.closing) continue;
1071
+ if (inst.opts.closeOnEscape === false || !inst.opts.closable) continue;
1072
+ newest = inst;
1073
+ }
1074
+ if (newest) close(newest.id);
1075
+ });
1076
+ }
1077
+ function bindVisibility() {
1078
+ if (visibilityBound || !canUseDOM || typeof document.addEventListener !== "function")
1079
+ return;
1080
+ visibilityBound = true;
1081
+ document.addEventListener("visibilitychange", () => {
1082
+ const hidden = document.hidden;
1083
+ for (const inst of registry.values()) {
1084
+ if (inst.closing || inst.opts.pauseOnPageHidden === false) continue;
1085
+ if (hidden) {
1086
+ pauseTimer(inst);
1087
+ } else if (!(typeof inst.el.matches === "function" && inst.el.matches(":hover"))) {
1088
+ resumeTimer(inst);
1089
+ }
1090
+ }
1091
+ });
1092
+ }
1093
+ function showBackdrop() {
1094
+ if (!canUseDOM) return;
1095
+ if (!backdropEl) {
1096
+ backdropEl = document.createElement("div");
1097
+ backdropEl.className = "ga-toast-backdrop";
1098
+ }
1099
+ if (!backdropEl.isConnected) document.body.appendChild(backdropEl);
1100
+ const bd = backdropEl;
1101
+ requestAnimationFrame(() => bd.classList.add("ga-toast-backdrop-show"));
1102
+ }
1103
+ function hideBackdrop() {
1104
+ if (!backdropEl) return;
1105
+ const bd = backdropEl;
1106
+ bd.classList.remove("ga-toast-backdrop-show");
1107
+ setTimeout(() => {
1108
+ if (activeModalId == null && bd.parentNode) bd.remove();
1109
+ }, 220);
1110
+ }
1111
+ function noopHandle(id) {
1112
+ const handle = {
1113
+ id,
1114
+ el: canUseDOM ? document.createElement("div") : {},
1115
+ update: () => handle,
1116
+ close: () => {
1117
+ }
1118
+ };
1119
+ return handle;
1120
+ }
1121
+ function show(options = {}) {
1122
+ if (!canUseDOM) return noopHandle(options.id || nextId());
1123
+ const opts = resolveOptions(options);
1124
+ if (opts.id && registry.has(opts.id)) {
1125
+ return update(opts.id, options) || noopHandle(opts.id);
1126
+ }
1127
+ if (opts.modal) {
1128
+ if (activeModalId && registry.has(activeModalId)) {
1129
+ return registry.get(activeModalId).handle;
1130
+ }
1131
+ opts.swipeToClose = false;
1132
+ opts.closeOnEscape = false;
1133
+ opts.moveFocus = true;
1134
+ }
1135
+ injectStyles();
1136
+ bindEscape();
1137
+ bindVisibility();
1138
+ const id = opts.id || nextId();
1139
+ const { el: el2, progressEl } = buildToastElement(opts, id);
1140
+ const controller = new AbortController();
1141
+ const timer = {
1142
+ duration: opts.duration || 0,
1143
+ remaining: opts.duration || 0,
1144
+ startedAt: 0,
1145
+ handle: null,
1146
+ paused: false
1147
+ };
1148
+ const handle = {
1149
+ id,
1150
+ el: el2,
1151
+ update: (o) => update(id, o) ? handle : handle,
1152
+ close: () => close(id)
1153
+ };
1154
+ const inst = {
1155
+ id,
1156
+ el: el2,
1157
+ handle,
1158
+ opts,
1159
+ controller,
1160
+ progressEl,
1161
+ timer,
1162
+ height: 0,
1163
+ closing: false,
1164
+ swipeX: 0,
1165
+ resizeObserver: null,
1166
+ prevFocus: opts.moveFocus ? document.activeElement : null
1167
+ };
1168
+ registry.set(id, inst);
1169
+ if (opts.modal) {
1170
+ activeModalId = id;
1171
+ el2.setAttribute("aria-modal", "true");
1172
+ showBackdrop();
1173
+ }
1174
+ const container = getContainer(opts.position);
1175
+ el2.style.transform = offscreenTransform(opts.position);
1176
+ el2.style.opacity = "0";
1177
+ container.appendChild(el2);
1178
+ attachHandlers(inst, opts);
1179
+ if (typeof ResizeObserver !== "undefined") {
1180
+ inst.resizeObserver = new ResizeObserver(() => {
1181
+ measure(inst);
1182
+ const parent = el2.parentElement;
1183
+ if (parent) layout(parent);
1184
+ });
1185
+ inst.resizeObserver.observe(el2);
1186
+ }
1187
+ requestAnimationFrame(() => {
1188
+ requestAnimationFrame(() => {
1189
+ if (!registry.has(id)) return;
1190
+ measure(inst);
1191
+ el2.dataset.gaMounted = "true";
1192
+ el2.style.opacity = "1";
1193
+ layout(container);
1194
+ startTimer(inst);
1195
+ if (opts.moveFocus) {
1196
+ const target = el2.querySelector("[data-ga-action]") || el2;
1197
+ if (target === el2) el2.tabIndex = -1;
1198
+ try {
1199
+ target.focus({ preventScroll: true });
1200
+ } catch {
1201
+ }
1202
+ }
1203
+ });
1204
+ });
1205
+ if (!opts.modal && opts.role !== "alertdialog") {
1206
+ announce(
1207
+ [opts.title, opts.message].filter(Boolean).join(". "),
1208
+ ASSERTIVE_TYPES.has(opts.type || "info")
1209
+ );
1210
+ }
1211
+ if (opts.onShow) {
1212
+ try {
1213
+ opts.onShow(handle);
1214
+ } catch (err) {
1215
+ log("toast:onShow:error", err);
1216
+ }
1217
+ }
1218
+ log("toast:show", { id, type: opts.type });
1219
+ return handle;
1220
+ }
1221
+ function close(target) {
1222
+ if (!target) return;
1223
+ const id = typeof target === "string" ? normalizeId(target) : target.id;
1224
+ const inst = registry.get(id);
1225
+ if (!inst || inst.closing) return;
1226
+ inst.closing = true;
1227
+ if (inst.timer.handle != null) {
1228
+ clearTimeout(inst.timer.handle);
1229
+ inst.timer.handle = null;
1230
+ }
1231
+ inst.controller.abort();
1232
+ inst.resizeObserver?.disconnect();
1233
+ const { el: el2 } = inst;
1234
+ const container = el2.parentElement;
1235
+ if (!inst.swipeX) {
1236
+ el2.style.transition = "";
1237
+ el2.classList.add("ga-toast-hide");
1238
+ el2.style.transform = offscreenTransform(inst.opts.position);
1239
+ el2.style.opacity = "0";
1240
+ }
1241
+ let removed = false;
1242
+ const finalize = () => {
1243
+ if (removed) return;
1244
+ removed = true;
1245
+ el2.removeEventListener("transitionend", onEnd);
1246
+ registry.delete(id);
1247
+ el2.remove();
1248
+ if (activeModalId === id) {
1249
+ activeModalId = null;
1250
+ hideBackdrop();
1251
+ }
1252
+ if (container) layout(container);
1253
+ const prev = inst.prevFocus;
1254
+ if (prev && typeof prev.focus === "function" && document.contains(prev)) {
1255
+ try {
1256
+ prev.focus({ preventScroll: true });
1257
+ } catch {
1258
+ }
1259
+ }
1260
+ if (inst.opts.onClose) {
1261
+ try {
1262
+ inst.opts.onClose(inst.handle);
1263
+ } catch (err) {
1264
+ log("toast:onClose:error", err);
1265
+ }
1266
+ }
1267
+ log("toast:close", { id });
1268
+ };
1269
+ const onEnd = (e) => {
1270
+ if (e.target === el2 && (e.propertyName === "transform" || e.propertyName === "opacity"))
1271
+ finalize();
1272
+ };
1273
+ el2.addEventListener("transitionend", onEnd);
1274
+ setTimeout(finalize, REMOVE_FALLBACK);
1275
+ }
1276
+ function normalizeId(idOrSelector) {
1277
+ return idOrSelector.startsWith("#") ? idOrSelector.slice(1) : idOrSelector;
1278
+ }
1279
+ function update(target, options) {
1280
+ const id = typeof target === "string" ? normalizeId(target) : target.id;
1281
+ const inst = registry.get(id);
1282
+ if (!inst) {
1283
+ log("toast:update:not-found", id);
1284
+ return null;
1285
+ }
1286
+ const opts = inst.opts;
1287
+ if (options.title !== void 0) {
1288
+ let title = inst.el.querySelector(".ga-toast-title");
1289
+ if (!title && options.title) {
1290
+ const content = inst.el.querySelector(".ga-toast-content");
1291
+ if (content) {
1292
+ let header = content.querySelector(".ga-toast-header");
1293
+ if (!header) {
1294
+ header = document.createElement("div");
1295
+ header.className = "ga-toast-header";
1296
+ content.insertBefore(header, content.firstChild);
1297
+ }
1298
+ title = document.createElement("div");
1299
+ title.className = "ga-toast-title";
1300
+ header.appendChild(title);
1301
+ }
1302
+ }
1303
+ if (title) title.textContent = options.title;
1304
+ opts.title = options.title;
1305
+ }
1306
+ if (options.message !== void 0) {
1307
+ const useHtml = options.html ?? opts.html;
1308
+ let message = inst.el.querySelector(".ga-toast-message");
1309
+ if (!message && options.message) {
1310
+ let body = inst.el.querySelector(".ga-toast-body");
1311
+ if (!body) {
1312
+ body = document.createElement("div");
1313
+ body.className = "ga-toast-body";
1314
+ const content = inst.el.querySelector(".ga-toast-content");
1315
+ const anchor = content?.querySelector(".ga-toast-actions, .ga-toast-steps");
1316
+ if (anchor) content.insertBefore(body, anchor);
1317
+ else content?.appendChild(body);
1318
+ }
1319
+ message = document.createElement("div");
1320
+ message.className = "ga-toast-message";
1321
+ body.appendChild(message);
1322
+ }
1323
+ if (message) {
1324
+ if (useHtml) message.innerHTML = options.message;
1325
+ else message.textContent = options.message;
1326
+ }
1327
+ opts.message = options.message;
1328
+ }
1329
+ if (options.type && options.type !== opts.type) {
1330
+ const prev = opts.type;
1331
+ inst.el.classList.remove(`ga-toast-${prev}`, "ga-toast-loading");
1332
+ inst.el.classList.add(`ga-toast-${options.type}`);
1333
+ inst.el.setAttribute("data-ga-type", options.type);
1334
+ if (options.type === "loading") inst.el.classList.add("ga-toast-loading");
1335
+ if (inst.progressEl) {
1336
+ inst.progressEl.className = inst.progressEl.className.replace(
1337
+ /ga-toast-progress-\w+/,
1338
+ `ga-toast-progress-${options.type}`
1339
+ );
1340
+ }
1341
+ const iconEl = inst.el.querySelector(".ga-toast-icon");
1342
+ if (iconEl && opts.autoIcon !== false && !options.icon)
1343
+ iconEl.innerHTML = ICONS[options.type] || ICONS.info;
1344
+ opts.type = options.type;
1345
+ }
1346
+ if (options.icon !== void 0) {
1347
+ let iconEl = inst.el.querySelector(".ga-toast-icon");
1348
+ if (options.icon) {
1349
+ if (!iconEl) {
1350
+ iconEl = document.createElement("div");
1351
+ iconEl.className = "ga-toast-icon";
1352
+ const content = inst.el.querySelector(".ga-toast-content");
1353
+ inst.el.insertBefore(iconEl, content);
1354
+ }
1355
+ iconEl.innerHTML = String(options.icon);
1356
+ } else if (iconEl) {
1357
+ iconEl.remove();
1358
+ }
1359
+ }
1360
+ if (options.closable !== void 0) {
1361
+ const existing = inst.el.querySelector(":scope > .ga-toast-close");
1362
+ if (options.closable && !existing) {
1363
+ inst.el.insertBefore(createCloseButton(), inst.progressEl || null);
1364
+ } else if (!options.closable && existing) {
1365
+ existing.remove();
1366
+ }
1367
+ opts.closable = options.closable;
1368
+ }
1369
+ if (options.duration !== void 0) {
1370
+ if (inst.timer.handle != null) clearTimeout(inst.timer.handle);
1371
+ inst.timer.duration = options.duration;
1372
+ inst.timer.remaining = options.duration;
1373
+ opts.duration = options.duration;
1374
+ if (options.duration > 0 && el(inst).dataset.gaMounted) startTimer(inst);
1375
+ }
1376
+ const parent = inst.el.parentElement;
1377
+ if (parent) layout(parent);
1378
+ log("toast:update", { id });
1379
+ return inst.handle;
1380
+ }
1381
+ function el(inst) {
1382
+ return inst.el;
1383
+ }
1384
+ function closeAll() {
1385
+ for (const id of Array.from(registry.keys())) close(id);
1386
+ }
1387
+ function clear(type) {
1388
+ if (!type) return closeAll();
1389
+ for (const inst of Array.from(registry.values()))
1390
+ if (inst.opts.type === type) close(inst.id);
1391
+ }
1392
+ function getCount(type) {
1393
+ if (!type) return registry.size;
1394
+ let n = 0;
1395
+ for (const inst of registry.values()) if (inst.opts.type === type) n += 1;
1396
+ return n;
1397
+ }
1398
+ function exists(id) {
1399
+ return registry.has(normalizeId(id));
1400
+ }
1401
+ function get(id) {
1402
+ return registry.get(normalizeId(id))?.el || null;
1403
+ }
1404
+ function typed(type, fallbackDuration) {
1405
+ return (message, options = {}) => show({ ...options, type, message, duration: options.duration ?? fallbackDuration });
1406
+ }
1407
+ var success = typed("success", 5e3);
1408
+ var error = typed("error", 8e3);
1409
+ var warning = typed("warning", 6e3);
1410
+ var info = typed("info", 4e3);
1411
+ function loading(message = "Loading\u2026", options = {}) {
1412
+ return show({
1413
+ closable: false,
1414
+ ...options,
1415
+ type: "loading",
1416
+ message,
1417
+ duration: 0,
1418
+ progress: false
1419
+ });
1420
+ }
1421
+ function confirm(message, options = {}) {
1422
+ const { confirmText, cancelText, onConfirm, onCancel, ...rest } = options;
1423
+ return show({
1424
+ type: "warning",
1425
+ duration: 0,
1426
+ closable: false,
1427
+ swipeToClose: false,
1428
+ role: "alertdialog",
1429
+ moveFocus: true,
1430
+ modal: true,
1431
+ position: "middle-center",
1432
+ ...rest,
1433
+ message,
1434
+ actions: [
1435
+ {
1436
+ text: cancelText || "Cancel",
1437
+ className: "ga-toast-btn-secondary",
1438
+ onClick: () => onCancel?.()
1439
+ },
1440
+ {
1441
+ text: confirmText || "Confirm",
1442
+ className: "ga-toast-btn-primary",
1443
+ onClick: () => onConfirm?.()
1444
+ }
1445
+ ]
1446
+ });
1447
+ }
1448
+ function promise(input, messages, options = {}) {
1449
+ const p = typeof input === "function" ? input() : input;
1450
+ const handle = loading(messages.loading, options);
1451
+ return p.then(
1452
+ (value) => {
1453
+ handle.update({
1454
+ type: "success",
1455
+ message: typeof messages.success === "function" ? messages.success(value) : messages.success,
1456
+ duration: options.duration ?? 5e3,
1457
+ closable: true
1458
+ });
1459
+ return value;
1460
+ },
1461
+ (err) => {
1462
+ handle.update({
1463
+ type: "error",
1464
+ message: typeof messages.error === "function" ? messages.error(err) : messages.error,
1465
+ duration: options.duration ?? 8e3,
1466
+ closable: true
1467
+ });
1468
+ throw err;
1469
+ }
1470
+ );
1471
+ }
1472
+ function setDefaults(defaults) {
1473
+ globalDefaults = { ...globalDefaults, ...defaults };
1474
+ }
1475
+ function setLogger(fn) {
1476
+ logger = typeof fn === "function" ? fn : null;
1477
+ }
1478
+ function custom(content, options = {}) {
1479
+ return show({ ...options, content });
1480
+ }
1481
+ var toast = Object.assign(
1482
+ (message, options) => info(message, options),
1483
+ {
1484
+ show,
1485
+ success,
1486
+ error,
1487
+ warning,
1488
+ info,
1489
+ loading,
1490
+ confirm,
1491
+ promise,
1492
+ custom,
1493
+ close,
1494
+ closeAll,
1495
+ clear,
1496
+ update,
1497
+ get,
1498
+ exists,
1499
+ getCount,
1500
+ setDefaults,
1501
+ setLogger,
1502
+ injectStyles
1503
+ }
1504
+ );
1505
+ var GaToasts = toast;
1506
+ var src_default = toast;
1507
+
1508
+ export { GaToasts, src_default as default, toast };