ux4g-components-web 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1115 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * UX4G Runtime Module
5
+ *
6
+ * Framework-agnostic runtime that encapsulates ALL interactive behaviors
7
+ * from the vendor CDN scripts (ux4g.js + ux4g-custom.js).
8
+ *
9
+ * Uses a singleton guard (window.__UX4G_RUNTIME_INITIALIZED__) to ensure
10
+ * initialization happens exactly once.
11
+ */
12
+ const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined';
13
+ let activeListeners = [];
14
+ let activeObservers = [];
15
+ const Registry = new WeakMap();
16
+ // ─── Utilities ───────────────────────────────────────────────────────────────
17
+ const U = {
18
+ qs(sel, root = document) { return root.querySelector(sel); },
19
+ qsa(sel, root = document) { return Array.from(root.querySelectorAll(sel)); },
20
+ on(el, evt, cb, opts) { if (!el)
21
+ return; el.addEventListener(evt, cb, opts); activeListeners.push({ target: el, event: evt, handler: cb, options: opts }); },
22
+ off(el, evt, cb, opts) { if (!el)
23
+ return; el.removeEventListener(evt, cb, opts); },
24
+ closest(el, sel) { return el ? el.closest(sel) : null; },
25
+ isVisible(el) { const h = el; return !!(el && (h.offsetWidth || h.offsetHeight || el.getClientRects().length)); },
26
+ reflow(el) { return el.offsetHeight; },
27
+ attr(el, name, fallback = null) { if (!el)
28
+ return fallback; const v = el.getAttribute(name); return v == null ? fallback : v; },
29
+ data(el, key, fallback = null) {
30
+ if (!el)
31
+ return fallback;
32
+ const ux = el.getAttribute(`data-ux-${key}`);
33
+ if (ux != null)
34
+ return ux;
35
+ const bs = el.getAttribute(`data-bs-${key}`);
36
+ if (bs != null)
37
+ return bs;
38
+ const u4 = el.getAttribute(`ux4g-${key}`);
39
+ if (u4 != null)
40
+ return u4;
41
+ return fallback;
42
+ },
43
+ bool(v, fallback = false) { if (v == null)
44
+ return fallback; if (typeof v === 'boolean')
45
+ return v; const s = String(v).trim().toLowerCase(); if (s === '' || s === 'true' || s === '1')
46
+ return true; if (s === 'false' || s === '0')
47
+ return false; return fallback; },
48
+ num(v, fallback = 0) { const n = Number(v); return Number.isFinite(n) ? n : fallback; },
49
+ dispatch(el, name, detail) { if (!el)
50
+ return; el.dispatchEvent(new CustomEvent(name, { bubbles: true, cancelable: true, detail })); },
51
+ focusables(root) { if (!root)
52
+ return []; return U.qsa('a[href],area[href],button:not([disabled]),input:not([disabled]):not([type="hidden"]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"]),[contenteditable="true"]', root).filter(el => U.isVisible(el)); },
53
+ lockBody(lock) { if (lock)
54
+ document.body.classList.add('ux4g-scroll-lock');
55
+ else
56
+ document.body.classList.remove('ux4g-scroll-lock'); },
57
+ ensureBackdrop(kind) { let bd = U.qs(`.ux4g-backdrop[data-kind="${kind}"]`); if (!bd) {
58
+ bd = document.createElement('div');
59
+ bd.className = 'ux4g-backdrop';
60
+ bd.setAttribute('data-kind', kind);
61
+ document.body.appendChild(bd);
62
+ } return bd; },
63
+ removeBackdrop(kind) { const bd = U.qs(`.ux4g-backdrop[data-kind="${kind}"]`); if (bd)
64
+ bd.remove(); },
65
+ placeFloating(target, floating, placement = 'bottom', offset = 8) {
66
+ if (!target || !floating)
67
+ return;
68
+ const rect = target.getBoundingClientRect();
69
+ const od = floating.style.display;
70
+ const ov = floating.style.visibility;
71
+ floating.style.display = 'block';
72
+ floating.style.visibility = 'hidden';
73
+ const fr = floating.getBoundingClientRect();
74
+ floating.style.display = od;
75
+ floating.style.visibility = ov;
76
+ const vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
77
+ const vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0);
78
+ const clamp = (v, min, max) => Math.min(max, Math.max(min, v));
79
+ const ws = placement.split('-')[0];
80
+ const opp = { top: 'bottom', bottom: 'top', left: 'right', right: 'left' };
81
+ const tries = [placement];
82
+ if (opp[ws])
83
+ tries.push(placement.replace(ws, opp[ws]));
84
+ ['bottom', 'top', 'right', 'left'].forEach(s => { if (!tries.includes(s))
85
+ tries.push(s); });
86
+ const compute = (p) => {
87
+ let t = 0, l = 0;
88
+ const [side, align] = p.split('-');
89
+ if (side === 'top') {
90
+ t = rect.top - fr.height - offset;
91
+ l = align === 'start' ? rect.left : align === 'end' ? rect.right - fr.width : rect.left + (rect.width - fr.width) / 2;
92
+ }
93
+ else if (side === 'bottom') {
94
+ t = rect.bottom + offset;
95
+ l = align === 'start' ? rect.left : align === 'end' ? rect.right - fr.width : rect.left + (rect.width - fr.width) / 2;
96
+ }
97
+ else if (side === 'left') {
98
+ l = rect.left - fr.width - offset;
99
+ t = align === 'start' ? rect.top : align === 'end' ? rect.bottom - fr.height : rect.top + (rect.height - fr.height) / 2;
100
+ }
101
+ else if (side === 'right') {
102
+ l = rect.right + offset;
103
+ t = align === 'start' ? rect.top : align === 'end' ? rect.bottom - fr.height : rect.top + (rect.height - fr.height) / 2;
104
+ }
105
+ else {
106
+ t = rect.bottom + offset;
107
+ l = rect.left + (rect.width - fr.width) / 2;
108
+ p = 'bottom';
109
+ }
110
+ return { t, l, p };
111
+ };
112
+ let chosen = null;
113
+ for (const p of tries) {
114
+ const c = compute(p);
115
+ if (c.t >= 0 && (c.t + fr.height) <= vh && c.l >= 0 && (c.l + fr.width) <= vw) {
116
+ chosen = c;
117
+ break;
118
+ }
119
+ }
120
+ if (!chosen)
121
+ chosen = compute(placement);
122
+ const top = clamp(chosen.t, 8, Math.max(8, vh - fr.height - 8));
123
+ const left = clamp(chosen.l, 8, Math.max(8, vw - fr.width - 8));
124
+ floating.style.position = 'fixed';
125
+ floating.style.top = `${top}px`;
126
+ floating.style.left = `${left}px`;
127
+ floating.setAttribute('data-placement', chosen.p);
128
+ const isTooltip = floating.classList.contains('ux4g-tooltip');
129
+ const base = isTooltip ? 'ux4g-tooltip' : 'ux4g-popover';
130
+ Array.from(floating.classList).forEach(cls => { if (cls.startsWith(`${base}-`) && cls !== base)
131
+ floating.classList.remove(cls); });
132
+ floating.classList.add(`${base}-${chosen.p}`);
133
+ },
134
+ repositionMenu(container, menu) {
135
+ if (!menu)
136
+ return;
137
+ menu.style.top = '';
138
+ menu.style.bottom = '';
139
+ menu.style.left = '';
140
+ menu.style.right = '';
141
+ const vh = window.innerHeight;
142
+ const vw = window.innerWidth;
143
+ const mr = menu.getBoundingClientRect();
144
+ const cr = container.getBoundingClientRect();
145
+ if (mr.bottom > vh && cr.top > mr.height) {
146
+ menu.style.top = 'auto';
147
+ menu.style.bottom = '100%';
148
+ }
149
+ const ur = menu.getBoundingClientRect();
150
+ if (ur.right > vw) {
151
+ menu.style.left = 'auto';
152
+ menu.style.right = '0';
153
+ }
154
+ if (ur.left < 0) {
155
+ menu.style.left = '0';
156
+ menu.style.right = 'auto';
157
+ }
158
+ },
159
+ };
160
+ function getI(el, key) { return Registry.get(el)?.[key] || null; }
161
+ function setI(el, key, inst) { let m = Registry.get(el); if (!m) {
162
+ m = {};
163
+ Registry.set(el, m);
164
+ } m[key] = inst; }
165
+ function escapeHtml(s) { return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#039;'); }
166
+ class DropdownComponent {
167
+ constructor(toggle) {
168
+ this._open = false;
169
+ this.toggle = toggle;
170
+ this.menu = this._findMenu(toggle);
171
+ U.on(this.toggle, 'click', ((e) => { e.preventDefault(); this.toggleDropdown(); }));
172
+ U.on(document, 'click', ((e) => { if (!this._open)
173
+ return; const t = e.target; if (this.menu && (this.menu.contains(t) || this.toggle.contains(t)))
174
+ return; this.hide(); }));
175
+ U.on(document, 'keydown', ((e) => { if (!this._open)
176
+ return; if (e.key === 'Escape') {
177
+ this.hide();
178
+ this.toggle.focus();
179
+ } }));
180
+ }
181
+ _findMenu(toggle) { const p = toggle.parentElement; let m = p ? p.querySelector('.dropdown-menu') : null; if (!m) {
182
+ const t = U.data(toggle, 'target') || U.attr(toggle, 'aria-controls');
183
+ if (t && t.startsWith('#'))
184
+ m = U.qs(t);
185
+ } return m; }
186
+ show() { if (!this.menu)
187
+ return; this._open = true; this.toggle.classList.add('show'); this.menu.classList.add('show'); this.toggle.setAttribute('aria-expanded', 'true'); U.placeFloating(this.toggle, this.menu, U.data(this.toggle, 'placement', 'bottom') || 'bottom', U.num(U.data(this.toggle, 'offset', '6'), 6)); U.dispatch(this.toggle, 'ux4g.dropdown.shown', { menu: this.menu }); }
188
+ hide() { if (!this.menu)
189
+ return; this._open = false; this.toggle.classList.remove('show'); this.menu.classList.remove('show'); this.toggle.setAttribute('aria-expanded', 'false'); U.dispatch(this.toggle, 'ux4g.dropdown.hidden', { menu: this.menu }); }
190
+ toggleDropdown() { this._open ? this.hide() : this.show(); }
191
+ static getOrCreate(el) { let i = getI(el, 'dropdown'); if (!i) {
192
+ i = new DropdownComponent(el);
193
+ setI(el, 'dropdown', i);
194
+ } return i; }
195
+ }
196
+ class CollapseComponent {
197
+ constructor(trigger) {
198
+ this.trigger = trigger;
199
+ this.target = this._resolveTarget(trigger);
200
+ this.parentSel = U.data(this.target, 'parent') || U.data(this.trigger, 'parent');
201
+ this.duration = this._readDur(this.target, 200);
202
+ U.on(this.trigger, 'click', ((e) => { e.preventDefault(); this.toggle(); }));
203
+ }
204
+ _resolveTarget(tr) { const s = U.data(tr, 'target') || U.attr(tr, 'href') || U.attr(tr, 'aria-controls') || U.attr(tr, 'ux4g-target'); if (s && s.startsWith('#'))
205
+ return U.qs(s); if (s)
206
+ return U.qs('#' + s); return null; }
207
+ _readDur(el, fb) { if (!el)
208
+ return fb; const d = getComputedStyle(el).transitionDuration || ''; const ms = d.includes('ms') ? parseFloat(d) : (d.includes('s') ? parseFloat(d) * 1000 : NaN); return Number.isFinite(ms) && ms > 0 ? ms : fb; }
209
+ show() {
210
+ if (!this.target)
211
+ return;
212
+ const t = this.target;
213
+ if (this.parentSel) {
214
+ const p = U.qs(this.parentSel);
215
+ if (p)
216
+ U.qsa('.collapse.show', p).forEach(el => { if (el === this.target)
217
+ return; el.classList.remove('show'); if (el.id) {
218
+ const id = el.id;
219
+ U.qsa(`[data-bs-target="#${id}"],[data-ux-target="#${id}"],[ux4g-target="#${id}"],a[href="#${id}"]`).forEach(x => { x.classList.add('collapsed'); x.setAttribute('aria-expanded', 'false'); });
220
+ } });
221
+ }
222
+ t.classList.add('collapsing');
223
+ t.classList.remove('collapse');
224
+ t.style.height = '0px';
225
+ U.reflow(t);
226
+ t.style.height = t.scrollHeight + 'px';
227
+ this.trigger.classList.remove('collapsed');
228
+ this.trigger.setAttribute('aria-expanded', 'true');
229
+ setTimeout(() => { t.classList.remove('collapsing'); t.classList.add('collapse', 'show'); t.style.height = ''; U.dispatch(t, 'ux4g.collapse.shown', {}); }, this.duration);
230
+ }
231
+ hide() {
232
+ if (!this.target)
233
+ return;
234
+ const t = this.target;
235
+ t.style.height = t.getBoundingClientRect().height + 'px';
236
+ U.reflow(t);
237
+ t.classList.add('collapsing');
238
+ t.classList.remove('collapse', 'show');
239
+ this.trigger.classList.add('collapsed');
240
+ this.trigger.setAttribute('aria-expanded', 'false');
241
+ setTimeout(() => { t.style.height = '0px'; }, 10);
242
+ setTimeout(() => { t.classList.remove('collapsing'); t.classList.add('collapse'); t.style.height = ''; U.dispatch(t, 'ux4g.collapse.hidden', {}); }, this.duration);
243
+ }
244
+ toggle() { if (!this.target)
245
+ return; this.target.classList.contains('show') ? this.hide() : this.show(); }
246
+ static getOrCreate(el) { let i = getI(el, 'collapse'); if (!i) {
247
+ i = new CollapseComponent(el);
248
+ setI(el, 'collapse', i);
249
+ } return i; }
250
+ }
251
+ class ModalComponent {
252
+ constructor(el) {
253
+ this._shown = false;
254
+ this._bdKind = 'modal';
255
+ this._lastFocus = null;
256
+ this.el = el;
257
+ this.duration = 250;
258
+ U.on(this.el, 'click', ((e) => { const d = U.closest(e.target, '[data-bs-dismiss="modal"],[data-ux-dismiss="modal"],.close-modal'); if (d) {
259
+ e.preventDefault();
260
+ this.hide();
261
+ } }));
262
+ U.on(document, 'keydown', ((e) => { if (!this._shown)
263
+ return; if (e.key === 'Escape') {
264
+ if (U.bool(U.data(this.el, 'keyboard', 'true'), true))
265
+ this.hide();
266
+ }
267
+ else if (e.key === 'Tab') {
268
+ this._trapTab(e);
269
+ } }));
270
+ }
271
+ _trapTab(e) { const f = U.focusables(this.el); if (!f.length)
272
+ return; const first = f[0], last = f[f.length - 1]; if (e.shiftKey && document.activeElement === first) {
273
+ e.preventDefault();
274
+ last.focus();
275
+ }
276
+ else if (!e.shiftKey && document.activeElement === last) {
277
+ e.preventDefault();
278
+ first.focus();
279
+ } }
280
+ show(trigger) {
281
+ if (this._shown)
282
+ return;
283
+ this._shown = true;
284
+ this._lastFocus = document.activeElement;
285
+ const bo = U.data(this.el, 'backdrop', 'true');
286
+ if (bo !== 'false') {
287
+ const bd = U.ensureBackdrop(this._bdKind);
288
+ bd.classList.add('show');
289
+ U.on(bd, 'click', (() => { if (bo === 'static')
290
+ return; this.hide(); }));
291
+ }
292
+ U.lockBody(true);
293
+ this.el.style.display = 'block';
294
+ this.el.removeAttribute('aria-hidden');
295
+ this.el.setAttribute('aria-modal', 'true');
296
+ this.el.setAttribute('role', this.el.getAttribute('role') || 'dialog');
297
+ U.reflow(this.el);
298
+ this.el.classList.add('show');
299
+ if (U.bool(U.data(this.el, 'focus', 'true'), true)) {
300
+ const f = U.focusables(this.el);
301
+ (f[0] || this.el).focus({ preventScroll: true });
302
+ }
303
+ U.dispatch(this.el, 'ux4g.modal.shown', { relatedTarget: trigger || null });
304
+ }
305
+ hide() {
306
+ if (!this._shown)
307
+ return;
308
+ this._shown = false;
309
+ this.el.classList.remove('show');
310
+ this.el.setAttribute('aria-hidden', 'true');
311
+ this.el.removeAttribute('aria-modal');
312
+ setTimeout(() => { this.el.style.display = 'none'; U.lockBody(false); U.removeBackdrop(this._bdKind); if (this._lastFocus?.focus)
313
+ this._lastFocus.focus({ preventScroll: true }); U.dispatch(this.el, 'ux4g.modal.hidden', {}); }, this.duration);
314
+ }
315
+ toggle(trigger) { this._shown ? this.hide() : this.show(trigger); }
316
+ static getOrCreate(el) { let i = getI(el, 'modal'); if (!i) {
317
+ i = new ModalComponent(el);
318
+ setI(el, 'modal', i);
319
+ } return i; }
320
+ }
321
+ class OffcanvasComponent {
322
+ constructor(el) {
323
+ this._shown = false;
324
+ this._bdKind = 'offcanvas';
325
+ this._lastFocus = null;
326
+ this.duration = 250;
327
+ this.el = el;
328
+ U.on(this.el, 'click', ((e) => { const d = U.closest(e.target, '[data-bs-dismiss="offcanvas"],[data-ux-dismiss="offcanvas"]'); if (d) {
329
+ e.preventDefault();
330
+ this.hide();
331
+ } }));
332
+ U.on(document, 'keydown', ((e) => { if (!this._shown)
333
+ return; if (e.key === 'Escape') {
334
+ if (U.bool(U.data(this.el, 'keyboard', 'true'), true))
335
+ this.hide();
336
+ }
337
+ else if (e.key === 'Tab') {
338
+ const f = U.focusables(this.el);
339
+ if (!f.length)
340
+ return;
341
+ const first = f[0], last = f[f.length - 1];
342
+ if (e.shiftKey && document.activeElement === first) {
343
+ e.preventDefault();
344
+ last.focus();
345
+ }
346
+ else if (!e.shiftKey && document.activeElement === last) {
347
+ e.preventDefault();
348
+ first.focus();
349
+ }
350
+ } }));
351
+ }
352
+ show(trigger) {
353
+ if (this._shown)
354
+ return;
355
+ this._shown = true;
356
+ this._lastFocus = document.activeElement;
357
+ const bo = U.data(this.el, 'backdrop', 'true');
358
+ if (bo !== 'false') {
359
+ const bd = U.ensureBackdrop(this._bdKind);
360
+ bd.classList.add('show');
361
+ U.on(bd, 'click', (() => { if (bo === 'static')
362
+ return; this.hide(); }));
363
+ }
364
+ U.lockBody(true);
365
+ this.el.style.visibility = 'visible';
366
+ this.el.classList.add('show');
367
+ this.el.setAttribute('aria-modal', 'true');
368
+ if (U.bool(U.data(this.el, 'focus', 'true'), true)) {
369
+ const f = U.focusables(this.el);
370
+ (f[0] || this.el).focus({ preventScroll: true });
371
+ }
372
+ U.dispatch(this.el, 'ux4g.offcanvas.shown', { relatedTarget: trigger || null });
373
+ }
374
+ hide() {
375
+ if (!this._shown)
376
+ return;
377
+ this._shown = false;
378
+ this.el.classList.remove('show');
379
+ this.el.removeAttribute('aria-modal');
380
+ setTimeout(() => { this.el.style.visibility = ''; U.lockBody(false); U.removeBackdrop(this._bdKind); if (this._lastFocus?.focus)
381
+ this._lastFocus.focus({ preventScroll: true }); U.dispatch(this.el, 'ux4g.offcanvas.hidden', {}); }, this.duration);
382
+ }
383
+ toggle(trigger) { this._shown ? this.hide() : this.show(trigger); }
384
+ static getOrCreate(el) { let i = getI(el, 'offcanvas'); if (!i) {
385
+ i = new OffcanvasComponent(el);
386
+ setI(el, 'offcanvas', i);
387
+ } return i; }
388
+ }
389
+ class FloatingComponent {
390
+ constructor(el, kind) {
391
+ this._open = false;
392
+ this._floating = null;
393
+ this._onWin = null;
394
+ this._raf = null;
395
+ this.el = el;
396
+ this.kind = kind;
397
+ this.placement = U.data(el, 'placement', kind === 'tooltip' ? 'top' : 'right') || (kind === 'tooltip' ? 'top' : 'right');
398
+ this.offset = U.num(U.data(el, 'offset', '8'), 8);
399
+ this.triggerMode = U.data(el, 'trigger', kind === 'tooltip' ? 'hover focus' : 'click') || (kind === 'tooltip' ? 'hover focus' : 'click');
400
+ this.html = U.bool(U.data(el, 'html', 'false'), false);
401
+ this._bind();
402
+ }
403
+ _getContent() {
404
+ const content = U.data(this.el, 'content');
405
+ if (this.kind === 'popover') {
406
+ const title = U.data(this.el, 'title') || this.el.getAttribute('title') || '';
407
+ const body = content || this.el.getAttribute('data-content') || '';
408
+ const t = this.html ? String(title) : escapeHtml(title);
409
+ const b = this.html ? String(body) : escapeHtml(body);
410
+ return `<div class="ux4g-popover-header">${t}</div><div class="ux4g-popover-body">${b}</div>`;
411
+ }
412
+ const t = content != null ? content : (this.el.getAttribute('title') || '');
413
+ return this.html ? String(t) : escapeHtml(t);
414
+ }
415
+ _create() { if (this._floating)
416
+ return; const div = document.createElement('div'); div.className = this.kind === 'tooltip' ? 'ux4g-tooltip' : 'ux4g-popover'; div.setAttribute('role', this.kind === 'tooltip' ? 'tooltip' : 'dialog'); div.innerHTML = this._getContent() || ''; document.body.appendChild(div); this._floating = div; }
417
+ show() {
418
+ if (this._open)
419
+ return;
420
+ this._open = true;
421
+ if (this.kind === 'tooltip') {
422
+ const t = this.el.getAttribute('title');
423
+ if (t != null) {
424
+ this.el.setAttribute('data-ux-original-title', t);
425
+ this.el.removeAttribute('title');
426
+ }
427
+ }
428
+ this._create();
429
+ this._floating.style.display = 'block';
430
+ this._floating.classList.add('show');
431
+ const update = () => { if (!this._open)
432
+ return; U.placeFloating(this.el, this._floating, this.placement, this.offset); this._raf = requestAnimationFrame(update); };
433
+ this._onWin = update;
434
+ this._raf = requestAnimationFrame(update);
435
+ U.on(window, 'scroll', this._onWin, { capture: true, passive: true });
436
+ U.on(window, 'resize', this._onWin);
437
+ U.dispatch(this.el, `ux4g.${this.kind}.shown`, {});
438
+ }
439
+ hide() {
440
+ if (!this._open)
441
+ return;
442
+ this._open = false;
443
+ if (this._floating) {
444
+ this._floating.classList.remove('show');
445
+ this._floating.style.display = 'none';
446
+ }
447
+ if (this.kind === 'tooltip') {
448
+ const ot = this.el.getAttribute('data-ux-original-title');
449
+ if (ot != null) {
450
+ this.el.setAttribute('title', ot);
451
+ this.el.removeAttribute('data-ux-original-title');
452
+ }
453
+ }
454
+ if (this._raf) {
455
+ cancelAnimationFrame(this._raf);
456
+ this._raf = null;
457
+ }
458
+ if (this._onWin) {
459
+ U.off(window, 'scroll', this._onWin, { capture: true });
460
+ U.off(window, 'resize', this._onWin);
461
+ this._onWin = null;
462
+ }
463
+ U.dispatch(this.el, `ux4g.${this.kind}.hidden`, {});
464
+ }
465
+ toggle() { this._open ? this.hide() : this.show(); }
466
+ _bind() {
467
+ let triggers = String(this.triggerMode).split(/\s+/).filter(Boolean);
468
+ if (this.kind === 'popover') {
469
+ triggers = triggers.filter(t => t !== 'hover');
470
+ if (!triggers.length)
471
+ triggers = ['click'];
472
+ }
473
+ if (triggers.includes('hover')) {
474
+ U.on(this.el, 'mouseenter', (() => this.show()));
475
+ U.on(this.el, 'mouseleave', (() => this.hide()));
476
+ }
477
+ if (triggers.includes('focus')) {
478
+ U.on(this.el, 'focus', (() => this.show()));
479
+ U.on(this.el, 'blur', (() => this.hide()));
480
+ }
481
+ if (triggers.includes('click')) {
482
+ U.on(this.el, 'click', ((e) => { e.preventDefault(); this.toggle(); }));
483
+ U.on(document, 'click', ((e) => { if (!this._open)
484
+ return; const t = e.target; if (this.el.contains(t) || (this._floating && this._floating.contains(t)))
485
+ return; this.hide(); }));
486
+ U.on(document, 'keydown', ((e) => { if (!this._open)
487
+ return; if (e.key === 'Escape')
488
+ this.hide(); }));
489
+ }
490
+ }
491
+ static getOrCreate(el, kind) { let i = getI(el, kind); if (!i) {
492
+ i = new FloatingComponent(el, kind);
493
+ setI(el, kind, i);
494
+ } return i; }
495
+ }
496
+ class ToastComponent {
497
+ constructor(el) {
498
+ this._timer = null;
499
+ this.el = el;
500
+ U.on(this.el, 'click', ((e) => { const d = U.closest(e.target, '[data-bs-dismiss="toast"],[data-ux-dismiss="toast"],.close-toast'); if (d) {
501
+ e.preventDefault();
502
+ this.hide();
503
+ } }));
504
+ }
505
+ show() { this.el.classList.add('show'); this.el.classList.remove('hide'); const ah = U.bool(U.data(this.el, 'autohide', 'true'), true); const delay = U.num(U.data(this.el, 'delay', '5000'), 5000); if (ah) {
506
+ if (this._timer)
507
+ clearTimeout(this._timer);
508
+ this._timer = setTimeout(() => this.hide(), delay);
509
+ } U.dispatch(this.el, 'ux4g.toast.shown', {}); }
510
+ hide() { this.el.classList.remove('show'); this.el.classList.add('hide'); if (this._timer)
511
+ clearTimeout(this._timer); U.dispatch(this.el, 'ux4g.toast.hidden', {}); }
512
+ static getOrCreate(el) { let i = getI(el, 'toast'); if (!i) {
513
+ i = new ToastComponent(el);
514
+ setI(el, 'toast', i);
515
+ } return i; }
516
+ }
517
+ class CarouselComponent {
518
+ constructor(el) {
519
+ this._timer = null;
520
+ this.el = el;
521
+ this.items = U.qsa('.carousel-item', el);
522
+ this.indicators = U.qsa('[data-bs-slide-to],[data-ux-slide-to]', el);
523
+ this.interval = U.num(U.data(el, 'interval', '5000'), 5000);
524
+ this.wrap = U.bool(U.data(el, 'wrap', 'true'), true);
525
+ U.on(el, 'click', ((e) => { const t = e.target; if (U.closest(t, '[data-bs-slide="prev"],[data-ux-slide="prev"]')) {
526
+ e.preventDefault();
527
+ this.prev();
528
+ } if (U.closest(t, '[data-bs-slide="next"],[data-ux-slide="next"]')) {
529
+ e.preventDefault();
530
+ this.next();
531
+ } const ind = U.closest(t, '[data-bs-slide-to],[data-ux-slide-to]'); if (ind) {
532
+ e.preventDefault();
533
+ this.to(U.num(ind.getAttribute('data-bs-slide-to') ?? ind.getAttribute('data-ux-slide-to'), 0));
534
+ } }));
535
+ if (U.data(el, 'pause', 'hover') === 'hover') {
536
+ U.on(el, 'mouseenter', (() => this._stop()));
537
+ U.on(el, 'mouseleave', (() => this._start()));
538
+ }
539
+ if (U.data(el, 'ride') === 'carousel')
540
+ this._start();
541
+ }
542
+ _activeIndex() { const idx = this.items.findIndex(i => i.classList.contains('active')); return idx >= 0 ? idx : 0; }
543
+ _setActive(ni) {
544
+ if (!this.items.length)
545
+ return;
546
+ const cur = this._activeIndex();
547
+ if (ni < 0)
548
+ ni = this.wrap ? this.items.length - 1 : 0;
549
+ if (ni >= this.items.length)
550
+ ni = this.wrap ? 0 : this.items.length - 1;
551
+ if (cur === ni)
552
+ return;
553
+ this.items[cur]?.classList.remove('active');
554
+ this.items[ni]?.classList.add('active');
555
+ this.indicators.forEach(ind => ind.classList.remove('active'));
556
+ const mi = this.indicators.find(x => U.num(x.getAttribute('data-bs-slide-to') ?? x.getAttribute('data-ux-slide-to'), -1) === ni);
557
+ if (mi)
558
+ mi.classList.add('active');
559
+ U.dispatch(this.el, 'ux4g.carousel.slid', { from: cur, to: ni });
560
+ }
561
+ next() { this._setActive(this._activeIndex() + 1); }
562
+ prev() { this._setActive(this._activeIndex() - 1); }
563
+ to(i) { this._setActive(i); }
564
+ _start() { if (this._timer || this.interval <= 0)
565
+ return; this._timer = setInterval(() => this.next(), this.interval); }
566
+ _stop() { if (this._timer) {
567
+ clearInterval(this._timer);
568
+ this._timer = null;
569
+ } }
570
+ static getOrCreate(el) { let i = getI(el, 'carousel'); if (!i) {
571
+ i = new CarouselComponent(el);
572
+ setI(el, 'carousel', i);
573
+ } return i; }
574
+ }
575
+ class TabComponent {
576
+ constructor(el) {
577
+ this.el = el;
578
+ U.on(el, 'click', ((e) => { e.preventDefault(); this.show(); }));
579
+ U.on(el, 'keydown', ((e) => { if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight')
580
+ return; const list = U.closest(this.el, ".nav, [role='tablist']"); if (!list)
581
+ return; const tabs = U.qsa("[data-bs-toggle='tab'],[data-ux-toggle='tab'],[role='tab']", list); const idx = tabs.indexOf(this.el); if (idx < 0)
582
+ return; e.preventDefault(); const n = e.key === 'ArrowRight' ? idx + 1 : idx - 1; const wi = (n + tabs.length) % tabs.length; tabs[wi].focus(); TabComponent.getOrCreate(tabs[wi]).show(); }));
583
+ }
584
+ _target() { const s = U.data(this.el, 'target') || U.attr(this.el, 'href') || U.attr(this.el, 'data-target'); if (s && s.startsWith('#'))
585
+ return U.qs(s); const c = this.el.getAttribute('aria-controls'); if (c)
586
+ return U.qs('#' + c); return null; }
587
+ show() {
588
+ const list = U.closest(this.el, ".nav, [role='tablist']");
589
+ const pane = this._target();
590
+ if (!list || !pane)
591
+ return;
592
+ U.qsa("[data-bs-toggle='tab'],[data-ux-toggle='tab'],[role='tab']", list).forEach(t => { t.classList.remove('active'); t.setAttribute('aria-selected', 'false'); t.setAttribute('tabindex', '-1'); });
593
+ this.el.classList.add('active');
594
+ this.el.setAttribute('aria-selected', 'true');
595
+ this.el.setAttribute('tabindex', '0');
596
+ const container = U.closest(pane, '.tab-content') || pane.parentElement;
597
+ if (container)
598
+ U.qsa('.tab-pane', container).forEach(p => p.classList.remove('active', 'show'));
599
+ pane.classList.add('active', 'show');
600
+ U.dispatch(this.el, 'ux4g.tab.shown', { relatedTarget: pane });
601
+ }
602
+ static getOrCreate(el) { let i = getI(el, 'tab'); if (!i) {
603
+ i = new TabComponent(el);
604
+ setI(el, 'tab', i);
605
+ } return i; }
606
+ }
607
+ class ScrollSpyComponent {
608
+ constructor(el) {
609
+ this._links = [];
610
+ this._sections = [];
611
+ this._io = null;
612
+ this.el = el;
613
+ this.targetSel = U.data(el, 'target');
614
+ this.offset = U.num(U.data(el, 'offset', '10'), 10);
615
+ this.refresh();
616
+ this._bind();
617
+ }
618
+ refresh() { const nav = this.targetSel ? U.qs(this.targetSel) : null; if (!nav)
619
+ return; this._links = U.qsa('a[href^="#"]', nav).filter(a => (a.getAttribute('href') || '').length > 1); this._sections = this._links.map(a => U.qs(a.getAttribute('href'))).filter(Boolean); }
620
+ _activate(id) { const nav = this.targetSel ? U.qs(this.targetSel) : null; if (!nav)
621
+ return; this._links.forEach(a => a.classList.remove('active')); const link = this._links.find(a => a.getAttribute('href') === '#' + id); if (link)
622
+ link.classList.add('active'); }
623
+ _bind() {
624
+ const container = (this.el === document.body || this.el === document.documentElement) ? window : this.el;
625
+ if ('IntersectionObserver' in window) {
626
+ const root = container === window ? null : this.el;
627
+ this._io = new IntersectionObserver((entries) => { const v = entries.filter(e => e.isIntersecting).sort((a, b) => b.intersectionRatio - a.intersectionRatio)[0]; if (v?.target?.id)
628
+ this._activate(v.target.id); }, { root, rootMargin: `-${this.offset}px 0px -60% 0px`, threshold: [0.1, 0.25, 0.5, 0.75] });
629
+ this._sections.forEach(s => this._io.observe(s));
630
+ activeObservers.push(this._io);
631
+ return;
632
+ }
633
+ const onScroll = () => { const st = container === window ? window.pageYOffset : this.el.scrollTop; let active = null; for (const s of this._sections) {
634
+ if (st + this.offset >= s.getBoundingClientRect().top + window.pageYOffset)
635
+ active = s;
636
+ } if (active?.id)
637
+ this._activate(active.id); };
638
+ U.on(container, 'scroll', onScroll, { passive: true });
639
+ onScroll();
640
+ }
641
+ static getOrCreate(el) { let i = getI(el, 'scrollspy'); if (!i) {
642
+ i = new ScrollSpyComponent(el);
643
+ setI(el, 'scrollspy', i);
644
+ } return i; }
645
+ }
646
+ class TableComponent {
647
+ constructor(el) { this.el = el; this._bindSort(); this._bindSelection(); }
648
+ _bindSort() { const cols = U.qsa('.ux4g-table-sortable th[data-sort]', this.el); cols.forEach(th => { U.on(th, 'click', ((e) => { if (U.closest(e.target, '.ux4g-table-filter-icon,.ux4g-search-input,.ux4g-search-clear'))
649
+ return; const cur = U.attr(th, 'data-sort', 'none'); const next = cur === 'asc' ? 'desc' : 'asc'; cols.forEach(o => { if (o !== th)
650
+ o.setAttribute('data-sort', 'none'); }); th.setAttribute('data-sort', next); const tbody = U.qs('tbody', this.el); if (tbody) {
651
+ const trs = Array.from(tbody.querySelectorAll('tr'));
652
+ const ci = Array.from(th.parentNode.children).indexOf(th);
653
+ trs.sort((a, b) => { const at = (a.children[ci]?.textContent || '').trim(); const bt = (b.children[ci]?.textContent || '').trim(); const an = Number(at.replace(/[₹$,\s]/g, '')); const bn = Number(bt.replace(/[₹$,\s]/g, '')); if (!isNaN(an) && !isNaN(bn))
654
+ return next === 'asc' ? an - bn : bn - an; const c = at.localeCompare(bt, undefined, { numeric: true, sensitivity: 'base' }); return next === 'asc' ? c : -c; });
655
+ trs.forEach(tr => tbody.appendChild(tr));
656
+ } U.dispatch(this.el, 'ux4g.table.sort', { column: th, direction: next }); })); }); }
657
+ _bindSelection() {
658
+ const sa = U.qs('thead .ux4g-checkbox', this.el);
659
+ if (!sa)
660
+ return;
661
+ const rcs = U.qsa('tbody .ux4g-checkbox', this.el);
662
+ if (!rcs.length)
663
+ return;
664
+ const upd = () => { let c = 0; rcs.forEach(cb => { const tr = U.closest(cb, 'tr'); if (cb.checked) {
665
+ c++;
666
+ if (tr)
667
+ tr.classList.add('ux4g-is-selected');
668
+ }
669
+ else {
670
+ if (tr)
671
+ tr.classList.remove('ux4g-is-selected');
672
+ } }); sa.checked = c === rcs.length; sa.indeterminate = c > 0 && c < rcs.length; };
673
+ U.on(sa, 'change', (() => { rcs.forEach(cb => { cb.checked = sa.checked; }); upd(); }));
674
+ rcs.forEach(cb => U.on(cb, 'change', upd));
675
+ upd();
676
+ }
677
+ static getOrCreate(el) { let i = getI(el, 'table'); if (!i) {
678
+ i = new TableComponent(el);
679
+ setI(el, 'table', i);
680
+ } return i; }
681
+ }
682
+ class ListComponent {
683
+ constructor(el) {
684
+ this.el = el;
685
+ U.on(this.el, 'click', ((e) => {
686
+ const t = e.target;
687
+ const item = U.closest(t, '.ux4g-list-item-row') || U.closest(t, '.ux4g-list-select-item');
688
+ if (!item)
689
+ return;
690
+ const isMulti = this.el.classList.contains('ux4g-multiselect') || this.el.classList.contains('ux4g-list-multiselect');
691
+ if (t.tagName === 'INPUT')
692
+ return;
693
+ const cb = U.qs('input[type="checkbox"]', item);
694
+ if (isMulti) {
695
+ const active = item.classList.toggle('active');
696
+ if (cb) {
697
+ cb.checked = active;
698
+ cb.dispatchEvent(new Event('change', { bubbles: true }));
699
+ }
700
+ }
701
+ else {
702
+ const was = item.classList.contains('active');
703
+ U.qsa('.ux4g-list-item-row,.ux4g-list-select-item', this.el).forEach(i => { i.classList.remove('active'); const inp = U.qs('input', i); if (inp)
704
+ inp.checked = false; });
705
+ if (!was) {
706
+ item.classList.add('active');
707
+ if (cb) {
708
+ cb.checked = true;
709
+ cb.dispatchEvent(new Event('change', { bubbles: true }));
710
+ }
711
+ }
712
+ }
713
+ U.dispatch(this.el, 'ux4g.list.change', { item, active: item.classList.contains('active') });
714
+ }));
715
+ }
716
+ static getOrCreate(el) { let i = getI(el, 'list'); if (!i) {
717
+ i = new ListComponent(el);
718
+ setI(el, 'list', i);
719
+ } return i; }
720
+ }
721
+ // ─── Init + Custom Behaviors + Public API ────────────────────────────────────
722
+ function init(root = document) {
723
+ U.qsa('[data-bs-toggle="dropdown"],[data-ux-toggle="dropdown"]', root).forEach(el => DropdownComponent.getOrCreate(el));
724
+ U.qsa('[data-bs-toggle="collapse"],[data-ux-toggle="collapse"],[ux4g-toggle="collapse"]', root).forEach(el => CollapseComponent.getOrCreate(el));
725
+ U.qsa('[data-bs-toggle="tab"],[data-ux-toggle="tab"]', root).forEach(el => TabComponent.getOrCreate(el));
726
+ U.qsa('[data-bs-toggle="tooltip"],[data-ux-toggle="tooltip"]', root).forEach(el => FloatingComponent.getOrCreate(el, 'tooltip'));
727
+ U.qsa('[data-bs-toggle="popover"],[data-ux-toggle="popover"]', root).forEach(el => FloatingComponent.getOrCreate(el, 'popover'));
728
+ U.qsa('.toast', root).forEach(el => ToastComponent.getOrCreate(el));
729
+ U.qsa('.carousel', root).forEach(el => CarouselComponent.getOrCreate(el));
730
+ U.qsa('[data-bs-spy="scroll"],[data-ux-spy="scroll"]', root).forEach(el => ScrollSpyComponent.getOrCreate(el));
731
+ U.qsa('.ux4g-table', root).forEach(el => TableComponent.getOrCreate(el));
732
+ U.qsa('.ux4g-list', root).forEach(el => ListComponent.getOrCreate(el));
733
+ }
734
+ function initDelegatedToggles() {
735
+ U.on(document, 'click', ((e) => {
736
+ const t = e.target;
737
+ const mb = U.closest(t, '[data-bs-toggle="modal"],[data-ux-toggle="modal"]');
738
+ if (mb) {
739
+ e.preventDefault();
740
+ const s = U.data(mb, 'target') || U.attr(mb, 'href');
741
+ const el = s && s.startsWith('#') ? U.qs(s) : null;
742
+ if (el)
743
+ ModalComponent.getOrCreate(el).toggle(mb);
744
+ return;
745
+ }
746
+ const om = U.closest(t, '.open-modal');
747
+ if (om) {
748
+ e.preventDefault();
749
+ const el = U.qs('#exampleModal');
750
+ if (el)
751
+ ModalComponent.getOrCreate(el).show(om);
752
+ return;
753
+ }
754
+ const ob = U.closest(t, '[data-bs-toggle="offcanvas"],[data-ux-toggle="offcanvas"]');
755
+ if (ob) {
756
+ e.preventDefault();
757
+ const s = U.data(ob, 'target') || U.attr(ob, 'href');
758
+ const el = s && s.startsWith('#') ? U.qs(s) : null;
759
+ if (el)
760
+ OffcanvasComponent.getOrCreate(el).toggle(ob);
761
+ return;
762
+ }
763
+ const ct = U.closest(t, '.close-toast');
764
+ if (ct) {
765
+ e.preventDefault();
766
+ const el = U.closest(ct, '.toast');
767
+ if (el)
768
+ ToastComponent.getOrCreate(el).hide();
769
+ return;
770
+ }
771
+ const cm = U.closest(t, '.close-modal');
772
+ if (cm) {
773
+ e.preventDefault();
774
+ const el = U.closest(cm, '.modal');
775
+ if (el)
776
+ ModalComponent.getOrCreate(el).hide();
777
+ return;
778
+ }
779
+ }));
780
+ }
781
+ function initCustomBehaviors() {
782
+ // Manual tooltip repositioning
783
+ U.on(document.body, 'mouseover', ((e) => { const w = U.closest(e.target, '.ux4g-tooltip-wrapper'); if (w) {
784
+ const tt = U.qs('.ux4g-tooltip', w);
785
+ if (tt && tt.dataset.uxAdjusted !== 'true') {
786
+ const vw = window.innerWidth;
787
+ const vh = window.innerHeight;
788
+ tt.style.transition = 'none';
789
+ tt.style.display = 'flex';
790
+ tt.style.opacity = '0';
791
+ tt.offsetHeight;
792
+ const r = tt.getBoundingClientRect();
793
+ let sx = 0, sy = 0;
794
+ if (r.left < 18)
795
+ sx = 18 - r.left;
796
+ else if (r.right > vw - 18)
797
+ sx = (vw - 18) - r.right;
798
+ if (r.top < 18)
799
+ sy = 18 - r.top;
800
+ else if (r.bottom > vh - 18)
801
+ sy = (vh - 18) - r.bottom;
802
+ if (sx || sy) {
803
+ tt.style.transform = `translate(${sx}px, ${sy}px)`;
804
+ tt.dataset.uxAdjusted = 'true';
805
+ }
806
+ tt.style.display = '';
807
+ tt.style.opacity = '';
808
+ setTimeout(() => { tt.style.transition = ''; }, 50);
809
+ }
810
+ } }));
811
+ U.on(document.body, 'mouseout', ((e) => { const w = U.closest(e.target, '.ux4g-tooltip-wrapper'); if (w && !w.contains(e.relatedTarget)) {
812
+ const tt = U.qs('.ux4g-tooltip', w);
813
+ if (tt) {
814
+ tt.style.transform = '';
815
+ delete tt.dataset.uxAdjusted;
816
+ }
817
+ } }));
818
+ // Breadcrumb dropdowns
819
+ const bds = U.qsa('.ux4g-breadcrumb-dropdown');
820
+ if (bds.length) {
821
+ const closeBd = (d) => { const tg = U.qs('.ux4g-breadcrumb-toggle', d); const mn = U.qs('.ux4g-breadcrumb-menu', d); if (tg && mn) {
822
+ tg.classList.remove('show');
823
+ mn.classList.remove('show');
824
+ tg.setAttribute('aria-expanded', 'false');
825
+ } };
826
+ bds.forEach(d => {
827
+ const tg = U.qs('.ux4g-breadcrumb-toggle', d);
828
+ const mn = U.qs('.ux4g-breadcrumb-menu', d);
829
+ if (!tg || !mn)
830
+ return;
831
+ U.on(tg, 'click', ((e) => { e.preventDefault(); e.stopPropagation(); const isOpen = mn.classList.contains('show'); bds.forEach(x => closeBd(x)); if (!isOpen) {
832
+ tg.classList.add('show');
833
+ mn.classList.add('show');
834
+ tg.setAttribute('aria-expanded', 'true');
835
+ U.repositionMenu(d, mn);
836
+ } }));
837
+ U.on(mn, 'click', (() => closeBd(d)));
838
+ });
839
+ U.on(document, 'click', (() => bds.forEach(x => closeBd(x))));
840
+ U.on(document, 'keydown', ((e) => { if (e.key === 'Escape')
841
+ bds.forEach(x => closeBd(x)); }));
842
+ }
843
+ // Drawer
844
+ const closeDrawer = () => { const od = document.querySelector('.ux4g-drawer.ux4g-drawer-open'); const oo = document.querySelector('.ux4g-drawer-overlay.ux4g-drawer-open'); if (!od || !oo)
845
+ return; od.classList.remove('ux4g-drawer-open'); oo.classList.remove('ux4g-drawer-open'); document.body.classList.remove('ux4g-drawer-lock'); };
846
+ U.qsa('[data-drawer]').forEach(btn => { U.on(btn, 'click', (() => { const dr = document.getElementById(btn.getAttribute('data-drawer') || ''); if (!dr)
847
+ return; const ov = dr.closest('.ux4g-drawer-overlay'); document.querySelectorAll('.ux4g-drawer-open').forEach(el => el.classList.remove('ux4g-drawer-open')); if (ov)
848
+ ov.classList.add('ux4g-drawer-open'); dr.classList.add('ux4g-drawer-open'); document.body.classList.add('ux4g-drawer-lock'); })); });
849
+ U.on(document, 'click', ((e) => { if (U.closest(e.target, '[data-drawer-close]')) {
850
+ closeDrawer();
851
+ return;
852
+ } const ov = U.closest(e.target, '.ux4g-drawer-overlay'); if (ov && !U.closest(e.target, '.ux4g-drawer'))
853
+ closeDrawer(); }));
854
+ U.on(document, 'keydown', ((e) => { if (e.key === 'Escape')
855
+ closeDrawer(); }));
856
+ // Modal helpers (data-modal-target)
857
+ U.qsa('[data-modal-target]').forEach(btn => { U.on(btn, 'click', (() => { const s = btn.getAttribute('data-modal-target'); const m = s ? document.querySelector(s) : null; if (m) {
858
+ m.classList.add('is-open');
859
+ document.body.style.overflow = 'hidden';
860
+ } })); });
861
+ U.qsa('[data-close-modal]').forEach(btn => { U.on(btn, 'click', ((e) => { const m = U.closest(e.target, '.ux4g-modal-backdrop'); if (m) {
862
+ m.classList.remove('is-open');
863
+ document.body.style.overflow = '';
864
+ } })); });
865
+ // Search/filter
866
+ U.qsa('.ux4g-search-container').forEach(sw => {
867
+ const inp = U.qs('.ux4g-search-input', sw);
868
+ const cb = U.qs('.ux4g-search-clear', sw);
869
+ if (!inp)
870
+ return;
871
+ const toggle = () => { sw.classList.toggle('ux4g-has-value', inp.value.trim() !== ''); };
872
+ U.on(inp, 'input', toggle);
873
+ if (cb)
874
+ U.on(cb, 'click', (() => { inp.value = ''; toggle(); inp.focus(); }));
875
+ toggle();
876
+ });
877
+ // Textarea counter
878
+ U.on(document, 'input', ((e) => { const t = e.target; if (t.matches && t.matches('.ux4g-textarea-input')) {
879
+ const w = t.closest('.ux4g-textarea');
880
+ if (!w)
881
+ return;
882
+ const c = w.querySelector('.ux4g-textarea-counter');
883
+ if (c)
884
+ c.textContent = `${t.value.length} / ${t.getAttribute('maxlength') || '0'}`;
885
+ } }));
886
+ // Switch keyboard
887
+ U.on(document, 'keydown', ((e) => { if (e.key === 'Enter') {
888
+ const inp = U.closest(e.target, '.ux4g-switch-input');
889
+ if (inp && !inp.disabled) {
890
+ e.preventDefault();
891
+ inp.checked = !inp.checked;
892
+ inp.dispatchEvent(new Event('change', { bubbles: true }));
893
+ }
894
+ } }));
895
+ // Custom dropdown (ux4g-dropdown)
896
+ const dds = Array.from(document.querySelectorAll('.ux4g-dropdown'));
897
+ if (dds.length) {
898
+ const closeDd = (d) => { d.classList.remove('is-open'); const c = U.qs('.ux4g-dropdown-control', d); if (c)
899
+ c.setAttribute('aria-expanded', 'false'); };
900
+ const openDd = (d) => { dds.forEach(x => { if (x !== d)
901
+ closeDd(x); }); d.classList.add('is-open'); const c = U.qs('.ux4g-dropdown-control', d); if (c)
902
+ c.setAttribute('aria-expanded', 'true'); const m = U.qs('.ux4g-dropdown-menu', d); if (m)
903
+ U.repositionMenu(d, m); };
904
+ dds.forEach(dd => {
905
+ const ctrl = U.qs('.ux4g-dropdown-control', dd);
906
+ const menu = U.qs('.ux4g-dropdown-menu', dd);
907
+ if (!ctrl || !menu)
908
+ return;
909
+ U.on(ctrl, 'click', ((ev) => { if (U.closest(ev.target, '[ux4g-dropdown-search]')) {
910
+ if (!dd.classList.contains('is-open'))
911
+ openDd(dd);
912
+ return;
913
+ } ev.preventDefault(); ev.stopPropagation(); dd.classList.contains('is-open') ? closeDd(dd) : openDd(dd); }));
914
+ U.on(menu, 'click', ((ev) => { const ch = U.closest(ev.target, '[ux4g-dropdown-choice]'); if (ch) {
915
+ ev.stopPropagation();
916
+ closeDd(dd);
917
+ } }));
918
+ U.on(menu, 'change', ((ev) => { const inp = U.closest(ev.target, '.ux4g-dropdown-option-input'); if (!inp)
919
+ return; if (!dd.classList.contains('ux4g-dropdown-multi'))
920
+ closeDd(dd); }));
921
+ });
922
+ U.on(document, 'click', ((ev) => { dds.forEach(d => { if (!d.contains(ev.target))
923
+ closeDd(d); }); }));
924
+ U.on(document, 'keydown', ((ev) => { if (ev.key === 'Escape')
925
+ dds.forEach(d => closeDd(d)); }));
926
+ }
927
+ // Combobox
928
+ const cbs = Array.from(document.querySelectorAll('.ux4g-combobox'));
929
+ if (cbs.length) {
930
+ const closeCb = (c) => { c.classList.remove('is-open'); const ctrl = U.qs('.ux4g-combobox-control', c); if (ctrl)
931
+ ctrl.setAttribute('aria-expanded', 'false'); };
932
+ const openCb = (c) => { cbs.forEach(x => { if (x !== c)
933
+ closeCb(x); }); c.classList.add('is-open'); const ctrl = U.qs('.ux4g-combobox-control', c); if (ctrl)
934
+ ctrl.setAttribute('aria-expanded', 'true'); const m = U.qs('.ux4g-combobox-menu', c); if (m)
935
+ U.repositionMenu(c, m); };
936
+ cbs.forEach(cb => {
937
+ const ctrl = U.qs('.ux4g-combobox-control', cb);
938
+ const menu = U.qs('.ux4g-combobox-menu', cb);
939
+ if (!ctrl || !menu)
940
+ return;
941
+ const inp = cb.querySelector('[ux4g-combobox-search]');
942
+ if (inp) {
943
+ U.on(inp, 'focus', (() => openCb(cb)));
944
+ U.on(inp, 'input', (() => openCb(cb)));
945
+ }
946
+ const caret = cb.querySelector('.ux4g-combobox-caret');
947
+ if (caret)
948
+ U.on(caret, 'click', ((e) => { e.stopPropagation(); cb.classList.contains('is-open') ? closeCb(cb) : openCb(cb); }));
949
+ U.on(ctrl, 'click', ((ev) => { if (U.closest(ev.target, '[ux4g-combobox-search]')) {
950
+ if (!cb.classList.contains('is-open'))
951
+ openCb(cb);
952
+ return;
953
+ } ev.preventDefault(); ev.stopPropagation(); cb.classList.contains('is-open') ? closeCb(cb) : openCb(cb); }));
954
+ U.on(menu, 'click', ((ev) => { const ch = U.closest(ev.target, '[ux4g-combobox-choice]'); if (ch) {
955
+ ev.stopPropagation();
956
+ closeCb(cb);
957
+ } }));
958
+ });
959
+ U.on(document, 'click', ((ev) => { cbs.forEach(c => { if (!c.contains(ev.target))
960
+ closeCb(c); }); }));
961
+ U.on(document, 'keydown', ((ev) => { if (ev.key === 'Escape')
962
+ cbs.forEach(c => closeCb(c)); }));
963
+ }
964
+ // UX4G Tabs
965
+ U.qsa('[data-ux4g-tab]').forEach(root => {
966
+ const list = U.qs('.ux4g-tab-list', root);
967
+ if (!list)
968
+ return;
969
+ const items = U.qsa('.ux4g-tab-item:not(.ux4g-tab-more)', list);
970
+ const panels = U.qsa('.ux4g-tab-panel', root);
971
+ const resetActive = () => { U.qsa('.ux4g-tab-item', list).forEach(i => i.classList.remove('is-active')); root.querySelectorAll('.ux4g-tab-dropdown-item').forEach(i => i.classList.remove('is-active')); };
972
+ const showPanel = (id) => { panels.forEach(p => p.classList.remove('is-active')); const t = root.querySelector('#' + id); if (t)
973
+ t.classList.add('is-active'); };
974
+ items.forEach(item => { U.on(item, 'click', (() => { if (item.classList.contains('ux4g-tab-item-disabled'))
975
+ return; resetActive(); item.classList.add('is-active'); const pid = item.dataset.panel; if (pid)
976
+ showPanel(pid); })); });
977
+ });
978
+ // Sliders
979
+ U.on(document, 'input', ((e) => {
980
+ const t = e.target;
981
+ if (!t.classList.contains('ux4g-slider-input'))
982
+ return;
983
+ const slider = t.closest('.ux4g-slider');
984
+ if (!slider)
985
+ return;
986
+ const fill = slider.querySelector('.ux4g-slider-fill');
987
+ const inp = t;
988
+ if (!slider.classList.contains('ux4g-slider-dual')) {
989
+ const thumb = slider.querySelector('.ux4g-slider-thumb');
990
+ const pct = ((parseFloat(inp.value) - parseFloat(inp.min)) / (parseFloat(inp.max) - parseFloat(inp.min))) * 100;
991
+ if (fill)
992
+ fill.style.width = pct + '%';
993
+ if (thumb)
994
+ thumb.style.left = pct + '%';
995
+ }
996
+ }));
997
+ // Custom carousel (.ux4g-carousel)
998
+ document.querySelectorAll('.ux4g-carousel').forEach(car => {
999
+ const sc = car.querySelector('.ux4g-carousel-slides');
1000
+ const slides = car.querySelectorAll('.ux4g-carousel-slide');
1001
+ const dots = car.querySelectorAll('.ux4g-carousel-dot');
1002
+ if (!slides.length)
1003
+ return;
1004
+ let ci = 0;
1005
+ const upd = (i) => { if (i < 0)
1006
+ i = slides.length - 1; if (i >= slides.length)
1007
+ i = 0; ci = i; if (sc)
1008
+ sc.style.transform = `translateX(-${ci * 100}%)`; slides.forEach((s, idx) => { s.classList.toggle('is-active', idx === ci); }); dots.forEach((d, idx) => d.classList.toggle('is-active', idx === ci)); };
1009
+ const pb = car.querySelector('.ux4g-carousel-arrow-prev');
1010
+ const nb = car.querySelector('.ux4g-carousel-arrow-next');
1011
+ if (pb)
1012
+ U.on(pb, 'click', ((e) => { e.preventDefault(); upd(ci - 1); }));
1013
+ if (nb)
1014
+ U.on(nb, 'click', ((e) => { e.preventDefault(); upd(ci + 1); }));
1015
+ dots.forEach((d, idx) => U.on(d, 'click', ((e) => { e.preventDefault(); upd(idx); })));
1016
+ upd(ci);
1017
+ });
1018
+ // Context alerts
1019
+ U.qsa('[data-ux4g-toggle="toast"]').forEach(btn => {
1020
+ U.on(btn, 'click', (() => {
1021
+ const pos = btn.dataset.ux4gPosition || 'top-right';
1022
+ const variant = btn.dataset.ux4gVariant || 'info';
1023
+ const title = btn.dataset.ux4gTitle || variant;
1024
+ const body = btn.dataset.ux4gBody || '';
1025
+ const cid = `ux4g-alert-container-${pos}`;
1026
+ let cont = document.getElementById(cid);
1027
+ if (!cont) {
1028
+ cont = document.createElement('div');
1029
+ cont.id = cid;
1030
+ cont.className = `ux4g-alert-container ux4g-alert-${pos}`;
1031
+ document.body.appendChild(cont);
1032
+ }
1033
+ const al = document.createElement('div');
1034
+ al.className = `ux4g-context-alert ux4g-alert-${variant} ${pos.includes('left') ? 'ux4g-animate-left' : 'ux4g-animate-right'}`;
1035
+ al.innerHTML = `<span class="ux4g-alert-title">${title}</span><button class="ux4g-alert-close"><i class="ux4g-icon">close</i></button><div class="ux4g-alert-message">${body}</div>`;
1036
+ const cb = al.querySelector('.ux4g-alert-close');
1037
+ if (cb)
1038
+ cb.addEventListener('click', () => { al.style.opacity = '0'; setTimeout(() => al.remove(), 400); });
1039
+ cont.appendChild(al);
1040
+ setTimeout(() => { if (al.parentNode) {
1041
+ al.style.opacity = '0';
1042
+ setTimeout(() => al.remove(), 400);
1043
+ } }, 5000);
1044
+ }));
1045
+ });
1046
+ // Feedback (NPS, Emoji, Stars)
1047
+ U.qsa('.feedback-nps-button').forEach(btn => { U.on(btn, 'click', (() => { const c = btn.closest('.ux4g-feedback-nps-wrapper') || btn.parentElement; const sibs = Array.from(c.querySelectorAll('.feedback-nps-button')); const ci = sibs.indexOf(btn); sibs.forEach((s, i) => s.classList.toggle('active', i <= ci)); })); });
1048
+ U.qsa('.feedback-emoji-button').forEach(btn => { U.on(btn, 'click', (() => { const c = btn.closest('.ux4g-d-flex') || document; c.querySelectorAll('.feedback-emoji-button').forEach(b => b.classList.remove('active')); btn.classList.add('active'); })); });
1049
+ U.qsa('.ux4g-feedback-star').forEach(star => { U.on(star, 'click', (() => { const c = star.parentElement; const sibs = Array.from(c.querySelectorAll('.ux4g-feedback-star')); const ci = sibs.indexOf(star); sibs.forEach((s, i) => s.classList.toggle('active', i <= ci)); })); });
1050
+ // Mega menu
1051
+ const catItems = U.qsa('.ux4g-mega-menu__category-item');
1052
+ const contentBlocks = U.qsa('.ux4g-mega-menu__content');
1053
+ if (catItems.length && contentBlocks.length) {
1054
+ catItems.forEach((item, idx) => { U.on(item, 'click', ((e) => { e.preventDefault(); catItems.forEach(c => c.classList.remove('ux4g-mega-menu__category-item--active')); item.classList.add('ux4g-mega-menu__category-item--active'); contentBlocks.forEach(b => b.classList.remove('ux4g-mega-menu__content--active')); const tb = document.getElementById(`category-${idx + 1}`); if (tb)
1055
+ tb.classList.add('ux4g-mega-menu__content--active'); })); });
1056
+ }
1057
+ // Result list accordion
1058
+ U.qsa('.ux4g-result-list-accordion-toggle').forEach(btn => { U.on(btn, 'click', (() => { const exp = btn.getAttribute('aria-expanded') === 'true'; btn.setAttribute('aria-expanded', String(!exp)); btn.innerText = exp ? 'expand_more' : 'expand_less'; })); });
1059
+ // Resize handler
1060
+ U.on(window, 'resize', (() => { document.querySelectorAll('.ux4g-dropdown.is-open,.ux4g-combobox.is-open').forEach(el => { const m = el.querySelector('.ux4g-dropdown-menu,.ux4g-combobox-menu'); if (m)
1061
+ U.repositionMenu(el, m); }); }));
1062
+ }
1063
+ function initMutationObserver() {
1064
+ if (typeof MutationObserver === 'undefined')
1065
+ return;
1066
+ const obs = new MutationObserver((mutations) => { let shouldInit = false; for (const m of mutations) {
1067
+ if (m.addedNodes.length) {
1068
+ shouldInit = true;
1069
+ break;
1070
+ }
1071
+ } if (shouldInit) {
1072
+ if (window.__ux4gInitRaf)
1073
+ cancelAnimationFrame(window.__ux4gInitRaf);
1074
+ window.__ux4gInitRaf = requestAnimationFrame(() => init(document));
1075
+ } });
1076
+ const start = () => { if (document.body)
1077
+ obs.observe(document.body, { childList: true, subtree: true });
1078
+ else
1079
+ setTimeout(start, 50); };
1080
+ start();
1081
+ activeObservers.push(obs);
1082
+ }
1083
+ function initRuntime() {
1084
+ if (!isBrowser)
1085
+ return;
1086
+ if (window.__UX4G_RUNTIME_INITIALIZED__)
1087
+ return;
1088
+ window.__UX4G_RUNTIME_INITIALIZED__ = true;
1089
+ init(document);
1090
+ initDelegatedToggles();
1091
+ initCustomBehaviors();
1092
+ initMutationObserver();
1093
+ }
1094
+ function destroyRuntime() {
1095
+ if (!isBrowser)
1096
+ return;
1097
+ activeListeners.forEach(({ target, event, handler, options }) => { target.removeEventListener(event, handler, options); });
1098
+ activeListeners = [];
1099
+ activeObservers.forEach(obs => obs.disconnect());
1100
+ activeObservers = [];
1101
+ if (window.__ux4gInitRaf) {
1102
+ cancelAnimationFrame(window.__ux4gInitRaf);
1103
+ window.__ux4gInitRaf = undefined;
1104
+ }
1105
+ document.querySelectorAll('.ux4g-backdrop').forEach(el => el.remove());
1106
+ document.querySelectorAll('.ux4g-tooltip,.ux4g-popover').forEach(el => { if (el.parentElement === document.body)
1107
+ el.remove(); });
1108
+ document.querySelectorAll('[id^="ux4g-alert-container-"]').forEach(el => el.remove());
1109
+ document.body.classList.remove('ux4g-scroll-lock', 'ux4g-drawer-lock');
1110
+ document.body.style.overflow = '';
1111
+ window.__UX4G_RUNTIME_INITIALIZED__ = false;
1112
+ }
1113
+
1114
+ exports.destroyRuntime = destroyRuntime;
1115
+ exports.initRuntime = initRuntime;