ux4g-components-web 1.1.0 → 1.1.2

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