ux4g-components-web 1.1.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,1092 +1,41 @@
1
1
  /**
2
2
  * UX4G Runtime Module
3
3
  *
4
- * Framework-agnostic runtime that encapsulates ALL interactive behaviors
5
- * from the vendor CDN scripts (ux4g.js + ux4g-custom.js).
4
+ * Injects the vendor JS (ux4g.js + ux4g-custom.js) into the page as inline scripts.
5
+ * These files handle all interactive behaviors: dropdowns, modals, tooltips,
6
+ * accordions, carousels, drawers, tabs, popovers, toasts, scrollspy, etc.
7
+ *
8
+ * The vendor JS is inlined at build time by the Rollup virtual plugin.
9
+ * At runtime, initRuntime() creates <script> elements with the inlined code.
6
10
  *
7
11
  * Uses a singleton guard (window.__UX4G_RUNTIME_INITIALIZED__) to ensure
8
- * initialization happens exactly once.
12
+ * the scripts are injected exactly once.
9
13
  */
10
14
  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
- }
15
+ /**
16
+ * Initialize the UX4G runtime by injecting the vendor JS scripts into the page.
17
+ *
18
+ * - Safe for SSR — no-ops when window/document are unavailable.
19
+ * - Singleton guard prevents double-injection.
20
+ * - The vendor JS handles its own DOMContentLoaded timing internally.
21
+ * - MutationObserver in the vendor JS auto-initializes dynamically added elements.
22
+ */
1080
23
  function initRuntime() {
1081
24
  if (!isBrowser)
1082
25
  return;
1083
26
  if (window.__UX4G_RUNTIME_INITIALIZED__)
1084
27
  return;
1085
28
  window.__UX4G_RUNTIME_INITIALIZED__ = true;
1086
- init(document);
1087
- initDelegatedToggles();
1088
- initCustomBehaviors();
1089
- initMutationObserver();
29
+ // Inject ux4g.js (sets up window.ux4g with all core components)
30
+ const script1 = document.createElement('script');
31
+ script1.setAttribute('data-ux4g-runtime', 'main');
32
+ script1.textContent = __UX4G_VENDOR_MAIN__;
33
+ document.head.appendChild(script1);
34
+ // Inject ux4g-custom.js (extends window.ux4g with custom behaviors)
35
+ const script2 = document.createElement('script');
36
+ script2.setAttribute('data-ux4g-runtime', 'custom');
37
+ script2.textContent = __UX4G_VENDOR_CUSTOM__;
38
+ document.head.appendChild(script2);
1090
39
  }
1091
40
 
1092
41
  /**