ux4g-components-web 1.1.0 → 1.1.2

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