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