veritheme 1.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/veritheme.js ADDED
@@ -0,0 +1,1108 @@
1
+ /*!
2
+ * veritheme.js v1.1.0
3
+ * https://veritheme.com
4
+ * MIT License
5
+ */
6
+
7
+ // UCAccordion — expand/collapse with single and multi mode
8
+ var UCAccordion = {
9
+ init: function() {
10
+ var uid = 0;
11
+ document.querySelectorAll('[data-accordion-item]').forEach(function(item) {
12
+ uid++;
13
+ var trigger = item.querySelector('[data-accordion-trigger]');
14
+ var content = item.querySelector('[data-accordion-content]');
15
+ if (trigger && content) {
16
+ var tId = 'acc-trigger-' + uid;
17
+ var cId = 'acc-content-' + uid;
18
+ trigger.id = tId;
19
+ content.id = cId;
20
+ trigger.setAttribute('aria-controls', cId);
21
+ if (!trigger.hasAttribute('role') && trigger.tagName !== 'BUTTON') {
22
+ trigger.setAttribute('role', 'button');
23
+ trigger.setAttribute('tabindex', '0');
24
+ }
25
+ content.setAttribute('aria-labelledby', tId);
26
+ content.setAttribute('role', 'region');
27
+ var isOpen = !content.classList.contains('vt-hidden') && !content.classList.contains('hidden');
28
+ trigger.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
29
+ }
30
+ });
31
+
32
+ function openContent(content) {
33
+ content.classList.remove('vt-hidden');
34
+ content.classList.add('open');
35
+ // Measure the final height with transitions suppressed: while
36
+ // padding-bottom is mid-transition, computed styles report the start
37
+ // value and the measured height comes out short — freezing a max-height
38
+ // that permanently clips the bottom padding.
39
+ var prevTransition = content.style.transition;
40
+ var prevMaxHeight = content.style.maxHeight;
41
+ content.style.transition = 'none';
42
+ content.style.maxHeight = 'none';
43
+ var target = content.scrollHeight; // includes the final padding-bottom
44
+ content.style.maxHeight = prevMaxHeight || '0px';
45
+ void content.offsetHeight; // commit the collapsed start value
46
+ content.style.transition = prevTransition;
47
+ content.style.maxHeight = target + 'px';
48
+ }
49
+
50
+ function closeContent(content) {
51
+ content.style.maxHeight = '0px';
52
+ content.classList.remove('open');
53
+ content.addEventListener('transitionend', function handler() {
54
+ if (content.style.maxHeight === '0px') {
55
+ content.classList.add('vt-hidden');
56
+ }
57
+ content.removeEventListener('transitionend', handler);
58
+ });
59
+ }
60
+
61
+ document.querySelectorAll('[data-accordion-content]').forEach(function(content) {
62
+ if (!content.classList.contains('vt-hidden') && !content.classList.contains('hidden')) {
63
+ openContent(content);
64
+ }
65
+ });
66
+
67
+ document.querySelectorAll('[data-accordion-trigger]').forEach(function(trigger) {
68
+ if (trigger.hasAttribute('data-accordion-init')) return;
69
+ trigger.setAttribute('data-accordion-init', 'true');
70
+
71
+ trigger.addEventListener('keydown', function(e) {
72
+ if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); trigger.click(); }
73
+ });
74
+ trigger.addEventListener('click', function() {
75
+ var item = trigger.closest('[data-accordion-item]');
76
+ var accordion = trigger.closest('[data-accordion]');
77
+ var mode = accordion.getAttribute('data-accordion');
78
+ var content = item.querySelector('[data-accordion-content]');
79
+ var icon = trigger.querySelector('[data-accordion-icon]');
80
+ var isOpen = !content.classList.contains('vt-hidden') && !content.classList.contains('hidden') && content.classList.contains('open');
81
+
82
+ if (mode === 'single') {
83
+ accordion.querySelectorAll('[data-accordion-item]').forEach(function(otherItem) {
84
+ var otherContent = otherItem.querySelector('[data-accordion-content]');
85
+ var otherTrigger = otherItem.querySelector('[data-accordion-trigger]');
86
+ var otherIcon = otherItem.querySelector('[data-accordion-trigger] [data-accordion-icon]');
87
+ if (otherItem !== item && !otherContent.classList.contains('vt-hidden') && !otherContent.classList.contains('hidden')) {
88
+ closeContent(otherContent);
89
+ if (otherIcon) otherIcon.classList.remove('vt-rotate-180');
90
+ if (otherTrigger) otherTrigger.setAttribute('aria-expanded', 'false');
91
+ }
92
+ });
93
+ }
94
+
95
+ if (isOpen) {
96
+ closeContent(content);
97
+ if (icon) icon.classList.remove('vt-rotate-180');
98
+ trigger.setAttribute('aria-expanded', 'false');
99
+ } else {
100
+ content.classList.remove('vt-hidden');
101
+ content.offsetHeight;
102
+ openContent(content);
103
+ if (icon) icon.classList.add('vt-rotate-180');
104
+ trigger.setAttribute('aria-expanded', 'true');
105
+ }
106
+ });
107
+ });
108
+ }
109
+ };
110
+
111
+ // UCCalendar — day selection for [data-calendar="single|range"] grids
112
+ var UCCalendar = {
113
+ init: function() {
114
+ document.querySelectorAll('[data-calendar]').forEach(function(cal) {
115
+ if (cal.hasAttribute('data-calendar-bound')) return;
116
+ cal.setAttribute('data-calendar-bound', 'true');
117
+ var mode = cal.getAttribute('data-calendar') || 'single';
118
+
119
+ cal.addEventListener('click', function(e) {
120
+ var day = e.target.closest('.vt-calendar-day');
121
+ if (!day || day.classList.contains('vt-outside') || day.classList.contains('vt-muted')) return;
122
+ var days = Array.prototype.slice.call(cal.querySelectorAll('.vt-calendar-day')).filter(function(d) {
123
+ return !d.classList.contains('vt-outside') && !d.classList.contains('vt-muted');
124
+ });
125
+
126
+ if (mode === 'range') {
127
+ var idx = days.indexOf(day);
128
+ if (cal._rangeStart == null || cal._rangeDone) {
129
+ days.forEach(function(d) {
130
+ d.classList.remove('vt-range-start', 'vt-range-middle', 'vt-range-end', 'vt-active');
131
+ });
132
+ day.classList.add('vt-range-start');
133
+ cal._rangeStart = idx;
134
+ cal._rangeDone = false;
135
+ } else {
136
+ var a = cal._rangeStart, b = idx;
137
+ if (a === b) return;
138
+ if (b < a) {
139
+ days[a].classList.remove('vt-range-start');
140
+ var t = a; a = b; b = t;
141
+ days[a].classList.add('vt-range-start');
142
+ }
143
+ for (var k = a + 1; k < b; k++) days[k].classList.add('vt-range-middle');
144
+ days[b].classList.add('vt-range-end');
145
+ cal._rangeDone = true;
146
+ }
147
+ } else {
148
+ days.forEach(function(d) { d.classList.remove('vt-active'); });
149
+ day.classList.add('vt-active');
150
+ }
151
+ });
152
+ });
153
+ }
154
+ };
155
+
156
+ // UCChip — toggleable filter chips inside [data-chip-group]
157
+ var UCChip = {
158
+ init: function() {
159
+ document.querySelectorAll('[data-chip-group]').forEach(function(group) {
160
+ if (group.hasAttribute('data-chip-bound')) return;
161
+ group.setAttribute('data-chip-bound', 'true');
162
+ group.querySelectorAll('.vt-chip-btn').forEach(function(btn) {
163
+ var selected = btn.getAttribute('data-selected') === 'true' || btn.classList.contains('vt-chip-active');
164
+ btn.setAttribute('aria-pressed', selected ? 'true' : 'false');
165
+ btn.addEventListener('click', function() {
166
+ var isSelected = btn.getAttribute('data-selected') === 'true';
167
+ btn.setAttribute('data-selected', isSelected ? 'false' : 'true');
168
+ btn.setAttribute('aria-pressed', isSelected ? 'false' : 'true');
169
+ btn.classList.toggle('vt-chip-active', !isSelected);
170
+ });
171
+ });
172
+ });
173
+ }
174
+ };
175
+
176
+ // UCRadioGroup — custom radio circles grouped by [data-group]
177
+ var UCRadioGroup = {
178
+ init: function() {
179
+ document.querySelectorAll('[data-group] .vt-radio').forEach(function(radio) {
180
+ var label = radio.closest('label');
181
+ if (!label || label.hasAttribute('data-radio-bound')) return;
182
+ label.setAttribute('data-radio-bound', 'true');
183
+ label.addEventListener('click', function() {
184
+ if (radio.classList.contains('disabled')) return;
185
+ var group = radio.closest('[data-group]');
186
+ group.querySelectorAll('.vt-radio').forEach(function(r) {
187
+ r.classList.remove('selected');
188
+ });
189
+ radio.classList.add('selected');
190
+ });
191
+ });
192
+ }
193
+ };
194
+
195
+ // selectRadio — called via onclick in HTML (docs markup)
196
+ function selectRadio(group, el) {
197
+ document.querySelectorAll('[data-group="' + group + '"] .vt-radio').forEach(function(r) {
198
+ r.classList.remove('selected');
199
+ });
200
+ var radio = el.querySelector('.vt-radio');
201
+ if (radio && !radio.classList.contains('disabled')) radio.classList.add('selected');
202
+ }
203
+
204
+ // UCTabs — tab switching for [data-tab-group] with roving-tabindex keyboard nav
205
+ var UCTabs = {
206
+ activate: function(container, tabName) {
207
+ container.querySelectorAll('[role="tab"]').forEach(function(t) {
208
+ var isActive = t.getAttribute('data-tab') === tabName;
209
+ t.setAttribute('aria-selected', isActive ? 'true' : 'false');
210
+ t.setAttribute('tabindex', isActive ? '0' : '-1');
211
+ t.classList.toggle('vt-active', isActive);
212
+ });
213
+ container.querySelectorAll('[role="tabpanel"]').forEach(function(p) {
214
+ p.classList.toggle('vt-hidden', p.getAttribute('data-panel') !== tabName);
215
+ });
216
+ },
217
+
218
+ init: function() {
219
+ document.querySelectorAll('[data-tab-group]').forEach(function(container) {
220
+ if (container.hasAttribute('data-tabs-bound')) return;
221
+ container.setAttribute('data-tabs-bound', 'true');
222
+ container.querySelectorAll('[role="tab"]').forEach(function(tab) {
223
+ tab.addEventListener('click', function() {
224
+ UCTabs.activate(container, tab.getAttribute('data-tab'));
225
+ });
226
+ });
227
+ });
228
+
229
+ if (!document.body.hasAttribute('data-tabs-keyboard-init')) {
230
+ document.body.setAttribute('data-tabs-keyboard-init', 'true');
231
+ document.addEventListener('keydown', function(e) {
232
+ var tab = e.target;
233
+ if (!tab || tab.getAttribute('role') !== 'tab') return;
234
+ var tablist = tab.closest('[role="tablist"]');
235
+ if (!tablist) return;
236
+ var tabs = Array.prototype.slice.call(tablist.querySelectorAll('[role="tab"]'));
237
+ var idx = tabs.indexOf(tab);
238
+ var newIdx = -1;
239
+ if (e.key === 'ArrowRight' || e.key === 'ArrowDown') { newIdx = (idx + 1) % tabs.length; }
240
+ else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') { newIdx = (idx - 1 + tabs.length) % tabs.length; }
241
+ else if (e.key === 'Home') { newIdx = 0; }
242
+ else if (e.key === 'End') { newIdx = tabs.length - 1; }
243
+ if (newIdx >= 0) { e.preventDefault(); tabs[newIdx].focus(); tabs[newIdx].click(); }
244
+ });
245
+ }
246
+ }
247
+ };
248
+
249
+ // UCCollapsible — expand/collapse content with optional animation
250
+ var UCCollapsible = {
251
+ init: function() {
252
+ // Initialize open collapsibles
253
+ document.querySelectorAll('[data-collapsible][data-collapsible-open]').forEach(function(collapsible) {
254
+ var content = collapsible.querySelector('[data-collapsible-content]');
255
+ var icon = collapsible.querySelector('[data-collapsible-icon]');
256
+ if (content) {
257
+ content.classList.remove('vt-hidden');
258
+ if (collapsible.hasAttribute('data-collapsible-animated')) {
259
+ content.style.maxHeight = content.scrollHeight + 'px';
260
+ }
261
+ }
262
+ if (icon) {
263
+ icon.classList.add('vt-rotate-180');
264
+ }
265
+ });
266
+
267
+ // Bind triggers with guard
268
+ document.querySelectorAll('[data-collapsible-trigger]').forEach(function(trigger) {
269
+ if (trigger.hasAttribute('data-collapsible-bound')) return;
270
+ trigger.setAttribute('data-collapsible-bound', 'true');
271
+
272
+ trigger.addEventListener('click', function() {
273
+ var collapsible = trigger.closest('[data-collapsible]');
274
+ var content = collapsible.querySelector(':scope > [data-collapsible-content]');
275
+ if (!content) {
276
+ content = collapsible.querySelector('[data-collapsible-content]');
277
+ }
278
+ var icon = collapsible.querySelector(':scope > * [data-collapsible-icon]') || collapsible.querySelector('[data-collapsible-icon]');
279
+ var isAnimated = collapsible.hasAttribute('data-collapsible-animated');
280
+ var labelCollapse = trigger.querySelector('[data-collapsible-label-collapse]');
281
+ var labelExpand = trigger.querySelector('[data-collapsible-label-expand]');
282
+ var isOpen;
283
+
284
+ if (isAnimated) {
285
+ isOpen = parseInt(content.style.maxHeight) > 0;
286
+ } else {
287
+ isOpen = !content.classList.contains('vt-hidden');
288
+ }
289
+
290
+ if (isOpen) {
291
+ if (isAnimated) {
292
+ content.style.maxHeight = '0';
293
+ } else {
294
+ content.classList.add('vt-hidden');
295
+ }
296
+ if (icon) icon.classList.remove('vt-rotate-180');
297
+ if (labelCollapse) labelCollapse.classList.add('vt-hidden');
298
+ if (labelExpand) labelExpand.classList.remove('vt-hidden');
299
+ collapsible.removeAttribute('data-collapsible-open');
300
+ } else {
301
+ if (isAnimated) {
302
+ content.style.maxHeight = content.scrollHeight + 'px';
303
+ } else {
304
+ content.classList.remove('vt-hidden');
305
+ }
306
+ if (icon) icon.classList.add('vt-rotate-180');
307
+ if (labelCollapse) labelCollapse.classList.remove('vt-hidden');
308
+ if (labelExpand) labelExpand.classList.add('vt-hidden');
309
+ collapsible.setAttribute('data-collapsible-open', '');
310
+ }
311
+ });
312
+ });
313
+ }
314
+ };
315
+
316
+ // UCCombobox — searchable single/multi-select with tags
317
+ var UCCombobox = {
318
+ init: function() {
319
+ var checkSvg = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="vt-w-4 vt-h-4 vt-shrink-0 vt-text-accents-brand vt-combobox-icon"><path d="M20 6 9 17l-5-5"/></svg>';
320
+ var emptySpacer = '<span class="vt-w-4 vt-h-4 vt-shrink-0 vt-combobox-icon"></span>';
321
+ var xTagSvg = '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="vt-w-3 vt-h-3"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>';
322
+
323
+ // Close on click outside
324
+ if (!document.body.hasAttribute('data-combobox-outside-init')) {
325
+ document.body.setAttribute('data-combobox-outside-init', 'true');
326
+ document.addEventListener('click', function(e) {
327
+ if (!e.target.closest('.vt-combobox-wrapper')) {
328
+ document.querySelectorAll('[data-combobox-content]').forEach(function(el) {
329
+ el.classList.remove('vt-open');
330
+ });
331
+ }
332
+ });
333
+ }
334
+
335
+ // Expose functions globally for inline onclick use
336
+ window.openCombobox = function(id) {
337
+ var el = document.getElementById(id);
338
+ if (el) el.classList.add('vt-open');
339
+ };
340
+
341
+ window.toggleCombobox = function(id) {
342
+ var el = document.getElementById(id);
343
+ if (el) el.classList.toggle('vt-open');
344
+ };
345
+
346
+ window.filterCombobox = function(input, id) {
347
+ var dropdown = document.getElementById(id);
348
+ var query = input.value.toLowerCase();
349
+ var items = dropdown.querySelectorAll('.vt-combobox-item');
350
+ var groups = dropdown.querySelectorAll('[data-combobox-group]');
351
+ var emptyEl = dropdown.querySelector('.vt-combobox-empty');
352
+ var totalVisible = 0;
353
+
354
+ if (groups.length > 0) {
355
+ groups.forEach(function(group) {
356
+ var groupItems = group.querySelectorAll('.vt-combobox-item');
357
+ var groupVisible = 0;
358
+ groupItems.forEach(function(item) {
359
+ if (item.textContent.toLowerCase().includes(query)) {
360
+ item.classList.remove('vt-hidden');
361
+ groupVisible++;
362
+ totalVisible++;
363
+ } else {
364
+ item.classList.add('vt-hidden');
365
+ }
366
+ });
367
+ group.style.display = groupVisible > 0 ? '' : 'none';
368
+ });
369
+ } else {
370
+ items.forEach(function(item) {
371
+ if (item.textContent.toLowerCase().includes(query)) {
372
+ item.classList.remove('vt-hidden');
373
+ totalVisible++;
374
+ } else {
375
+ item.classList.add('vt-hidden');
376
+ }
377
+ });
378
+ }
379
+
380
+ if (emptyEl) {
381
+ emptyEl.classList.toggle('vt-hidden', totalVisible > 0 || !query);
382
+ }
383
+ };
384
+
385
+ function setItemIcon(item, checked) {
386
+ var icon = item.querySelector('.vt-combobox-icon');
387
+ if (icon) icon.outerHTML = checked ? checkSvg : emptySpacer;
388
+ }
389
+
390
+ window.selectComboboxItem = function(item) {
391
+ var text = item.textContent.trim();
392
+ var wrapper = item.closest('.vt-combobox-wrapper');
393
+ var keepIcons = wrapper.hasAttribute('data-keep-icons');
394
+ var input = wrapper.querySelector('input');
395
+ var dropdown = item.closest('[data-combobox-content]');
396
+
397
+ input.value = text;
398
+
399
+ dropdown.querySelectorAll('.vt-combobox-item').forEach(function(i) {
400
+ i.classList.remove('vt-selected');
401
+ if (!keepIcons) setItemIcon(i, false);
402
+ });
403
+ item.classList.add('vt-selected');
404
+ if (!keepIcons) setItemIcon(item, true);
405
+
406
+ dropdown.classList.remove('vt-open');
407
+ };
408
+
409
+ window.selectComboboxButton = function(item, dropdownId) {
410
+ var text = item.textContent.trim();
411
+ var wrapper = item.closest('.vt-combobox-wrapper');
412
+ var label = wrapper.querySelector('.vt-combobox-button-label');
413
+ var dropdown = document.getElementById(dropdownId);
414
+
415
+ label.textContent = text;
416
+ label.classList.remove('vt-text-mains-quaternary');
417
+
418
+ dropdown.querySelectorAll('.vt-combobox-item').forEach(function(i) {
419
+ i.classList.remove('vt-selected');
420
+ setItemIcon(i, false);
421
+ });
422
+ item.classList.add('vt-selected');
423
+ setItemIcon(item, true);
424
+
425
+ dropdown.classList.remove('vt-open');
426
+ };
427
+
428
+ window.toggleMultiComboboxItem = function(item) {
429
+ var isSelected = item.classList.toggle('vt-selected');
430
+ setItemIcon(item, isSelected);
431
+
432
+ var wrapper = item.closest('.vt-combobox-wrapper');
433
+ var tagContainer = wrapper.querySelector('.vt-combobox-tags');
434
+ var text = item.textContent.trim();
435
+
436
+ if (isSelected) {
437
+ var tag = document.createElement('span');
438
+ tag.className = 'vt-combobox-tag vt-inline-flex vt-items-center vt-gap-1 vt-rounded vt-bg-surfaces-subtle vt-px-2 vt-py-0.5 vt-text-xs vt-font-medium';
439
+ tag.setAttribute('data-tag-value', text);
440
+ tag.appendChild(document.createTextNode(text));
441
+ var removeBtn = document.createElement('button');
442
+ removeBtn.type = 'button';
443
+ removeBtn.className = 'vt-ml-0.5 vt-rounded-sm vt-hover:bg-surfaces-moderate vt-transition-colors';
444
+ removeBtn.innerHTML = xTagSvg;
445
+ removeBtn.addEventListener('click', function(event) {
446
+ event.stopPropagation();
447
+ window.removeComboboxTag(removeBtn);
448
+ });
449
+ tag.appendChild(removeBtn);
450
+ tagContainer.insertBefore(tag, tagContainer.querySelector('input'));
451
+ } else {
452
+ var existing = tagContainer.querySelector('[data-tag-value="' + text + '"]');
453
+ if (existing) existing.remove();
454
+ }
455
+
456
+ UCCombobox._updateMultiPlaceholder(wrapper);
457
+ };
458
+
459
+ window.removeComboboxTag = function(btn) {
460
+ var tag = btn.closest('.vt-combobox-tag');
461
+ if (!tag) return;
462
+ var value = tag.getAttribute('data-tag-value');
463
+ var wrapper = tag.closest('.vt-combobox-wrapper');
464
+ tag.remove();
465
+
466
+ var dropdown = wrapper.querySelector('[data-combobox-content]');
467
+ if (dropdown) {
468
+ dropdown.querySelectorAll('.vt-combobox-item').forEach(function(item) {
469
+ if (item.textContent.trim() === value) {
470
+ item.classList.remove('vt-selected');
471
+ setItemIcon(item, false);
472
+ }
473
+ });
474
+ }
475
+
476
+ UCCombobox._updateMultiPlaceholder(wrapper);
477
+ };
478
+ },
479
+
480
+ _updateMultiPlaceholder: function(wrapper) {
481
+ var input = wrapper.querySelector('input');
482
+ var tags = wrapper.querySelectorAll('.vt-combobox-tag');
483
+ if (input) {
484
+ input.placeholder = tags.length > 0 ? '' : 'Add framework...';
485
+ }
486
+ }
487
+ };
488
+
489
+ // UCResizable — drag-to-resize panels horizontally or vertically
490
+ var UCResizable = {
491
+ init: function() {
492
+ document.querySelectorAll('[data-resizable]').forEach(function(container) {
493
+ if (container.hasAttribute('data-resizable-init')) return;
494
+ container.setAttribute('data-resizable-init', 'true');
495
+
496
+ var direction = container.dataset.resizable;
497
+ var isHorizontal = direction === 'horizontal';
498
+ var minPct = parseFloat(container.dataset.min || '10');
499
+ var maxPct = parseFloat(container.dataset.max || '90');
500
+ var handles = container.querySelectorAll(':scope > .vt-resize-handle');
501
+
502
+ handles.forEach(function(handle) {
503
+ var isDown = false;
504
+ var startPos = 0;
505
+ var prevPanel = null;
506
+ var nextPanel = null;
507
+ var containerSize = 0;
508
+ var prevStart = 0;
509
+ var nextStart = 0;
510
+
511
+ handle.addEventListener('mousedown', function(e) {
512
+ e.preventDefault();
513
+ isDown = true;
514
+ prevPanel = handle.previousElementSibling;
515
+ nextPanel = handle.nextElementSibling;
516
+ if (!prevPanel || !nextPanel) return;
517
+
518
+ containerSize = isHorizontal ? container.offsetWidth : container.offsetHeight;
519
+ prevStart = isHorizontal ? prevPanel.offsetWidth : prevPanel.offsetHeight;
520
+ nextStart = isHorizontal ? nextPanel.offsetWidth : nextPanel.offsetHeight;
521
+ startPos = isHorizontal ? e.clientX : e.clientY;
522
+
523
+ document.body.style.cursor = isHorizontal ? 'col-resize' : 'row-resize';
524
+ document.body.style.userSelect = 'none';
525
+ handle.style.background = 'hsl(var(--accents-brand) / 0.5)';
526
+ });
527
+
528
+ document.addEventListener('mousemove', function(e) {
529
+ if (!isDown || !prevPanel || !nextPanel) return;
530
+ var delta = (isHorizontal ? e.clientX : e.clientY) - startPos;
531
+ var newPrev = prevStart + delta;
532
+ var newNext = nextStart - delta;
533
+
534
+ var minSize = containerSize * (minPct / 100);
535
+ var maxSize = containerSize * (maxPct / 100);
536
+
537
+ if (newPrev < minSize) { newPrev = minSize; newNext = prevStart + nextStart - minSize; }
538
+ if (newNext < minSize) { newNext = minSize; newPrev = prevStart + nextStart - minSize; }
539
+ if (newPrev > maxSize) { newPrev = maxSize; newNext = prevStart + nextStart - maxSize; }
540
+ if (newNext > maxSize) { newNext = maxSize; newPrev = prevStart + nextStart - maxSize; }
541
+
542
+ var prevPct = (newPrev / containerSize * 100).toFixed(1);
543
+ var nextPct = (newNext / containerSize * 100).toFixed(1);
544
+
545
+ if (isHorizontal) {
546
+ prevPanel.style.width = prevPct + '%';
547
+ nextPanel.style.width = nextPct + '%';
548
+ } else {
549
+ prevPanel.style.height = prevPct + '%';
550
+ nextPanel.style.height = nextPct + '%';
551
+ }
552
+
553
+ prevPanel.style.flex = 'none';
554
+ nextPanel.style.flex = 'none';
555
+
556
+ var prevLabel = prevPanel.querySelector('.vt-size-label');
557
+ var nextLabel = nextPanel.querySelector('.vt-size-label');
558
+ if (prevLabel) prevLabel.textContent = Math.round(parseFloat(prevPct)) + '%';
559
+ if (nextLabel) nextLabel.textContent = Math.round(parseFloat(nextPct)) + '%';
560
+ });
561
+
562
+ document.addEventListener('mouseup', function() {
563
+ if (!isDown) return;
564
+ isDown = false;
565
+ document.body.style.cursor = '';
566
+ document.body.style.userSelect = '';
567
+ handle.style.background = '';
568
+ });
569
+ });
570
+ });
571
+ }
572
+ };
573
+
574
+ // UCCarousel — slide carousel with dots, prev/next navigation
575
+ var UCCarousel = {
576
+ init: function() {
577
+ document.querySelectorAll('[data-carousel]').forEach(function(el) {
578
+ if (el.hasAttribute('data-carousel-init')) return;
579
+ el.setAttribute('data-carousel-init', 'true');
580
+
581
+ var carousel = el;
582
+ var track = carousel.querySelector('[data-carousel-track]');
583
+ var prevBtn = carousel.querySelector('[data-carousel-prev]');
584
+ var nextBtn = carousel.querySelector('[data-carousel-next]');
585
+ var dotsContainer = carousel.querySelector('[data-carousel-dots]');
586
+ var total = parseInt(carousel.dataset.total || '0');
587
+ var visible = parseInt(carousel.dataset.visible || '1');
588
+ var maxIndex = total - visible;
589
+ var current = 0;
590
+
591
+ function update() {
592
+ if (visible === 1) {
593
+ track.style.transform = 'translateX(-' + (current * 100) + '%)';
594
+ } else {
595
+ var slide = track.children[0];
596
+ var gap = parseFloat(getComputedStyle(track).gap) || 0;
597
+ var offset = current * (slide.offsetWidth + gap);
598
+ track.style.transform = 'translateX(-' + offset + 'px)';
599
+ }
600
+
601
+ if (dotsContainer) {
602
+ dotsContainer.querySelectorAll('[data-dot]').forEach(function(dot) {
603
+ var idx = parseInt(dot.dataset.dot || '0');
604
+ dot.className = 'vt-w-2 vt-h-2 vt-rounded-full vt-transition-colors ' + (idx === current ? 'vt-bg-accents-brand' : 'vt-bg-surfaces-moderate');
605
+ });
606
+ }
607
+
608
+ if (prevBtn) prevBtn.style.opacity = current === 0 ? '0.4' : '1';
609
+ if (nextBtn) nextBtn.style.opacity = current >= maxIndex ? '0.4' : '1';
610
+ }
611
+
612
+ if (prevBtn) {
613
+ prevBtn.addEventListener('click', function() {
614
+ if (current > 0) { current--; update(); }
615
+ });
616
+ }
617
+
618
+ if (nextBtn) {
619
+ nextBtn.addEventListener('click', function() {
620
+ if (current < maxIndex) { current++; update(); }
621
+ });
622
+ }
623
+
624
+ if (dotsContainer) {
625
+ dotsContainer.querySelectorAll('[data-dot]').forEach(function(dot) {
626
+ dot.addEventListener('click', function() {
627
+ current = Math.min(parseInt(dot.dataset.dot || '0'), maxIndex);
628
+ update();
629
+ });
630
+ });
631
+ }
632
+
633
+ update();
634
+ });
635
+ }
636
+ };
637
+
638
+ // UCDropdown — dropdown menu with checkbox and radio item support
639
+ var UCDropdown = {
640
+ init: function() {
641
+ // Document-level listeners once; trigger binding runs every init so
642
+ // nodes added after page load (astro:after-swap) still get wired.
643
+ if (!document.body.hasAttribute('data-dropdowns-init')) {
644
+ document.body.setAttribute('data-dropdowns-init', 'true');
645
+
646
+ document.addEventListener('click', function(e) {
647
+ if (!e.target.closest('[data-dropdown-trigger]') && !e.target.closest('.vt-dropdown-menu')) {
648
+ document.querySelectorAll('.vt-dropdown-menu').forEach(function(el) {
649
+ // Combobox reuses .vt-dropdown-menu for its panel and manages
650
+ // open/close itself — closing it here fights the combobox.
651
+ if (el.closest('.vt-combobox-wrapper')) return;
652
+ el.classList.remove('vt-open');
653
+ });
654
+ }
655
+ });
656
+
657
+ document.addEventListener('keydown', function(e) {
658
+ if (e.key === 'Escape') {
659
+ document.querySelectorAll('.vt-dropdown-menu.vt-open').forEach(function(el) {
660
+ el.classList.remove('vt-open');
661
+ });
662
+ document.querySelectorAll('[data-dropdown-trigger]').forEach(function(t) {
663
+ t.setAttribute('aria-expanded', 'false');
664
+ });
665
+ }
666
+ });
667
+ }
668
+
669
+ document.querySelectorAll('[data-dropdown-trigger]').forEach(function(trigger) {
670
+ if (trigger.hasAttribute('data-dropdown-bound')) return;
671
+ trigger.setAttribute('data-dropdown-bound', 'true');
672
+ var targetId = trigger.getAttribute('data-dropdown-trigger');
673
+ trigger.setAttribute('aria-haspopup', 'menu');
674
+ trigger.setAttribute('aria-expanded', 'false');
675
+ if (targetId) trigger.setAttribute('aria-controls', targetId);
676
+ trigger.addEventListener('click', function(e) {
677
+ e.stopPropagation();
678
+ var content = document.getElementById(targetId);
679
+ if (!content) return;
680
+
681
+ document.querySelectorAll('.vt-dropdown-menu').forEach(function(el) {
682
+ if (el !== content) el.classList.remove('vt-open');
683
+ });
684
+
685
+ var willOpen = !content.classList.contains('vt-open');
686
+ content.classList.toggle('vt-open');
687
+ trigger.setAttribute('aria-expanded', willOpen ? 'true' : 'false');
688
+ });
689
+ });
690
+ }
691
+ };
692
+
693
+ // Checkbox toggle — called via onclick in HTML
694
+ function toggleCheckbox(el) {
695
+ var indicator = el.querySelector('.checkbox-indicator');
696
+ var isChecked = indicator.classList.contains('vt-bg-accents-brand');
697
+
698
+ if (isChecked) {
699
+ indicator.classList.remove('vt-bg-accents-brand', 'vt-text-generic-white', 'vt-border-transparent');
700
+ indicator.innerHTML = '';
701
+ el.setAttribute('aria-checked', 'false');
702
+ } else {
703
+ indicator.classList.add('vt-bg-accents-brand', 'vt-text-generic-white', 'vt-border-transparent');
704
+ indicator.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" class="vt-w-3 vt-h-3"><path d="M20 6 9 17l-5-5"/></svg>';
705
+ el.setAttribute('aria-checked', 'true');
706
+ }
707
+ }
708
+
709
+ // Menu radio select — called via onclick in HTML
710
+ function selectMenuRadio(el) {
711
+ var parent = el.closest('.vt-dropdown-menu');
712
+ parent.querySelectorAll('.radio-indicator').forEach(function(r) {
713
+ r.classList.remove('vt-border-accents-brand');
714
+ r.classList.add('vt-border-input');
715
+ r.innerHTML = '';
716
+ });
717
+
718
+ var indicator = el.querySelector('.radio-indicator');
719
+ indicator.classList.remove('vt-border-input');
720
+ indicator.classList.add('vt-border-accents-brand');
721
+ indicator.innerHTML = '<div class="vt-w-2 vt-h-2 vt-rounded-full vt-bg-accents-brand"></div>';
722
+ }
723
+
724
+ // UCScrollView — drag-to-scroll and scroll indicators
725
+ var UCScrollView = {
726
+ init: function() {
727
+ // Drag-to-scroll for horizontal containers
728
+ document.querySelectorAll('.vt-drag-scroll').forEach(function(el) {
729
+ if (el.hasAttribute('data-drag-init')) return;
730
+ el.setAttribute('data-drag-init', 'true');
731
+
732
+ var isDown = false;
733
+ var startX = 0;
734
+ var scrollLeft = 0;
735
+
736
+ el.addEventListener('mousedown', function(e) {
737
+ isDown = true;
738
+ el.style.cursor = 'grabbing';
739
+ el.style.userSelect = 'none';
740
+ startX = e.pageX - el.offsetLeft;
741
+ scrollLeft = el.scrollLeft;
742
+ });
743
+
744
+ el.addEventListener('mouseleave', function() {
745
+ isDown = false;
746
+ el.style.cursor = 'grab';
747
+ el.style.userSelect = '';
748
+ });
749
+
750
+ el.addEventListener('mouseup', function() {
751
+ isDown = false;
752
+ el.style.cursor = 'grab';
753
+ el.style.userSelect = '';
754
+ });
755
+
756
+ el.addEventListener('mousemove', function(e) {
757
+ if (!isDown) return;
758
+ e.preventDefault();
759
+ var x = e.pageX - el.offsetLeft;
760
+ var walk = (x - startX) * 1.5;
761
+ el.scrollLeft = scrollLeft - walk;
762
+ });
763
+ });
764
+
765
+ // Scroll indicators — vertical
766
+ document.querySelectorAll('.vt-scroll-indicator').forEach(function(indicator) {
767
+ if (indicator.hasAttribute('data-indicator-init')) return;
768
+ indicator.setAttribute('data-indicator-init', 'true');
769
+
770
+ var scroll = indicator.querySelector('.vt-scroll, .vt-scroll-hidden, .vt-scroll-autohide');
771
+ if (!scroll) return;
772
+
773
+ scroll.addEventListener('scroll', function() {
774
+ var st = scroll.scrollTop;
775
+ var sh = scroll.scrollHeight;
776
+ var ch = scroll.clientHeight;
777
+ indicator.classList.toggle('vt-show-top', st > 8);
778
+ indicator.classList.toggle('vt-show-bottom', st < sh - ch - 8);
779
+ });
780
+ });
781
+
782
+ // Scroll indicators — horizontal
783
+ document.querySelectorAll('.vt-scroll-indicator-h').forEach(function(indicator) {
784
+ if (indicator.hasAttribute('data-indicator-h-init')) return;
785
+ indicator.setAttribute('data-indicator-h-init', 'true');
786
+
787
+ var scroll = indicator.querySelector('.vt-scroll, .vt-scroll-hidden, .vt-scroll-autohide, [class*="vt-overflow-x"]');
788
+ if (!scroll) return;
789
+
790
+ scroll.addEventListener('scroll', function() {
791
+ var sl = scroll.scrollLeft;
792
+ var sw = scroll.scrollWidth;
793
+ var cw = scroll.clientWidth;
794
+ indicator.classList.toggle('vt-show-left', sl > 8);
795
+ indicator.classList.toggle('vt-show-right', sl < sw - cw - 8);
796
+ });
797
+ });
798
+ }
799
+ };
800
+
801
+ // UCNumberInput — increment/decrement with min/max constraints
802
+ var UCNumberInput = {
803
+ init: function() {
804
+ document.querySelectorAll('[data-number-input]').forEach(function(wrapper) {
805
+ if (wrapper.hasAttribute('data-ni-bound')) return;
806
+ wrapper.setAttribute('data-ni-bound', 'true');
807
+
808
+ var input = wrapper.querySelector('[data-number-value]');
809
+ var decBtn = wrapper.querySelector('[data-action="decrement"]');
810
+ var incBtn = wrapper.querySelector('[data-action="increment"]');
811
+ if (!input) return;
812
+
813
+ function updateButtons() {
814
+ var val = parseInt(input.value) || 0;
815
+ var min = input.hasAttribute('min') ? parseInt(input.min) : -Infinity;
816
+ var max = input.hasAttribute('max') ? parseInt(input.max) : Infinity;
817
+ if (decBtn) decBtn.disabled = val <= min;
818
+ if (incBtn) incBtn.disabled = val >= max;
819
+ }
820
+
821
+ function step(delta) {
822
+ var val = parseInt(input.value) || 0;
823
+ var min = input.hasAttribute('min') ? parseInt(input.min) : -Infinity;
824
+ var max = input.hasAttribute('max') ? parseInt(input.max) : Infinity;
825
+ var next = Math.min(Math.max(val + delta, min), max);
826
+ input.value = next;
827
+ updateButtons();
828
+ }
829
+
830
+ if (decBtn) decBtn.addEventListener('click', function() { step(-1); });
831
+ if (incBtn) incBtn.addEventListener('click', function() { step(1); });
832
+
833
+ input.addEventListener('change', function() {
834
+ var min = input.hasAttribute('min') ? parseInt(input.min) : -Infinity;
835
+ var max = input.hasAttribute('max') ? parseInt(input.max) : Infinity;
836
+ var val = parseInt(input.value) || min;
837
+ input.value = Math.min(Math.max(val, min), max);
838
+ updateButtons();
839
+ });
840
+
841
+ updateButtons();
842
+ });
843
+ }
844
+ };
845
+
846
+ // UCPopover — toggle popover panels with outside-click close
847
+ var UCPopover = {
848
+ init: function() {
849
+ document.querySelectorAll('[data-popover-trigger]').forEach(function(btn) {
850
+ var id = btn.getAttribute('data-popover-trigger');
851
+ btn.setAttribute('aria-haspopup', 'dialog');
852
+ btn.setAttribute('aria-expanded', 'false');
853
+ if (id) btn.setAttribute('aria-controls', id);
854
+
855
+ btn.onclick = function(e) {
856
+ e.stopPropagation();
857
+ var el = id ? document.getElementById(id) : null;
858
+ if (!el) return;
859
+
860
+ var isOpen = el.classList.contains('vt-open');
861
+
862
+ document.querySelectorAll('.vt-popover-content').forEach(function(p) {
863
+ p.classList.remove('vt-open');
864
+ });
865
+ document.querySelectorAll('[data-popover-trigger]').forEach(function(t) {
866
+ t.setAttribute('aria-expanded', 'false');
867
+ });
868
+
869
+ if (!isOpen) {
870
+ el.classList.add('vt-open');
871
+ btn.setAttribute('aria-expanded', 'true');
872
+ }
873
+ };
874
+ });
875
+
876
+ if (!document.body.hasAttribute('data-popover-outside-init')) {
877
+ document.body.setAttribute('data-popover-outside-init', 'true');
878
+ document.addEventListener('click', function(e) {
879
+ var target = e.target;
880
+ if (!target.closest('.vt-popover-content') && !target.closest('[data-popover-trigger]')) {
881
+ document.querySelectorAll('.vt-popover-content').forEach(function(p) {
882
+ p.classList.remove('vt-open');
883
+ });
884
+ document.querySelectorAll('[data-popover-trigger]').forEach(function(t) {
885
+ t.setAttribute('aria-expanded', 'false');
886
+ });
887
+ }
888
+ });
889
+ document.addEventListener('keydown', function(e) {
890
+ if (e.key === 'Escape') {
891
+ document.querySelectorAll('.vt-popover-content').forEach(function(p) {
892
+ p.classList.remove('vt-open');
893
+ });
894
+ document.querySelectorAll('[data-popover-trigger]').forEach(function(t) {
895
+ t.setAttribute('aria-expanded', 'false');
896
+ });
897
+ }
898
+ });
899
+ }
900
+ }
901
+ };
902
+
903
+ // UCSelect — custom select dropdown with single selection
904
+ var UCSelect = {
905
+ init: function() {
906
+ if (document.body.hasAttribute('data-select-init')) return;
907
+ document.body.setAttribute('data-select-init', 'true');
908
+
909
+ document.addEventListener('click', function(e) {
910
+ if (!e.target.closest('.vt-select-wrapper')) {
911
+ document.querySelectorAll('.vt-select-dropdown').forEach(function(d) {
912
+ d.classList.remove('open');
913
+ });
914
+ }
915
+ });
916
+ }
917
+ };
918
+
919
+ // toggleSelect — called via onclick in HTML
920
+ function toggleSelect(id) {
921
+ var dropdown = document.getElementById(id);
922
+ document.querySelectorAll('.vt-select-dropdown').forEach(function(d) {
923
+ if (d.id !== id) d.classList.remove('open');
924
+ });
925
+ dropdown.classList.toggle('open');
926
+ }
927
+
928
+ // selectItem — called via onclick in HTML
929
+ function selectItem(id, triggerId, value) {
930
+ document.getElementById(triggerId).querySelector('span').textContent = value;
931
+ document.getElementById(triggerId).querySelector('span').classList.remove('placeholder');
932
+ var dropdown = document.getElementById(id);
933
+ dropdown.querySelectorAll('.vt-select-item').forEach(function(i) {
934
+ i.classList.remove('selected');
935
+ });
936
+ dropdown.querySelectorAll('.vt-select-item').forEach(function(i) {
937
+ if (i.textContent.trim() === value) i.classList.add('selected');
938
+ });
939
+ dropdown.classList.remove('open');
940
+ }
941
+
942
+ // UCSlider — paints the filled track portion based on value/min/max
943
+ var UCSlider = {
944
+ init: function() {
945
+ document.querySelectorAll('input[type="range"]').forEach(function(input) {
946
+ if (input.hasAttribute('data-slider-init')) return;
947
+ input.setAttribute('data-slider-init', 'true');
948
+
949
+ function update() {
950
+ var min = parseFloat(input.min || '0');
951
+ var max = parseFloat(input.max || '100');
952
+ var val = parseFloat(input.value || '0');
953
+ var pct = max > min ? ((val - min) / (max - min)) * 100 : 0;
954
+ input.style.setProperty('--slider-progress', pct + '%');
955
+ }
956
+
957
+ update();
958
+ input.addEventListener('input', update);
959
+ });
960
+ }
961
+ };
962
+
963
+ // UCToast — programmatic toast notifications
964
+ var UCToast = {
965
+ _count: 0,
966
+
967
+ show: function(type) {
968
+ var container = document.getElementById('toast-container');
969
+ if (!container) {
970
+ // Out-of-the-box: create the host container on first use.
971
+ container = document.createElement('div');
972
+ container.id = 'toast-container';
973
+ container.className = 'vt-fixed vt-bottom-4 vt-right-4 vt-z-50 vt-flex vt-flex-col vt-items-end';
974
+ document.body.appendChild(container);
975
+ }
976
+
977
+ var id = 'toast-' + (++UCToast._count);
978
+ var configs = {
979
+ default: { title: 'Scheduled: Catch up', desc: 'Friday, February 14, 2026 at 5:57 PM', icon: '', btn: 'Undo' },
980
+ success: { title: 'Success', desc: 'Your changes have been saved.', icon: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="vt-w-5 vt-h-5 vt-text-accents-brand"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><path d="M22 4 12 14.01l-3-3"/></svg>', btn: '' },
981
+ error: { title: 'Error', desc: 'Something went wrong. Please try again.', icon: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="vt-w-5 vt-h-5 vt-text-error-primary"><circle cx="12" cy="12" r="10"/><line x1="12" x2="12" y1="8" y2="12"/><line x1="12" x2="12.01" y1="16" y2="16"/></svg>', btn: 'Retry' },
982
+ action: { title: 'Event created', desc: 'Sunday, December 03, 2023', icon: '', btn: 'Undo' }
983
+ };
984
+
985
+ var c = configs[type] || configs.default;
986
+ var toast = document.createElement('div');
987
+ toast.id = id;
988
+ toast.className = 'vt-flex vt-items-start vt-gap-3 vt-w-80 vt-p-4 vt-rounded-lg vt-border vt-border-border-default vt-bg-surfaces-surface vt-shadow-lg vt-mb-2';
989
+ toast.setAttribute('role', type === 'error' ? 'alert' : 'status');
990
+ toast.setAttribute('aria-live', type === 'error' ? 'assertive' : 'polite');
991
+ toast.setAttribute('aria-atomic', 'true');
992
+ toast.innerHTML =
993
+ (c.icon ? '<div class="vt-flex-shrink-0 vt-mt-0.5">' + c.icon + '</div>' : '') +
994
+ '<div class="vt-flex-1 vt-min-w-0">' +
995
+ '<p class="vt-text-sm vt-font-semibold">' + c.title + '</p>' +
996
+ '<p class="vt-text-xs vt-text-mains-quaternary vt-mt-0.5">' + c.desc + '</p>' +
997
+ '</div>' +
998
+ (c.btn ? '<button onclick="this.parentElement.remove()" class="vt-flex-shrink-0 vt-inline-flex vt-items-center vt-justify-center vt-rounded-lg vt-text-xs vt-font-medium vt-h-7 vt-px-2 vt-border vt-border-border-strong vt-bg-surfaces-surface vt-hover:bg-surfaces-subtle vt-transition-colors">' + c.btn + '</button>' : '') +
999
+ '<button onclick="this.parentElement.remove()" aria-label="Close" class="vt-flex-shrink-0 vt-p-0.5 vt-text-mains-quaternary vt-hover:text-mains-primary vt-transition-colors">' +
1000
+ '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="vt-w-3.5 vt-h-3.5"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>' +
1001
+ '</button>';
1002
+
1003
+ container.appendChild(toast);
1004
+ setTimeout(function() {
1005
+ var el = document.getElementById(id);
1006
+ if (el) el.remove();
1007
+ }, 4000);
1008
+ },
1009
+
1010
+ init: function() {
1011
+ // Expose showToast globally for onclick use
1012
+ window.showToast = function(type) { UCToast.show(type); };
1013
+ }
1014
+ };
1015
+
1016
+ // UCDialog — modal dialog with focus trap and keyboard support
1017
+ var UCDialog = {
1018
+ init: function() {
1019
+ // Expose functions globally for onclick use
1020
+ window.openDialog = function(id) {
1021
+ var el = document.getElementById(id);
1022
+ if (!el) return;
1023
+ if (el._keyHandler) document.removeEventListener('keydown', el._keyHandler);
1024
+ el._previousFocus = document.activeElement;
1025
+ el.classList.add('vt-open');
1026
+ el.removeAttribute('aria-hidden');
1027
+ el.setAttribute('role', 'dialog');
1028
+ el.setAttribute('aria-modal', 'true');
1029
+ var focusable = el.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
1030
+ if (focusable) focusable.focus();
1031
+ el._keyHandler = function(e) {
1032
+ if (e.key === 'Escape') { window.closeDialog(id); return; }
1033
+ if (e.key !== 'Tab') return;
1034
+ var all = el.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
1035
+ var first = all[0];
1036
+ var last = all[all.length - 1];
1037
+ if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }
1038
+ else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
1039
+ };
1040
+ document.addEventListener('keydown', el._keyHandler);
1041
+ };
1042
+
1043
+ window.closeDialog = function(id) {
1044
+ var el = document.getElementById(id);
1045
+ if (!el) return;
1046
+ el.classList.remove('vt-open');
1047
+ el.setAttribute('aria-hidden', 'true');
1048
+ if (el._keyHandler) document.removeEventListener('keydown', el._keyHandler);
1049
+ if (el._previousFocus && typeof el._previousFocus.focus === 'function') {
1050
+ el._previousFocus.focus();
1051
+ }
1052
+ el._keyHandler = null;
1053
+ el._previousFocus = null;
1054
+ };
1055
+ }
1056
+ };
1057
+
1058
+ // UCTreeView — expandable/collapsible tree nodes
1059
+ var UCTreeView = {
1060
+ init: function() {
1061
+ document.querySelectorAll('[data-tree]').forEach(function(tree) {
1062
+ if (tree.hasAttribute('data-tree-bound')) return;
1063
+ tree.setAttribute('data-tree-bound', 'true');
1064
+
1065
+ tree.querySelectorAll('[data-tree-branch]').forEach(function(branch) {
1066
+ var toggle = branch.querySelector(':scope > .vt-tree-toggle');
1067
+ if (!toggle) return;
1068
+ toggle.addEventListener('click', function() {
1069
+ branch.classList.toggle('open');
1070
+ });
1071
+ });
1072
+ });
1073
+ }
1074
+ };
1075
+
1076
+ // Auto-initialize all components
1077
+ (function() {
1078
+ function initAll() {
1079
+ UCAccordion.init();
1080
+ UCCalendar.init();
1081
+ UCChip.init();
1082
+ UCRadioGroup.init();
1083
+ UCTabs.init();
1084
+ UCCollapsible.init();
1085
+ UCCombobox.init();
1086
+ UCResizable.init();
1087
+ UCCarousel.init();
1088
+ UCDropdown.init();
1089
+ UCScrollView.init();
1090
+ UCNumberInput.init();
1091
+ UCPopover.init();
1092
+ UCSelect.init();
1093
+ UCSlider.init();
1094
+ UCToast.init();
1095
+ UCDialog.init();
1096
+ UCTreeView.init();
1097
+ }
1098
+
1099
+ if (document.readyState === 'loading') {
1100
+ document.addEventListener('DOMContentLoaded', initAll);
1101
+ } else {
1102
+ initAll();
1103
+ }
1104
+
1105
+ document.addEventListener('astro:after-swap', function() {
1106
+ initAll();
1107
+ });
1108
+ })();