basecoat-css 0.1.1 → 0.2.0-beta.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.
package/dist/js/all.js ADDED
@@ -0,0 +1,701 @@
1
+ (() => {
2
+ const initDropdownMenu = (dropdownMenuComponent) => {
3
+ const trigger = dropdownMenuComponent.querySelector('[popovertarget]');
4
+ const popover = dropdownMenuComponent.querySelector('[popover]');
5
+ const menu = popover?.querySelector('[role="menu"]');
6
+ if (!trigger || !popover || !menu) return;
7
+
8
+ let menuItems = [];
9
+ let activeIndex = -1;
10
+
11
+ const setActiveItem = (index) => {
12
+ if (activeIndex > -1 && menuItems[activeIndex]) {
13
+ menuItems[activeIndex].classList.remove('active');
14
+ }
15
+ activeIndex = index;
16
+ if (activeIndex > -1 && menuItems[activeIndex]) {
17
+ const activeItem = menuItems[activeIndex];
18
+ activeItem.classList.add('active');
19
+ trigger.setAttribute('aria-activedescendant', activeItem.id);
20
+ activeItem.scrollIntoView({ block: 'nearest' });
21
+ } else {
22
+ trigger.removeAttribute('aria-activedescendant');
23
+ }
24
+ };
25
+
26
+ const handleKeyDown = (e) => {
27
+ if (!popover.matches(':popover-open')) {
28
+ if (['ArrowDown', 'ArrowUp', 'Enter', ' '].includes(e.key)) {
29
+ e.preventDefault();
30
+ trigger.click();
31
+ }
32
+ return;
33
+ }
34
+
35
+ let nextIndex = activeIndex;
36
+ if (menuItems.length === 0) return;
37
+
38
+ switch (e.key) {
39
+ case 'ArrowDown':
40
+ e.preventDefault();
41
+ if (activeIndex < menuItems.length - 1) {
42
+ nextIndex = activeIndex + 1;
43
+ }
44
+ break;
45
+ case 'ArrowUp':
46
+ e.preventDefault();
47
+ if (activeIndex > 0) {
48
+ nextIndex = activeIndex - 1;
49
+ }
50
+ break;
51
+ case 'Home':
52
+ e.preventDefault();
53
+ nextIndex = 0;
54
+ break;
55
+ case 'End':
56
+ e.preventDefault();
57
+ nextIndex = menuItems.length - 1;
58
+ break;
59
+ case 'Enter':
60
+ case ' ':
61
+ e.preventDefault();
62
+ menuItems[activeIndex]?.click();
63
+ break;
64
+ }
65
+
66
+ if (nextIndex !== activeIndex) {
67
+ setActiveItem(nextIndex);
68
+ }
69
+ };
70
+
71
+ trigger.addEventListener('keydown', handleKeyDown);
72
+
73
+ popover.addEventListener('toggle', (e) => {
74
+ trigger.setAttribute('aria-expanded', e.newState === 'open');
75
+ if (e.newState === 'open') {
76
+ menuItems = Array.from(menu.querySelectorAll('[role^="menuitem"]:not([disabled])'));
77
+ menuItems.forEach((item, index) => {
78
+ if (!item.id) item.id = `${menu.id}-item-${index}`;
79
+ });
80
+ setActiveItem(0);
81
+ } else {
82
+ setActiveItem(-1);
83
+ }
84
+ });
85
+
86
+ menu.addEventListener('click', (e) => {
87
+ if (e.target.closest('[role^="menuitem"]')) {
88
+ popover.hidePopover();
89
+ }
90
+ });
91
+
92
+ menu.addEventListener('mouseover', (e) => {
93
+ const item = e.target.closest('[role^="menuitem"]:not([disabled])');
94
+ if (item) {
95
+ const index = menuItems.indexOf(item);
96
+ if (index > -1 && index !== activeIndex) {
97
+ setActiveItem(index);
98
+ }
99
+ }
100
+ });
101
+
102
+ dropdownMenuComponent.dataset.dropdownMenuInitialized = true;
103
+ };
104
+
105
+ document.querySelectorAll('.dropdown-menu:not([data-dropdown-menu-initialized])').forEach(initDropdownMenu);
106
+
107
+ const observer = new MutationObserver((mutations) => {
108
+ mutations.forEach((mutation) => {
109
+ mutation.addedNodes.forEach((node) => {
110
+ if (node.nodeType === Node.ELEMENT_NODE) {
111
+ if (node.matches('.dropdown-menu:not([data-dropdown-menu-initialized])')) {
112
+ initDropdownMenu(node);
113
+ }
114
+ node.querySelectorAll('.dropdown-menu:not([data-dropdown-menu-initialized])').forEach(initDropdownMenu);
115
+ }
116
+ });
117
+ });
118
+ });
119
+
120
+ observer.observe(document.body, { childList: true, subtree: true });
121
+ })();
122
+ (() => {
123
+ const initSelect = (selectComponent) => {
124
+ const trigger = selectComponent.querySelector(':scope > [popovertarget]');
125
+ const selectedValue = trigger.querySelector(':scope > span');
126
+ const popover = selectComponent.querySelector(':scope > [popover]');
127
+ const listbox = popover.querySelector('[role="listbox"]');
128
+ const input = selectComponent.querySelector(':scope > input[type="hidden"]');
129
+ const filter = selectComponent.querySelector('header input[type="text"]');
130
+ if (!trigger || !popover || !listbox || !input) return;
131
+
132
+ const options = Array.from(listbox.querySelectorAll('[role="option"]'));
133
+ let visibleOptions = [...options];
134
+ let activeIndex = -1;
135
+
136
+ const updateValue = (option) => {
137
+ if (option) {
138
+ selectedValue.innerHTML = option.dataset.label || option.innerHTML;
139
+ input.value = option.dataset.value;
140
+ listbox.querySelector('[role="option"][aria-selected="true"]')?.removeAttribute('aria-selected');
141
+ option.setAttribute('aria-selected', 'true');
142
+ }
143
+ };
144
+
145
+ const selectOption = (option) => {
146
+ if (!option) return;
147
+
148
+ updateValue(option);
149
+
150
+ trigger.removeAttribute('aria-activedescendant');
151
+ options.forEach(opt => opt.classList.remove('active'));
152
+ activeIndex = -1;
153
+ popover.hidePopover();
154
+ };
155
+
156
+ if (filter) {
157
+ const filterOptions = () => {
158
+ const searchTerm = filter.value.trim().toLowerCase();
159
+
160
+ if (activeIndex > -1) {
161
+ options[activeIndex].classList.remove('active');
162
+ trigger.removeAttribute('aria-activedescendant');
163
+ activeIndex = -1;
164
+ }
165
+
166
+ visibleOptions = [];
167
+ options.forEach(option => {
168
+ const optionText = (option.dataset.label || option.textContent).trim().toLowerCase();
169
+ const matches = optionText.includes(searchTerm);
170
+ option.setAttribute('aria-hidden', String(!matches));
171
+ if (matches) {
172
+ visibleOptions.push(option);
173
+ }
174
+ });
175
+ };
176
+
177
+ filter.addEventListener('input', filterOptions);
178
+ }
179
+
180
+ let initialOption = options.find(opt => input.value && opt.dataset.value === input.value);
181
+ if (!initialOption && options.length > 0) initialOption = options[0];
182
+
183
+ updateValue(initialOption);
184
+
185
+ const handleKeyNavigation = (e) => {
186
+ if (!['ArrowDown', 'ArrowUp', 'Enter', 'Home', 'End'].includes(e.key)) {
187
+ return;
188
+ }
189
+
190
+ if (!popover.matches(':popover-open')) {
191
+ if (e.currentTarget === trigger && e.key !== 'Enter') {
192
+ e.preventDefault();
193
+ trigger.click();
194
+ }
195
+ return;
196
+ }
197
+
198
+ e.preventDefault();
199
+
200
+ if (e.key === 'Enter') {
201
+ if (activeIndex > -1) {
202
+ selectOption(options[activeIndex]);
203
+ }
204
+ return;
205
+ }
206
+
207
+ if (visibleOptions.length === 0) return;
208
+
209
+ const currentVisibleIndex = activeIndex > -1 ? visibleOptions.indexOf(options[activeIndex]) : -1;
210
+ let nextVisibleIndex = currentVisibleIndex;
211
+
212
+ switch (e.key) {
213
+ case 'ArrowDown':
214
+ if (currentVisibleIndex < visibleOptions.length - 1) {
215
+ nextVisibleIndex = currentVisibleIndex + 1;
216
+ }
217
+ break;
218
+ case 'ArrowUp':
219
+ if (currentVisibleIndex > 0) {
220
+ nextVisibleIndex = currentVisibleIndex - 1;
221
+ } else if (currentVisibleIndex === -1) {
222
+ nextVisibleIndex = 0; // Start from top if nothing is active
223
+ }
224
+ break;
225
+ case 'Home':
226
+ nextVisibleIndex = 0;
227
+ break;
228
+ case 'End':
229
+ nextVisibleIndex = visibleOptions.length - 1;
230
+ break;
231
+ }
232
+
233
+ if (nextVisibleIndex !== currentVisibleIndex) {
234
+ if (currentVisibleIndex > -1) {
235
+ visibleOptions[currentVisibleIndex].classList.remove('active');
236
+ }
237
+
238
+ const newActiveOption = visibleOptions[nextVisibleIndex];
239
+ newActiveOption.classList.add('active');
240
+ activeIndex = options.indexOf(newActiveOption);
241
+
242
+ if (newActiveOption.id) {
243
+ trigger.setAttribute('aria-activedescendant', newActiveOption.id);
244
+ }
245
+ newActiveOption.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
246
+ }
247
+ };
248
+
249
+ trigger.addEventListener('keydown', handleKeyNavigation);
250
+ if (filter) {
251
+ filter.addEventListener('keydown', handleKeyNavigation);
252
+ }
253
+
254
+ listbox.addEventListener('click', (e) => {
255
+ const clickedOption = e.target.closest('[role="option"]');
256
+ if (clickedOption) {
257
+ selectOption(clickedOption);
258
+ }
259
+ });
260
+
261
+ popover.addEventListener('toggle', (e) => {
262
+ trigger.setAttribute('aria-expanded', e.newState === 'open');
263
+
264
+ if (e.newState === 'open') {
265
+ if (filter) filter.focus();
266
+
267
+ const selectedOption = listbox.querySelector('[role="option"][aria-selected="true"]');
268
+ let startingOption = null;
269
+
270
+ if (selectedOption && visibleOptions.includes(selectedOption)) {
271
+ startingOption = selectedOption;
272
+ } else if (visibleOptions.length > 0) {
273
+ startingOption = visibleOptions[0];
274
+ }
275
+
276
+ if (activeIndex > -1) options[activeIndex]?.classList.remove('active');
277
+
278
+ if (startingOption) {
279
+ activeIndex = options.indexOf(startingOption);
280
+ startingOption.classList.add('active');
281
+ if (startingOption.id) {
282
+ trigger.setAttribute('aria-activedescendant', startingOption.id);
283
+ }
284
+ startingOption.scrollIntoView({ block: 'nearest' });
285
+ } else {
286
+ activeIndex = -1;
287
+ }
288
+ } else if (e.newState === 'closed') {
289
+ if (filter) {
290
+ filter.value = '';
291
+ visibleOptions = [...options];
292
+ options.forEach(opt => opt.setAttribute('aria-hidden', 'false'));
293
+ }
294
+
295
+ trigger.removeAttribute('aria-activedescendant');
296
+ if (activeIndex > -1) options[activeIndex]?.classList.remove('active');
297
+ activeIndex = -1;
298
+ }
299
+ });
300
+
301
+ selectComponent.dataset.selectInitialized = true;
302
+ };
303
+
304
+ document.querySelectorAll('div.select:not([data-select-initialized])').forEach(initSelect);
305
+
306
+ const observer = new MutationObserver((mutations) => {
307
+ mutations.forEach((mutation) => {
308
+ mutation.addedNodes.forEach((node) => {
309
+ if (node.nodeType === Node.ELEMENT_NODE) {
310
+ if (node.matches('div.select:not([data-select-initialized])')) {
311
+ initSelect(node);
312
+ }
313
+ node.querySelectorAll('div.select:not([data-select-initialized])').forEach(initSelect);
314
+ }
315
+ });
316
+ });
317
+ });
318
+
319
+ observer.observe(document.body, { childList: true, subtree: true });
320
+ })();
321
+ (() => {
322
+ // Monkey patching the history API to detect client-side navigation
323
+ if (!window.history.__basecoatPatched) {
324
+ const originalPushState = window.history.pushState;
325
+ window.history.pushState = function(...args) {
326
+ originalPushState.apply(this, args);
327
+ window.dispatchEvent(new Event('basecoat:locationchange'));
328
+ };
329
+
330
+ const originalReplaceState = window.history.replaceState;
331
+ window.history.replaceState = function(...args) {
332
+ originalReplaceState.apply(this, args);
333
+ window.dispatchEvent(new Event('basecoat:locationchange'));
334
+ };
335
+
336
+ window.history.__basecoatPatched = true;
337
+ }
338
+
339
+ const initSidebar = (sidebarComponent) => {
340
+ const initialOpen = sidebarComponent.dataset.initialOpen !== 'false';
341
+ const initialMobileOpen = sidebarComponent.dataset.initialMobileOpen === 'true';
342
+ const breakpoint = parseInt(sidebarComponent.dataset.breakpoint) || 768;
343
+
344
+ let open = breakpoint > 0
345
+ ? (window.innerWidth >= breakpoint ? initialOpen : initialMobileOpen)
346
+ : initialOpen;
347
+
348
+ const updateCurrentPageLinks = () => {
349
+ const currentPath = window.location.pathname.replace(/\/$/, '');
350
+ sidebarComponent.querySelectorAll('a').forEach(link => {
351
+ if (link.hasAttribute('data-ignore-current')) return;
352
+
353
+ const linkPath = new URL(link.href).pathname.replace(/\/$/, '');
354
+ if (linkPath === currentPath) {
355
+ link.setAttribute('aria-current', 'page');
356
+ } else {
357
+ link.removeAttribute('aria-current');
358
+ }
359
+ });
360
+ };
361
+
362
+ const updateState = () => {
363
+ sidebarComponent.setAttribute('aria-hidden', !open);
364
+ if (open) {
365
+ sidebarComponent.removeAttribute('inert');
366
+ } else {
367
+ sidebarComponent.setAttribute('inert', '');
368
+ }
369
+ };
370
+
371
+ const setState = (state) => {
372
+ open = state;
373
+ updateState();
374
+ };
375
+
376
+ const sidebarId = sidebarComponent.id;
377
+
378
+ window.addEventListener('sidebar:open', (e) => {
379
+ if (!e.detail?.id || e.detail.id === sidebarId) setState(true);
380
+ });
381
+ window.addEventListener('sidebar:close', (e) => {
382
+ if (!e.detail?.id || e.detail.id === sidebarId) setState(false);
383
+ });
384
+ window.addEventListener('sidebar:toggle', (e) => {
385
+ if (!e.detail?.id || e.detail.id === sidebarId) setState(!open);
386
+ });
387
+
388
+ sidebarComponent.addEventListener('click', (e) => {
389
+ const target = e.target;
390
+ const nav = sidebarComponent.querySelector('nav');
391
+
392
+ const isMobile = window.innerWidth < breakpoint;
393
+
394
+ if (isMobile && (target.closest('a, button') && !target.closest('[data-keep-mobile-sidebar-open]'))) {
395
+ if (document.activeElement) document.activeElement.blur();
396
+ setState(false);
397
+ return;
398
+ }
399
+
400
+ if (target === sidebarComponent || (nav && !nav.contains(target))) {
401
+ if (document.activeElement) document.activeElement.blur();
402
+ setState(false);
403
+ }
404
+ });
405
+
406
+ window.addEventListener('popstate', updateCurrentPageLinks);
407
+ window.addEventListener('basecoat:locationchange', updateCurrentPageLinks);
408
+
409
+ updateState();
410
+ updateCurrentPageLinks();
411
+ sidebarComponent.dataset.sidebarInitialized = true;
412
+ };
413
+
414
+ document.querySelectorAll('.sidebar:not([data-sidebar-initialized])').forEach(initSidebar);
415
+
416
+ const observer = new MutationObserver((mutations) => {
417
+ mutations.forEach((mutation) => {
418
+ mutation.addedNodes.forEach((node) => {
419
+ if (node.nodeType === Node.ELEMENT_NODE) {
420
+ if (node.matches('.sidebar:not([data-sidebar-initialized])')) {
421
+ initSidebar(node);
422
+ }
423
+ node.querySelectorAll('.sidebar:not([data-sidebar-initialized])').forEach(initSidebar);
424
+ }
425
+ });
426
+ });
427
+ });
428
+
429
+ observer.observe(document.body, { childList: true, subtree: true });
430
+ })();
431
+ (() => {
432
+ const initTabs = (tabsComponent) => {
433
+ const tablist = tabsComponent.querySelector('[role="tablist"]');
434
+ if (!tablist) return;
435
+
436
+ const tabs = Array.from(tablist.querySelectorAll('[role="tab"]'));
437
+ const panels = tabs.map(tab => document.getElementById(tab.getAttribute('aria-controls'))).filter(Boolean);
438
+
439
+ const selectTab = (tabToSelect) => {
440
+ tabs.forEach((tab, index) => {
441
+ tab.setAttribute('aria-selected', 'false');
442
+ tab.setAttribute('tabindex', '-1');
443
+ if (panels[index]) panels[index].hidden = true;
444
+ });
445
+
446
+ tabToSelect.setAttribute('aria-selected', 'true');
447
+ tabToSelect.setAttribute('tabindex', '0');
448
+ const activePanel = document.getElementById(tabToSelect.getAttribute('aria-controls'));
449
+ if (activePanel) activePanel.hidden = false;
450
+ };
451
+
452
+ tablist.addEventListener('click', (e) => {
453
+ const clickedTab = e.target.closest('[role="tab"]');
454
+ if (clickedTab) selectTab(clickedTab);
455
+ });
456
+
457
+ tablist.addEventListener('keydown', (e) => {
458
+ const currentTab = e.target;
459
+ if (!tabs.includes(currentTab)) return;
460
+
461
+ let nextTab;
462
+ const currentIndex = tabs.indexOf(currentTab);
463
+
464
+ switch (e.key) {
465
+ case 'ArrowRight':
466
+ nextTab = tabs[(currentIndex + 1) % tabs.length];
467
+ break;
468
+ case 'ArrowLeft':
469
+ nextTab = tabs[(currentIndex - 1 + tabs.length) % tabs.length];
470
+ break;
471
+ case 'Home':
472
+ nextTab = tabs[0];
473
+ break;
474
+ case 'End':
475
+ nextTab = tabs[tabs.length - 1];
476
+ break;
477
+ default:
478
+ return;
479
+ }
480
+
481
+ e.preventDefault();
482
+ selectTab(nextTab);
483
+ nextTab.focus();
484
+ });
485
+
486
+ tabsComponent.dataset.tabsInitialized = true;
487
+ };
488
+
489
+ document.querySelectorAll('.tabs:not([data-tabs-initialized])').forEach(initTabs);
490
+
491
+ const observer = new MutationObserver((mutations) => {
492
+ mutations.forEach((mutation) => {
493
+ mutation.addedNodes.forEach((node) => {
494
+ if (node.nodeType === Node.ELEMENT_NODE) {
495
+ if (node.matches('.tabs:not([data-tabs-initialized])')) {
496
+ initTabs(node);
497
+ }
498
+ node.querySelectorAll('.tabs:not([data-tabs-initialized])').forEach(initTabs);
499
+ }
500
+ });
501
+ });
502
+ });
503
+
504
+ observer.observe(document.body, { childList: true, subtree: true });
505
+ })();
506
+ (() => {
507
+ let toaster;
508
+ const toasts = new WeakMap();
509
+ let isPaused = false;
510
+ const ICONS = {
511
+ success: '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/></svg>',
512
+ error: '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/></svg>',
513
+ info: '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>',
514
+ warning: '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>'
515
+ };
516
+
517
+ function initToaster(toasterElement) {
518
+ if (toasterElement.dataset.toasterInitialized) return;
519
+ toaster = toasterElement;
520
+
521
+ toaster.addEventListener('mouseenter', pauseAllTimeouts);
522
+ toaster.addEventListener('mouseleave', resumeAllTimeouts);
523
+ toaster.addEventListener('click', (e) => {
524
+ const actionLink = e.target.closest('.toast footer a');
525
+ const actionButton = e.target.closest('.toast footer button');
526
+ if (actionLink || actionButton) {
527
+ closeToast(e.target.closest('.toast'));
528
+ }
529
+ });
530
+
531
+ toaster.querySelectorAll('.toast:not([data-toast-initialized])').forEach(initToast);
532
+ toaster.dataset.toasterInitialized = 'true';
533
+ }
534
+
535
+ function initToast(element) {
536
+ if (element.dataset.toastInitialized) return;
537
+
538
+ const duration = parseInt(element.dataset.duration);
539
+ const timeoutDuration = duration !== -1
540
+ ? duration || (element.dataset.category === 'error' ? 5000 : 3000)
541
+ : -1;
542
+
543
+ const state = {
544
+ remainingTime: timeoutDuration,
545
+ timeoutId: null,
546
+ startTime: null,
547
+ };
548
+
549
+ if (timeoutDuration !== -1) {
550
+ if (isPaused) {
551
+ state.timeoutId = null;
552
+ } else {
553
+ state.startTime = Date.now();
554
+ state.timeoutId = setTimeout(() => closeToast(element), timeoutDuration);
555
+ }
556
+ }
557
+ toasts.set(element, state);
558
+
559
+ element.dataset.toastInitialized = 'true';
560
+ }
561
+
562
+ function pauseAllTimeouts() {
563
+ if (isPaused) return;
564
+
565
+ isPaused = true;
566
+
567
+ toaster.querySelectorAll('.toast:not([aria-hidden="true"])').forEach(element => {
568
+ if (!toasts.has(element)) return;
569
+
570
+ const state = toasts.get(element);
571
+ if (state.timeoutId) {
572
+ clearTimeout(state.timeoutId);
573
+ state.timeoutId = null;
574
+ state.remainingTime -= Date.now() - state.startTime;
575
+ }
576
+ });
577
+ }
578
+
579
+ function resumeAllTimeouts() {
580
+ if (!isPaused) return;
581
+
582
+ isPaused = false;
583
+
584
+ toaster.querySelectorAll('.toast:not([aria-hidden="true"])').forEach(element => {
585
+ if (!toasts.has(element)) return;
586
+
587
+ const state = toasts.get(element);
588
+ if (state.remainingTime !== -1 && !state.timeoutId) {
589
+ if (state.remainingTime > 0) {
590
+ state.startTime = Date.now();
591
+ state.timeoutId = setTimeout(() => closeToast(element), state.remainingTime);
592
+ } else {
593
+ closeToast(element);
594
+ }
595
+ }
596
+ });
597
+ }
598
+
599
+ function closeToast(element) {
600
+ if (!toasts.has(element)) return;
601
+
602
+ const state = toasts.get(element);
603
+ clearTimeout(state.timeoutId);
604
+ toasts.delete(element);
605
+
606
+ if (document.activeElement) document.activeElement.blur();
607
+ element.setAttribute('aria-hidden', 'true');
608
+ element.addEventListener('transitionend', () => element.remove(), { once: true });
609
+ }
610
+
611
+ function executeAction(button, toast) {
612
+ const actionString = button.dataset.toastAction;
613
+ if (!actionString) return;
614
+ try {
615
+ const func = new Function('close', actionString);
616
+ func(() => closeToast(toast));
617
+ } catch (e) {
618
+ console.error('Error executing toast action:', e);
619
+ }
620
+ }
621
+
622
+ function createToast(config) {
623
+ const {
624
+ category = 'info',
625
+ title,
626
+ description,
627
+ action,
628
+ cancel,
629
+ duration,
630
+ icon,
631
+ } = config;
632
+
633
+ const iconHtml = icon || (category && ICONS[category]) || '';
634
+ const titleHtml = title ? `<h2>${title}</h2>` : '';
635
+ const descriptionHtml = description ? `<p>${description}</p>` : '';
636
+ const actionHtml = action?.href
637
+ ? `<a href="${action.href}" class="btn" data-toast-action>${action.label}</a>`
638
+ : action?.onclick
639
+ ? `<button type="button" class="btn" data-toast-action onclick="${action.onclick}">${action.label}</button>`
640
+ : '';
641
+ const cancelHtml = cancel
642
+ ? `<button type="button" class="btn-outline h-6 text-xs px-2.5 rounded-sm" data-toast-cancel onclick="${cancel?.onclick}">${cancel.label}</button>`
643
+ : '';
644
+
645
+ const footerHtml = actionHtml || cancelHtml ? `<footer>${actionHtml}${cancelHtml}</footer>` : '';
646
+
647
+ const html = `
648
+ <div
649
+ class="toast"
650
+ role="${category === 'error' ? 'alert' : 'status'}"
651
+ aria-atomic="true"
652
+ ${category ? `data-category="${category}"` : ''}
653
+ ${duration !== undefined ? `data-duration="${duration}"` : ''}
654
+ >
655
+ <div class="toast-content">
656
+ ${iconHtml}
657
+ <section>
658
+ ${titleHtml}
659
+ ${descriptionHtml}
660
+ </section>
661
+ ${footerHtml}
662
+ </div>
663
+ </div>
664
+ </div>
665
+ `;
666
+ const template = document.createElement('template');
667
+ template.innerHTML = html.trim();
668
+ return template.content.firstChild;
669
+ }
670
+
671
+ const initialToaster = document.getElementById('toaster');
672
+ if (initialToaster) initToaster(initialToaster);
673
+
674
+ window.addEventListener('basecoat:toast', (e) => {
675
+ if (!toaster) {
676
+ console.error('Cannot create toast: toaster container not found on page.');
677
+ return;
678
+ }
679
+ const config = e.detail?.config || {};
680
+ const toastElement = createToast(config);
681
+ toaster.appendChild(toastElement);
682
+ });
683
+
684
+ const observer = new MutationObserver((mutations) => {
685
+ mutations.forEach((mutation) => {
686
+ mutation.addedNodes.forEach((node) => {
687
+ if (node.nodeType !== Node.ELEMENT_NODE) return;
688
+
689
+ if (node.matches('#toaster')) {
690
+ initToaster(node);
691
+ }
692
+
693
+ if (toaster && node.matches('.toast:not([data-toast-initialized])')) {
694
+ initToast(node);
695
+ }
696
+ });
697
+ });
698
+ });
699
+
700
+ observer.observe(document.body, { childList: true, subtree: true });
701
+ })();