ux4g-components-web 1.1.0 → 1.1.2

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