1"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[761],{8024:()=>{let n="undefined"!=typeof window&&"undefined"!=typeof document;if(!function(){if(!n||window.__UX4G_RUNTIME_INITIALIZED__)return;window.__UX4G_RUNTIME_INITIALIZED__=!0;let e=document.createElement("script");e.setAttribute("data-ux4g-runtime","main"),e.textContent='/*!\r\n * UX4G.js\r\n * Components: Dropdown, Collapse/Accordion, Offcanvas, Tooltip, Popover, Toast, Carousel, Tabs, Scrollspy\r\n * Compatibility: UX4G markup (data-ux-*)\r\n * Version: 1.1.0\r\n */\r\n\r\n(function (global) {\r\n "use strict";\r\n\r\n // -----------------------------\r\n // Utilities\r\n // -----------------------------\r\n const U = {\r\n qs(sel, root = document) { return root.querySelector(sel); },\r\n qsa(sel, root = document) { return Array.from(root.querySelectorAll(sel)); },\r\n on(el, evt, cb, opts) { el && el.addEventListener(evt, cb, opts); },\r\n off(el, evt, cb, opts) { el && el.removeEventListener(evt, cb, opts); },\r\n closest(el, sel) { return el ? el.closest(sel) : null; },\r\n isVisible(el) { return !!(el && (el.offsetWidth || el.offsetHeight || el.getClientRects().length)); },\r\n reflow(el) { return el && el.offsetHeight; },\r\n attr(el, name, fallback = null) {\r\n if (!el) return fallback;\r\n const v = el.getAttribute(name);\r\n return v == null ? fallback : v;\r\n },\r\n // Prefer UX4G attributes\r\n data(el, key, fallback = null) {\r\n if (!el) return fallback;\r\n const ux = el.getAttribute(`data-ux-${key}`);\r\n if (ux != null) return ux;\r\n const bs = el.getAttribute(`data-bs-${key}`);\r\n if (bs != null) return bs;\r\n const ux4g = el.getAttribute(`ux4g-${key}`);\r\n if (ux4g != null) return ux4g;\r\n return fallback;\r\n },\r\n bool(v, fallback = false) {\r\n if (v == null) return fallback;\r\n if (typeof v === "boolean") return v;\r\n const s = String(v).trim().toLowerCase();\r\n if (s === "" || s === "true" || s === "1") return true;\r\n if (s === "false" || s === "0") return false;\r\n return fallback;\r\n },\r\n num(v, fallback = 0) {\r\n const n = Number(v);\r\n return Number.isFinite(n) ? n : fallback;\r\n },\r\n dispatch(el, name, detail) {\r\n if (!el) return;\r\n el.dispatchEvent(new CustomEvent(name, { bubbles: true, cancelable: true, detail }));\r\n },\r\n focusables(root) {\r\n if (!root) return [];\r\n const sel = [\r\n "a[href]",\r\n "area[href]",\r\n "button:not([disabled])",\r\n "input:not([disabled]):not([type=\'hidden\'])",\r\n "select:not([disabled])",\r\n "textarea:not([disabled])",\r\n "[tabindex]:not([tabindex=\'-1\'])",\r\n "[contenteditable=\'true\']"\r\n ].join(",");\r\n return U.qsa(sel, root).filter(U.isVisible);\r\n },\r\n // Body scroll lock (simple)\r\n lockBody(lock) {\r\n const cls = "ux4g-scroll-lock";\r\n if (lock) document.body.classList.add(cls);\r\n else document.body.classList.remove(cls);\r\n },\r\n ensureBackdrop(kind) {\r\n let bd = U.qs(`.ux4g-backdrop[data-kind="${kind}"]`);\r\n if (!bd) {\r\n bd = document.createElement("div");\r\n bd.className = "ux4g-backdrop";\r\n bd.setAttribute("data-kind", kind);\r\n document.body.appendChild(bd);\r\n }\r\n return bd;\r\n },\r\n removeBackdrop(kind) {\r\n const bd = U.qs(`.ux4g-backdrop[data-kind="${kind}"]`);\r\n if (bd) bd.remove();\r\n },\r\n // Lightweight positioning (NOT Popper-perfect)\r\n placeFloating(target, floating, placement = "bottom", offset = 8, constrainToViewport = true) {\r\n if (!target || !floating) return;\r\n\r\n let rect = target.getBoundingClientRect();\r\n const scrollX = window.pageXOffset || document.documentElement.scrollLeft;\r\n const scrollY = window.pageYOffset || document.documentElement.scrollTop;\r\n let vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
1\r\n let vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0);\r\n \r\n // Temporary show to measure\r\n const originalDisplay = floating.style.display;\r\n const originalVisibility = floating.style.visibility;\r\n floating.style.display = "block";\r\n floating.style.visibility = "hidden";\r\n floating.style.position = constrainToViewport ? "fixed" : "absolute";\r\n\r\n const op = floating.offsetParent;\r\n if (op && op !== document.body && op !== document.documentElement) {\r\n const opRect = op.getBoundingClientRect();\r\n vw = opRect.width;\r\n vh = opRect.height;\r\n rect = {\r\n top: rect.top - opRect.top,\r\n left: rect.left - opRect.left,\r\n right: rect.right - opRect.left,\r\n bottom: rect.bottom - opRect.top,\r\n width: rect.width,\r\n height: rect.height\r\n };\r\n }\r\n\r\n if (!constrainToViewport) {\r\n floating.style.boxSizing = "border-box";\r\n floating.style.minWidth = "0";\r\n floating.style.maxWidth = `${Math.max(1, vw - 16)}px`;\r\n floating.style.overflow = "visible";\r\n }\r\n\r\n let fr = floating.getBoundingClientRect();\r\n\r\n const clamp = (v, min, max) => Math.min(max, Math.max(min, v));\r\n\r\n const resolvedPlacement = !constrainToViewport ? floating.getAttribute("data-ux-resolved-placement") : null;\r\n const want = resolvedPlacement || placement;\r\n const [wantedSide, wantedAlign] = want.split("-");\r\n const tries = [want];\r\n const opposite = { top: "bottom", bottom: "top", left: "right", right: "left" };\r\n const sideFallback = opposite[wantedSide];\r\n \r\n if (wantedAlign) {\r\n const alignFallback = wantedAlign === "start" ? "end" : "start";\r\n tries.push(`${wantedSide}-${alignFallback}`);\r\n tries.push(wantedSide);\r\n } else {\r\n tries.push(`${wantedSide}-start`);\r\n tries.push(`${wantedSide}-end`);\r\n }\r\n \r\n if (sideFallback) {\r\n tries.push(sideFallback + (wantedAlign ? \'-\' + wantedAlign : \'\'));\r\n if (wantedAlign) {\r\n const alignFallback = wantedAlign === "start" ? "end" : "start";\r\n tries.push(`${sideFallback}-${alignFallback}`);\r\n tries.push(sideFallback);\r\n } else {\r\n tries.push(`${sideFallback}-start`);\r\n tries.push(`${sideFallback}-end`);\r\n }\r\n }\r\n \r\n const altAxis = (wantedSide === "left" || wantedSide === "right") ? ["top", "bottom"] : ["left", "right"];\r\n altAxis.forEach(axis => {\r\n ["", "-start", "-end"].forEach(align => {\r\n const p = axis + align;\r\n if (!tries.includes(p)) tries.push(p);\r\n });\r\n });\r\n ["bottom", "top", "right", "left"].forEach(s => {\r\n if (!tries.includes(s)) tries.push(s);\r\n });\r\n\r\n const compute = (p) => {\r\n let t = 0, l = 0;\r\n const [side, align] = p.split("-");\r\n \r\n if (side === "top") {\r\n t = rect.top - fr.height - offset;\r\n if (align === "start") l = rect.left;\r\n else if (align === "end") l = rect.right - fr.width;\r\n else l = rect.left + (rect.width - fr.width) / 2;\r\n } else if (side === "bottom") {\r\n t = rect.bottom + offset;\r\n if (align === "start") l = rect.left;\r\n else if (align === "end") l = rect.right - fr.width;\r\n else l = rect.left + (rect.width - fr.width) / 2;\r\n } else if (side === "left") {\r\n l = rect.left - fr.width - offset;\r\n if (align === "start") t = rect.top;\r\n else if (align === "end") t = rect.bottom - fr.height;\r\n else t = rect.top + (rect.height - fr.height) / 2;\r\n } else if (side === "right") {\r\n l = rect.right + offset;\r\n if (align === "start") t = rect.top;\r\n else if (align === "end") t = rect.bottom - fr.height;\r\n else t = rect.top + (rect.height - fr.height) / 2;\r\n } else {\r\n t = rect.bottom + offset;\r\n l = rect.left + (rect.width - fr.width) / 2;\r\n p = "bottom";\r\n }\r\n return { t, l, p };\r\n };\r\n\r\n let chosen = null;\r\n if (constrainToViewport || !resolvedPlacement) {\r\n for (const p of tries) {\r\n const c = compute(p);\r\n const fitsH = c.t >= 0 && (c.t + fr.height) <= vh;\r\n const fitsW = c.l >= 0 && (c.l + fr.width) <= vw;\r\n if (fitsH && fitsW) { chosen = c; break; }\r\n }\r\n }\r\n if (!chosen && !resolvedPlacement) {\r\n const visibleArea = c => {\r\n const visibleWidth = Math.max(0, Math.min(vw, c.l + fr.width) - Math.max(0, c.l));\r\n const visibleHeight = Math.max(0, Math.min(vh, c.t + fr.height) - Math.max(0, c.t));\r\n return visibleWidth * visibleHeight;\r\n };\r\n chosen = tries.map(compute).sort((a, b) => visibleArea(b) - visibleArea(a))[0];\r\n }\r\n if (!chosen) chosen = compute(want);\r\n\r\n if (!constrainToViewport) {\r\n floating.setAttribute("data-ux-resolved-placement", chosen.p);\r\n const [side] = chosen.p.split("-");\r\n const availableWidth = side === "left"\r\n ? rect.left - offset - 8\r\n : side === "right"\r\n ? vw - rect.right - offset - 8\r\n : vw - 16;\r\n\r\n floating.style.maxWidth = `${Math.max(1, availableWidth)}px`;\r\n\r\n const body = floating.querySelector(".ux4g-popover-body");\r\n if (body) {\r\n body.style.boxSizing = "border-box";\r\n body.style.maxWidth = "100%";
1\r\n body.style.overflowWrap = "anywhere";\r\n body.style.wordBreak = "break-word";\r\n }\r\n\r\n fr = floating.getBoundingClientRect();\r\n chosen = compute(chosen.p);\r\n }\r\n\r\n floating.style.display = originalDisplay;\r\n floating.style.visibility = originalVisibility;\r\n\r\n const [chosenSide] = chosen.p.split("-");\r\n const hasOp = op && op !== document.body && op !== document.documentElement;\r\n let top = constrainToViewport\r\n ? clamp(chosen.t, 8, Math.max(8, vh - fr.height - 8))\r\n : (hasOp ? chosen.t : chosen.t + scrollY);\r\n let left = constrainToViewport\r\n ? clamp(chosen.l, 8, Math.max(8, vw - fr.width - 8))\r\n : (hasOp ? chosen.l : chosen.l + scrollX);\r\n\r\n floating.style.position = constrainToViewport ? "fixed" : "absolute";\r\n floating.style.top = `${top}px`;\r\n floating.style.left = `${left}px`;\r\n floating.setAttribute("data-placement", chosen.p);\r\n \r\n const isTooltip = floating.classList.contains(\'ux4g-tooltip\');\r\n const baseClass = isTooltip ? \'ux4g-tooltip\' : \'ux4g-popover\';\r\n \r\n const placementPrefix = `${baseClass}-`;\r\n for (const cls of Array.from(floating.classList)) {\r\n if (cls.startsWith(placementPrefix) && cls !== baseClass) {\r\n floating.classList.remove(cls);\r\n }\r\n }\r\n floating.classList.add(`${placementPrefix}${chosen.p}`);\r\n }\r\n };\r\n\r\n const Registry = new WeakMap();\r\n const getI = (el, key) => (Registry.get(el)?.[key]) || null;\r\n const setI = (el, key, inst) => {\r\n let map = Registry.get(el);\r\n if (!map) { map = {}; Registry.set(el, map); }\r\n map[key] = inst;\r\n };\r\n\r\n const escapeHtml = (s) => String(s)\r\n .replaceAll("&", "&")\r\n .replaceAll("<", "<")\r\n .replaceAll(">", ">")\r\n .replaceAll(\'"\', """)\r\n .replaceAll("\'", "'");\r\n\r\n // -----------------------------\r\n // Dropdown\r\n // -----------------------------\r\n class Dropdown {\r\n constructor(toggle) {\r\n this.toggle = toggle;\r\n this.menu = this._findMenu(toggle);\r\n this._open = false;\r\n\r\n U.on(this.toggle, "click", (e) => {\r\n e.preventDefault();\r\n this.toggleDropdown();\r\n });\r\n\r\n U.on(document, "click", (e) => {\r\n if (!this._open) return;\r\n if (this.menu && (this.menu.contains(e.target) || this.toggle.contains(e.target))) return;\r\n this.hide();\r\n });\r\n\r\n U.on(document, "keydown", (e) => {\r\n if (!this._open) return;\r\n if (e.key === "Escape") {\r\n this.hide();\r\n this.toggle.focus();\r\n }\r\n });\r\n }\r\n\r\n _findMenu(toggle) {\r\n // Standard dropdown structure: .dropdown > [toggle] + .dropdown-menu\r\n const parent = toggle.parentElement;\r\n let menu = parent ? parent.querySelector(".dropdown-menu") : null;\r\n if (!menu) {\r\n const target = U.data(toggle, "target") || U.attr(toggle, "aria-controls");\r\n if (target && target.startsWith("#")) menu = U.qs(target);\r\n }\r\n return menu;\r\n }\r\n\r\n show() {\r\n if (!this.menu) return;\r\n this._open = true;\r\n this.toggle.classList.add("show");\r\n this.menu.classList.add("show");\r\n this.toggle.setAttribute("aria-expanded", "true");\r\n\r\n const placement = U.data(this.toggle, "placement", "bottom");\r\n const offset = U.num(U.data(this.toggle, "offset", 6), 6);\r\n U.placeFloating(this.toggle, this.menu, placement, offset);\r\n\r\n U.dispatch(this.toggle, "ux4g.dropdown.shown", { menu: this.menu });\r\n }\r\n\r\n hide() {\r\n if (!this.menu) return;\r\n this._open = false;\r\n this.toggle.classList.remove("show");\r\n this.menu.classList.remove("show");\r\n this.toggle.setAttribute("aria-expanded", "false");\r\n U.dispatch(this.toggle, "ux4g.dropdown.hidden", { menu: this.menu });\r\n }\r\n\r\n toggleDropdown() { this._open ? this.hide() : this.show(); }\r\n\r\n static getOrCreate(el) {\r\n let inst = getI(el, "dropdown");\r\n if (!inst) { inst = new Dropdown(el); setI(el, "dropdown", inst); }\r\n return inst;\r\n }\r\n }\r\n\r\n // -----------------------------\r\n // Collapse / Accordion\r\n // -----------------------------\r\n class Collapse {\r\n constructor(trigger) {\r\n this.trigger = trigger;\r\n this.target = this._resolveTarget(trigger);\r\n // ux4g accordion uses data-bs-parent on .collapse\r\n this.parentSel = U.data(this.target, "parent") || U.data(this.trigger, "parent");\r\n this.duration = this._readDuration(this.target, 200);\r\n\r\n U.on(this.trigger, "click", (e) => {\r\n e.preventDefault();\r\n this.toggle();\r\n });\r\n }\r\n\r\n _resolveTarget(trigger) {\r\n const sel = U.data(trigger, "target") || U.attr(trigger, "href") || U.attr(trigger, "aria-controls") || U.attr(trigger, "ux4g-target");\r\n if (sel && sel.startsWith("#")) return U.qs(sel);\r\n return U.qs("#" + sel);\r\n }\r\n\r\n _readDuration(el, fallbackMs) {\r\n if (!el) return fallbackMs;\r\n const d = getComputedStyle(el).transitionDuration || "";\r\n const ms = d.includes("ms") ? parseFloat(d) : (d.includes("s") ? parseFloat(d) * 1000 : NaN);\r\n return Number.isFinite(ms) && ms > 0 ? ms : fallbackMs;\r\n }\r\n\r\n show() {\r\n if (!this.target) return;\r\n\r\n // Accordion: close other open within parent\r\n if (this.parentSel) {\r\n const parent = U.qs(this.parentSel);\r\n if (parent) {\r\n U.qsa(".collapse.show", parent).forEach((el) => {\r\n if (el === this.target) return;\r\n el.classList.remove("show");\r\n // update triggers for that element\r\n if (el.id) {\r\n U.qsa(`[data-bs-target="#${el.id}"],[data-ux-target="#${el.id}"],[ux4g-target="#${el.id}"],a[href="#${el.id}"]`)\r\n .forEach(t => {\r\n t.classList.add("collapsed");\r\n t.setAttribute("aria-expanded", "false");\r\n });\r\n }\r\n });\r\n }\r\n }\r\n\r\n // Animate height\r\n this.target.classList.add("collapsing");\r\n this.target.classList.remove("collapse");\r\n this.target.style.height = "0px";\r\n U.reflow(this.target);\r\n\r\n const h = this.target.scrollHeight;\r\n this.target.style.height = h + "px";\r\n\r\n this.trigger.classList.remove("collapsed");\r\n this.trigger.setAttribute("aria-expanded", "true");\r\n\r\n window.setTimeout(() => {\r\n this.target.classList.remove("collapsing");\r\n this.target.classList.add("collapse", "show");\r\n this.target.style.height = "";\r\n U.dispatch(this.target, "ux4g.collapse.shown", {});\r\n }, this.duration);\r\n }\r\n\r\n hide() {\r\n if (!this.target) return;\r\n\r\n this.target.style.height = this.target.getBoundingClientRect().height + "px";\r\n U.reflow(this.target);\r\n\r\n this.target.classList.add("collapsing");\r\n this.target.classList.remove("collapse", "show");\r\n\r\n this.trigger.classList.add("collapsed");\r\n this.trigger.setAttribute("aria-expanded", "false");\r\n\r\n window.setTimeout(() => {\r\n this.target.style.height = "0px";\r\n }, 10);\r\n\r\n window.setTimeout(() => {\r\n this.target.classList.remove("collapsing");\r\n this.target.classList.add("collapse");\r\n this.target.style.height = "";\r\n U.dispatch(this.target, "ux4g.collapse.hidden", {});\r\n }, this.duration);\r\n }\r\n\r\n toggle() {\r\n if (!this.target) return;\r\n this.target.classList.contains("show") ? this.hide() : this.show();\r\n }\r\n\r\n static getOrCreate(el) {\r\n let inst = getI(el, "collapse");\r\n if (!inst) { inst = new Collapse(el); setI(el, "collapse", inst); }\r\n return inst;\r\n }\r\n }\r\n\r\n // -----------------------------\r\n // Offcanvas\r\n // -----------------------------\r\n class Offcanvas {\r\n constructor(el) {\r\n this.el = el;\r\n this._shown = false;\r\n this._bdKind = "offcanvas";\r\n this._lastFocus = null;\r\n this.duration = this._readDuration(el, 250);\r\n\r\n U.on(this.el, "click", (e) => {\r\n const dismiss = U.closest(e.target, \'[data-bs-dismiss="offcanvas"],[data-ux-dismiss="offcanvas"]\');\r\n if (dismiss) {\r\n e.preventDefault();\r\n this.hide();\r\n }\r\n });\r\n\r\n U.on(document, "keydown", (e) => {\r\n if (!this._shown) return;
1\r\n if (e.key === "Escape") {\r\n const kb = U.bool(U.data(this.el, "keyboard", "true"), true);\r\n if (kb) this.hide();\r\n } else if (e.key === "Tab") {\r\n this._trapTab(e);\r\n }\r\n });\r\n }\r\n\r\n _readDuration(el, fallbackMs) {\r\n if (!el) return fallbackMs;\r\n const d = getComputedStyle(el).transitionDuration || "";\r\n const ms = d.includes("ms") ? parseFloat(d) : (d.includes("s") ? parseFloat(d) * 1000 : NaN);\r\n return Number.isFinite(ms) && ms > 0 ? ms : fallbackMs;\r\n }\r\n\r\n _trapTab(e) {\r\n const f = U.focusables(this.el);\r\n if (!f.length) return;\r\n const first = f[0], last = f[f.length - 1];\r\n if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }\r\n else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }\r\n }\r\n\r\n show(trigger) {\r\n if (this._shown) return;\r\n this._shown = true;\r\n this._lastFocus = document.activeElement;\r\n\r\n const backdropOpt = U.data(this.el, "backdrop", "true");\r\n const backdrop = backdropOpt !== "false";\r\n\r\n if (backdrop) {\r\n const bd = U.ensureBackdrop(this._bdKind);\r\n bd.classList.add("show");\r\n U.on(bd, "click", () => {\r\n if (backdropOpt === "static") return;\r\n this.hide();\r\n });\r\n }\r\n\r\n U.lockBody(true);\r\n this.el.style.visibility = "visible";\r\n this.el.classList.add("show");\r\n this.el.setAttribute("aria-modal", "true");\r\n\r\n const focus = U.bool(U.data(this.el, "focus", "true"), true);\r\n if (focus) {\r\n const f = U.focusables(this.el);\r\n (f[0] || this.el).focus({ preventScroll: true });\r\n }\r\n\r\n U.dispatch(this.el, "ux4g.offcanvas.shown", { relatedTarget: trigger || null });\r\n }\r\n\r\n hide() {\r\n if (!this._shown) return;\r\n this._shown = false;\r\n\r\n this.el.classList.remove("show");\r\n this.el.removeAttribute("aria-modal");\r\n\r\n window.setTimeout(() => {\r\n this.el.style.visibility = "";\r\n U.lockBody(false);\r\n U.removeBackdrop(this._bdKind);\r\n\r\n if (this._lastFocus && typeof this._lastFocus.focus === "function") {\r\n this._lastFocus.focus({ preventScroll: true });\r\n }\r\n U.dispatch(this.el, "ux4g.offcanvas.hidden", {});\r\n }, this.duration);\r\n }\r\n\r\n toggle(trigger) { this._shown ? this.hide() : this.show(trigger); }\r\n\r\n static getOrCreate(el) {\r\n let inst = getI(el, "offcanvas");\r\n if (!inst) { inst = new Offcanvas(el); setI(el, "offcanvas", inst); }\r\n return inst;\r\n }\r\n }\r\n\r\n // -----------------------------\r\n // Tooltip / Popover (lightweight)\r\n // -----------------------------\r\n let floatingId = 0;\r\n\r\n class Floating {\r\n constructor(el, kind) {\r\n this.el = el;\r\n this.kind = kind; // tooltip | popover\r\n this._open = false;\r\n this._floating = null;\r\n this._uid = ++floatingId;\r\n this._warnings = new Set();\r\n\r\n this.placement = U.data(el, "placement", kind === "tooltip" ? "top" : "right");\r\n this.offset = U.num(U.data(el, "offset", 8), 8);\r\n this.trigger = U.data(el, "trigger", kind === "tooltip" ? "hover focus" : "click");\r\n this.html = U.bool(U.data(el, "html", "false"), false);\r\n\r\n if (this.kind === "popover") {\r\n const contentId = U.data(this.el, "content-id");\r\n const source = contentId\r\n ? (this.el.ownerDocument || document).getElementById(contentId)\r\n : null;\r\n if (source && source.tagName !== "TEMPLATE") {\r\n source.hidden = true;\r\n }\r\n }\r\n\r\n this._bind();\r\n }\r\n\r\n _warn(message) {\r\n if (this._warnings.has(message)) return;\r\n this._warnings.add(message);\r\n console.warn(`[UX4G Popover] ${message}`);\r\n }\r\n\r\n _rewriteClonedIds(fragment) {\r\n const idMap = new Map();\r\n U.qsa("[id]", fragment).forEach((element) =>
1 {\r\n const originalId = element.id;\r\n const clonedId = `ux4g-popover-${this._uid}-${originalId}`;\r\n idMap.set(originalId, clonedId);\r\n element.id = clonedId;\r\n });\r\n\r\n const tokenReferenceAttributes = [\r\n "aria-controls",\r\n "aria-describedby",\r\n "aria-details",\r\n "aria-labelledby",\r\n "aria-owns",\r\n "for"\r\n ];\r\n\r\n U.qsa("*", fragment).forEach((element) => {\r\n tokenReferenceAttributes.forEach((attribute) => {\r\n const value = element.getAttribute(attribute);\r\n if (!value) return;\r\n const rewritten = value\r\n .split(/\\s+/)\r\n .map((token) => idMap.get(token) || token)\r\n .join(" ");\r\n element.setAttribute(attribute, rewritten);\r\n });\r\n\r\n const href = element.getAttribute("href");\r\n if (href && href.startsWith("#") && idMap.has(href.slice(1))) {\r\n element.setAttribute("href", `#${idMap.get(href.slice(1))}`);\r\n }\r\n });\r\n }\r\n\r\n _getContentFragment() {\r\n if (this.kind !== "popover") return null;\r\n\r\n const contentId = U.data(this.el, "content-id");\r\n if (!contentId) return null;\r\n\r\n if (U.data(this.el, "content") != null) {\r\n this._warn(\r\n `Both data-ux-content-id and data-ux-content are set; ` +\r\n `data-ux-content-id="${contentId}" takes precedence.`\r\n );\r\n }\r\n\r\n const ownerDocument = this.el.ownerDocument || document;\r\n const matches = U.qsa("[id]", ownerDocument)\r\n .filter((element) => element.id === contentId);\r\n const source = ownerDocument.getElementById(contentId);\r\n\r\n if (!source) {\r\n this._warn(`No content element found with id "${contentId}".`);\r\n return ownerDocument.createDocumentFragment();\r\n }\r\n\r\n if (matches.length > 1) {\r\n this._warn(\r\n `Multiple elements use id "${contentId}". IDs must be unique; ` +\r\n `the first matching element will be used.`\r\n );\r\n }\r\n\r\n const fragment = source.tagName === "TEMPLATE"\r\n ? source.content.cloneNode(true)\r\n : (() => {\r\n source.hidden = true;\r\n const clone = ownerDocument.createDocumentFragment();\r\n Array.from(source.childNodes).forEach((node) => {\r\n clone.appendChild(node.cloneNode(true));\r\n });\r\n return clone;\r\n })();\r\n\r\n this._rewriteClonedIds(fragment);\r\n return fragment;\r\n }\r\n\r\n _getAnchorParent() {\r\n const ownerDocument = this.el.ownerDocument || document;\r\n let anchorParent = this.el.parentElement || ownerDocument.body;\r\n let ancestor = anchorParent;\r\n\r\n while (ancestor && ancestor !== ownerDocument.body) {\r\n const style = getComputedStyle(ancestor);\r\n const clipsContent = [style.overflow, style.overflowX, style.overflowY]\r\n .some((value) => value && value !== "visible");\r\n\r\n if (clipsContent && ancestor.parentElement) {\r\n anchorParent = ancestor.parentElement;\r\n }\r\n ancestor = ancestor.parentElement;\r\n }\r\n\r\n return anchorParent;\r\n }\r\n\r\n _getContent() {\r\n const content = U.data(this.el, "content");\r\n if (this.kind === "popover") {\r\n const hasContentId = !!U.data(this.el, "content-id");\r\n const title = U.data(this.el, "title") || this.el.getAttribute("title") || "";\r\n const subtitle = U.data(this.el, "subtitle") || "";\r\n const icon = U.data(this.el, "icon") || "";\r\n const label = U.data(this.el, "label") || "";\r\n const actionHtml = U.data(this.el, "action-html") || "";\r\n\r\n const t = this.html ? String(title) : escapeHtml(title);\r\n const s = this.html ? String(subtitle) : escapeHtml(subtitle);\r\n const c = hasContentId\r\n ? ""\r\n : (this.html ? String(content || "") : escapeHtml(content || ""));\r\n const i = this.html ? String(icon) : escapeHtml(icon);\r\n const l = this.html ? String(label) : escapeHtml(label);\r\n\r\n let inner = "";\r\n if (t || s) {\r\n inner += `<div class="ux4g-popover-header">\r\n <div class="ux4g-popover-title-row">\r\n <div class="ux4g-popover-title">\r\n ${i ? `<i class="ux4g-icon">${i}</i>` : ""}
1\r\n <span>${t}</span>\r\n ${l ? `<span class="ux4g-tag-outline-brand">${l}</span>` : ""}\r\n </div>\r\n ${actionHtml ? `\r\n ${actionHtml}\r\n ` : ""}\r\n </div>\r\n ${s ? `<div class="ux4g-popover-subtitle">${s}</div>` : ""}\r\n </div>`;\r\n }\r\n inner += `<div class="ux4g-popover-body">${c}</div>`;\r\n \r\n const hasArrow = U.bool(U.data(this.el, "arrow", "true"), true);\r\n if (hasArrow) {\r\n inner += \'<div class="ux4g-popover-arrow"><i class="ux4g-icon">arrow_drop_up</i></div>\';\r\n }\r\n \r\n return inner;\r\n }\r\n\r\n const t = content != null ? content : (this.el.getAttribute("title") || "");\r\n return this.html ? String(t) : escapeHtml(t);\r\n }\r\n\r\n _create() {\r\n if (this._floating) return;\r\n const div = document.createElement("div");\r\n div.className = this.kind === "tooltip" ? "ux4g-tooltip" : "ux4g-popover";\r\n div.setAttribute("role", this.kind === "tooltip" ? "tooltip" : "dialog");\r\n div.id = `ux4g-${this.kind}-${this._uid}`;\r\n \r\n const hasArrow = U.bool(U.data(this.el, "arrow", "true"), true);\r\n if (!hasArrow) {\r\n div.classList.add("ux4g-popover-no-arrow");\r\n }\r\n\r\n div.innerHTML = this._getContent() || "";\r\n const contentFragment = this._getContentFragment();\r\n if (contentFragment) {\r\n const body = div.querySelector(".ux4g-popover-body");\r\n if (body) body.appendChild(contentFragment);\r\n }\r\n const anchorParent = this._getAnchorParent();\r\n const anchorPosition = getComputedStyle(anchorParent).position;\r\n if (!anchorPosition || anchorPosition === "static") {\r\n anchorParent.style.position = "relative";\r\n }\r\n anchorParent.appendChild(div);\r\n this._floating = div;\r\n\r\n this.el.setAttribute("aria-controls", div.id);\r\n this.el.setAttribute("aria-expanded", "false");\r\n if (this.kind === "popover") {\r\n this.el.setAttribute("aria-haspopup", "dialog");\r\n }\r\n }\r\n\r\n show() {\r\n if (this._open) return;\r\n this._open = true;\r\n\r\n // Prevent native tooltip doubling\r\n if (this.kind === "tooltip") {\r\n const t = this.el.getAttribute("title");\r\n if (t != null) {\r\n this.el.setAttribute("data-ux-original-title", t);\r\n this.el.removeAttribute("title");\r\n }\r\n }\r\n\r\n this._create();\r\n this._floating.style.display = "block";\r\n this._floating.classList.add("show");\r\n this.el.setAttribute("aria-expanded", "true");\r\n \r\n init(this._floating);\r\n U.dispatch(this.el, `ux4g.${this.kind}.contentmounted`, {\r\n floating: this._floating\r\n });\r\n \r\n const update = () => {\r\n if (!this._open) return;\r\n if (!this.el.isConnected) {\r\n this.hide();\r\n if (this._floating) {\r\n this._floating.remove();\r\n this._floating = null;\r\n }\r\n return;\r\n }\r\n U.placeFloating(this.el, this._floating, this.placement, this.offset, true);\r\n if (this.kind !== "popover") this._raf = requestAnimationFrame(update);\r\n };\r\n this._onWin = update;\r\n if (this.kind === "popover") {\r\n update();\r\n } else {\r\n this._raf = requestAnimationFrame(update);\r\n }\r\n\r\n U.on(window, "scroll", this._onWin, { capture: true, passive: true });\r\n U.on(window, "resize", this._onWin);\r\n\r\n if (this.kind === "popover" && typeof MutationObserver !== "undefined") {\r\n this._domObserver?.disconnect();\r\n this._domObserver = new MutationObserver(() => {\r\n if (this.el.isConnected) return;\r\n this.hide();\r\n if (this._floating) {\r\n this._floating.remove();\r\n this._floating = null;\r\n }\r\n });\r\n this._domObserver.observe(\r\n (this.el.ownerDocument || document).body,\r\n { childList: true, subtree: true }\r\n );\r\n }\r\n\r\n U.dispatch(this.el, `ux4g.${this.kind}.shown`, {});\r\n }\r\n\r\n hide() {\r\n if (!this._open) return;\r\n this._open = false;\r\n\r\n if (this._floating) {\r\n this._floating.classList.remove("show");\r\n this._floating.style.display = "none";\r\n this._floating.removeAttribute("data-ux-resolved-placement");\r\n }\r\n this.el.setAttribute("aria-expanded", "false");\r\n\r\n if (this.kind === "tooltip") {\r\n const ot = this.el.getAttribute("data-ux-original-title");\r\n if (ot != null) {\r\n this.el.setAttribute("title", ot);\r\n this.el.removeAttribute("data-ux-original-title");\r\n }\r\n }\r\n\r\n if (this._raf) {\r\n cancelAnimationFrame(this._raf);\r\n this._raf = null;\r\n }\r\n\r\n if (this._onWin) {\r\n U.off(window, "scroll", this._onWin, { capture: true });\r\n U.off(window, "resize", this._onWin);\r\n this._onWin = null;\r\n }\r\n\r\n if (this._domObserver) {\r\n this._domObserver.disconnect();\r\n this._domObserver = null;\r\n }\r\n\r\n U.dispatch(this.el, `ux4g.${this.kind}.hidden`, {});\r\n }\r\n\r\n toggle() { this._open ? this.hide() : this.show(); }\r\n\r\n _bind() {\r\n let triggers = String(this.trigger).split(/\\s+/).filter(
1Boolean);\r\n \r\n if (this.kind === "popover") {\r\n triggers = triggers.filter(t => t !== "hover");\r\n if (!triggers.length) triggers = ["click"];\r\n }\r\n\r\n if (triggers.includes("hover")) {\r\n U.on(this.el, "mouseenter", () => this.show());\r\n U.on(this.el, "mouseleave", () => this.hide());\r\n }\r\n if (triggers.includes("focus")) {\r\n U.on(this.el, "focus", () => this.show());\r\n U.on(this.el, "blur", () => this.hide());\r\n }\r\n if (triggers.includes("click")) {\r\n U.on(this.el, "click", (e) => { e.preventDefault(); this.toggle(); });\r\n\r\n U.on(document, "click", (e) => {\r\n if (!this._open) return;\r\n if (this.el.contains(e.target) || (this._floating && this._floating.contains(e.target))) return;\r\n this.hide();\r\n });\r\n\r\n U.on(document, "keydown", (e) => {\r\n if (!this._open) return;\r\n if (e.key === "Escape") {\r\n e.preventDefault();\r\n this.hide();\r\n window.setTimeout(() => {\r\n this.el.focus({ preventScroll: true });\r\n }, 0);\r\n }\r\n });\r\n }\r\n }\r\n\r\n static getOrCreate(el, kind) {\r\n const key = kind;\r\n let inst = getI(el, key);\r\n if (!inst) { inst = new Floating(el, kind); setI(el, key, inst); }\r\n return inst;\r\n }\r\n }\r\n\r\n // -----------------------------\r\n // Toast\r\n // -----------------------------\r\n class Toast {\r\n constructor(el) {\r\n this.el = el;\r\n this._timer = null;\r\n\r\n U.on(this.el, "click", (e) => {\r\n const dismiss = U.closest(e.target, \'[data-bs-dismiss="toast"],[data-ux-dismiss="toast"],.close-toast\');\r\n if (dismiss) {\r\n e.preventDefault();\r\n this.hide();\r\n }\r\n });\r\n }\r\n\r\n show() {\r\n this.el.classList.add("show");\r\n this.el.classList.remove("hide");\r\n\r\n const autohide = U.bool(U.data(this.el, "autohide", "true"), true);\r\n const delay = U.num(U.data(this.el, "delay", 5000), 5000);\r\n\r\n if (autohide) {\r\n clearTimeout(this._timer);\r\n this._timer = setTimeout(() => this.hide(), delay);\r\n }\r\n U.dispatch(this.el, "ux4g.toast.shown", {});\r\n }\r\n\r\n hide() {\r\n this.el.classList.remove("show");\r\n this.el.classList.add("hide");\r\n clearTimeout(this._timer);\r\n U.dispatch(this.el, "ux4g.toast.hidden", {});\r\n }\r\n\r\n static getOrCreate(el) {\r\n let inst = getI(el, "toast");\r\n if (!inst) { inst = new Toast(el); setI(el, "toast", inst); }\r\n return inst;\r\n }\r\n }\r\n\r\n // -----------------------------\r\n // Carousel\r\n // -----------------------------\r\n class Carousel {\r\n constructor(el) {\r\n this.el = el;\r\n this.items = U.qsa(".carousel-item", el);\r\n this.indicators = U.qsa("[data-bs-slide-to],[data-ux-slide-to]", el);\r\n this.interval = U.num(U.data(el, "interval", 5000), 5000);\r\n this.ride = U.data(el, "ride");\r\n this.pause = U.data(el, "pause", "hover");\r\n this.wrap = U.bool(U.data(el, "wrap", "true"), true);\r\n this._timer = null;\r\n\r\n U.on(el, "click", (e) => {\r\n const prev = U.closest(e.target, \'[data-bs-slide="prev"],[data-ux-slide="prev"]\');\r\n const next = U.closest(e.target, \'[data-bs-slide="next"],[data-ux-slide="next"]\');\r\n if (prev) { e.preventDefault(); this.prev(); }\r\n if (next) { e.preventDefault(); this.next(); }\r\n\r\n const ind = U.closest(e.target, "[data-bs-slide-to],[data-ux-slide-to]");\r\n if (ind) {\r\n e.preventDefault();\r\n const v = ind.getAttribute("data-bs-slide-to") ?? ind.getAttribute("data-ux-slide-to");\r\n this.to(U.num(v, 0));\r\n }\r\n });\r\n\r\n if (this.pause === "hover") {\r\n U.on(el, "mouseenter", () => this._stop());\r\n U.on(el, "mouseleave", () => this._start());\r\n }\r\n\r\n if (this.ride === "carousel") this._start();\r\n }\r\n\r\n _activeIndex() {\r\n const idx = this.items.findIndex(i => i.classList.contains("active"));\r\n return idx >= 0 ? idx : 0;\r\n }
1\r\n\r\n _setActive(nextIndex) {\r\n if (!this.items.length) return;\r\n const cur = this._activeIndex();\r\n\r\n if (nextIndex < 0) nextIndex = this.wrap ? this.items.length - 1 : 0;\r\n if (nextIndex >= this.items.length) nextIndex = this.wrap ? 0 : this.items.length - 1;\r\n if (cur === nextIndex) return;\r\n\r\n this.items[cur]?.classList.remove("active");\r\n this.items[nextIndex]?.classList.add("active");\r\n\r\n this.indicators.forEach(ind => ind.classList.remove("active"));\r\n const ind = this.indicators.find(x => {\r\n const v = x.getAttribute("data-bs-slide-to") ?? x.getAttribute("data-ux-slide-to");\r\n return U.num(v, -1) === nextIndex;\r\n });\r\n if (ind) ind.classList.add("active");\r\n\r\n U.dispatch(this.el, "ux4g.carousel.slid", { from: cur, to: nextIndex });\r\n }\r\n\r\n next() { this._setActive(this._activeIndex() + 1); }\r\n prev() { this._setActive(this._activeIndex() - 1); }\r\n to(i) { this._setActive(i); }\r\n\r\n _start() {\r\n if (this._timer || this.interval <= 0) return;\r\n this._timer = setInterval(() => this.next(), this.interval);\r\n }\r\n\r\n _stop() { clearInterval(this._timer); this._timer = null; }\r\n\r\n static getOrCreate(el) {\r\n let inst = getI(el, "carousel");\r\n if (!inst) { inst = new Carousel(el); setI(el, "carousel", inst); }\r\n return inst;\r\n }\r\n }\r\n\r\n // -----------------------------\r\n // Tabs\r\n // -----------------------------\r\n class Tab {\r\n constructor(el) {\r\n this.el = el;\r\n\r\n U.on(el, "click", (e) => { e.preventDefault(); this.show(); });\r\n\r\n U.on(el, "keydown", (e) => {\r\n if (e.key !== "ArrowLeft" && e.key !== "ArrowRight") return;\r\n const list = U.closest(this.el, ".nav, [role=\'tablist\']");\r\n if (!list) return;\r\n\r\n const tabs = U.qsa("[data-bs-toggle=\'tab\'],[data-ux-toggle=\'tab\'],[role=\'tab\']", list);\r\n const idx = tabs.indexOf(this.el);\r\n if (idx < 0) return;\r\n\r\n e.preventDefault();\r\n const next = (e.key === "ArrowRight") ? idx + 1 : idx - 1;\r\n const wrapIdx = (next + tabs.length) % tabs.length;\r\n tabs[wrapIdx].focus();\r\n Tab.getOrCreate(tabs[wrapIdx]).show();\r\n });\r\n }\r\n\r\n _target() {\r\n const sel = U.data(this.el, "target") || U.attr(this.el, "href") || U.attr(this.el, "data-target");\r\n if (sel && sel.startsWith("#")) return U.qs(sel);\r\n const controls = this.el.getAttribute("aria-controls");\r\n if (controls) return U.qs("#" + controls);\r\n return null;\r\n }\r\n\r\n show() {\r\n const list = U.closest(this.el, ".nav, [role=\'tablist\']");\r\n const pane = this._target();\r\n if (!list || !pane) return;\r\n\r\n const tabs = U.qsa("[data-bs-toggle=\'tab\'],[data-ux-toggle=\'tab\'],[role=\'tab\']", list);
1\r\n tabs.forEach(t => {\r\n t.classList.remove("active");\r\n t.setAttribute("aria-selected", "false");\r\n t.setAttribute("tabindex", "-1");\r\n });\r\n\r\n this.el.classList.add("active");\r\n this.el.setAttribute("aria-selected", "true");\r\n this.el.setAttribute("tabindex", "0");\r\n\r\n const container = U.closest(pane, ".tab-content") || pane.parentElement;\r\n const panes = container ? U.qsa(".tab-pane", container) : [];\r\n panes.forEach(p => p.classList.remove("active", "show"));\r\n\r\n pane.classList.add("active", "show");\r\n U.dispatch(this.el, "ux4g.tab.shown", { relatedTarget: pane });\r\n }\r\n\r\n static getOrCreate(el) {\r\n let inst = getI(el, "tab");\r\n if (!inst) { inst = new Tab(el); setI(el, "tab", inst); }\r\n return inst;\r\n }\r\n }\r\n\r\n // -----------------------------\r\n // Scrollspy\r\n // -----------------------------\r\n class ScrollSpy {\r\n constructor(el) {\r\n this.el = el; // element with data-bs-spy="scroll"\r\n this.targetSel = U.data(el, "target");\r\n this.offset = U.num(U.data(el, "offset", 10), 10);\r\n this._links = [];\r\n this._sections = [];\r\n\r\n this.refresh();\r\n this._bind();\r\n }\r\n\r\n refresh() {\r\n const nav = this.targetSel ? U.qs(this.targetSel) : null;\r\n if (!nav) return;\r\n\r\n this._links = U.qsa(\'a[href^="#"]\', nav)\r\n .filter(a => a.getAttribute("href").length > 1);\r\n\r\n this._sections = this._links\r\n .map(a => U.qs(a.getAttribute("href")))\r\n .filter(Boolean);\r\n }\r\n\r\n _activate(id) {\r\n const nav = this.targetSel ? U.qs(this.targetSel) : null;\r\n if (!nav) return;\r\n\r\n this._links.forEach(a => a.classList.remove("active"));\r\n const link = this._links.find(a => a.getAttribute("href") === "#" + id);\r\n if (link) link.classList.add("active");\r\n }\r\n\r\n _bind() {\r\n // If the spy is on body, use window; else use that element\r\n const container = (this.el === document.body || this.el === document.documentElement) ? window : this.el;\r\n\r\n if ("IntersectionObserver" in window) {\r\n const root = (container === window) ? null : this.el;\r\n\r\n const io = new IntersectionObserver((entries) => {\r\n const visible = entries\r\n .filter(e => e.isIntersecting)\r\n .sort((a, b) => b.intersectionRatio - a.intersectionRatio)[0];\r\n if (visible?.target?.id) this._activate(visible.target.id);\r\n }, {\r\n root,\r\n rootMargin: `-${this.offset}px 0px -60% 0px`,\r\n threshold: [0.1, 0.25, 0.5, 0.75]\r\n });\r\n\r\n this._sections.forEach(s => io.observe(s));\r\n this._io = io;\r\n return;\r\n }\r\n\r\n this._onScroll = () => {\r\n const scrollTop = (container === window) ? window.pageYOffset : this.el.scrollTop;\r\n let active = null;\r\n\r\n for (const s of this._sections) {\r\n const top = s.getBoundingClientRect().top + window.pageYOffset;\r\n if (scrollTop + this.offset >= top) active = s;\r\n }\r\n if (active?.id) this._activate(active.id);\r\n };\r\n\r\n U.on(container, "scroll", this._onScroll, { passive: true });\r\n this._onScroll();\r\n }\r\n\r\n static getOrCreate(el) {\r\n let inst = getI(el, "scrollspy");\r\n if (!inst) { inst = new ScrollSpy(el); setI(el, "scrollspy", inst); }\r\n return inst;\r\n }\r\n }\r\n\r\n // -----------------------------\r\n // Table\r\n // -----------------------------\r\n class Table {\r\n constructor(el) {\r\n this.el = el;\r\n this._bindSort();\r\n this._bindResize();\r\n this._bindSelection();\r\n this._bindFilter();\r\n }\r\n\r\n _bindFilter() {\r\n const filterBtns = U.qsa(".ux4g-table-filter-icon", this.el);\r\n filterBtns.forEach(btn => {\r\n U.on(btn, "click", (e) => {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n const targetTh = U.closest(btn, "th");\r\n if (targetTh) {\r\n // Close other filters first if needed or just toggle this one\r\n targetTh.
1classList.toggle("ux4g-is-filtering");\r\n if (targetTh.classList.contains("ux4g-is-filtering")) {\r\n const input = U.qs(".ux4g-search-input", targetTh);\r\n if (input) input.focus();\r\n }\r\n }\r\n });\r\n });\r\n\r\n const closeBtns = U.qsa(".ux4g-search-clear", this.el);\r\n closeBtns.forEach(btn => {\r\n U.on(btn, "click", (e) => {\r\n // Only close if it\'s within a table filter\r\n const th = U.closest(btn, "th.ux4g-is-filtering");\r\n if (th) {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n th.classList.remove("ux4g-is-filtering");\r\n const input = U.qs(".ux4g-search-input", th);\r\n if (input) {\r\n input.value = \'\';\r\n const container = U.closest(input, \'.ux4g-search-container\');\r\n if (container) container.classList.remove(\'ux4g-has-value\');\r\n }\r\n }\r\n });\r\n });\r\n\r\n const inputs = U.qsa("th .ux4g-search-input", this.el);\r\n inputs.forEach(input => {\r\n U.on(input, "input", (e) => {\r\n const container = U.closest(input, \'.ux4g-search-container\');\r\n if (container) {\r\n if (input.value.length > 0) {\r\n container.classList.add(\'ux4g-has-value\');\r\n } else {\r\n container.classList.remove(\'ux4g-has-value\');\r\n }\r\n }\r\n });\r\n });\r\n }\r\n\r\n _bindSort() {\r\n const sortableCols = U.qsa(".ux4g-table-sortable th[data-sort]", this.el);\r\n sortableCols.forEach(th => {\r\n if (!U.qs(".ux4g-table-sort-icon", th)) {\r\n const content = U.qs(".ux4g-table-th-content", th) || th;\r\n const icon = document.createElement("i");\r\n icon.className = "ux4g-icon ux4g-table-sort-icon";\r\n icon.innerHTML = "arrow_downward";\r\n content.appendChild(icon);\r\n }\r\n\r\n U.on(th, "click", (e) => {\r\n // If the click happened on a filter button or something else interactive, ignore sort\r\n if (U.closest(e.target, ".ux4g-table-filter-icon") || U.closest(e.target, ".ux4g-search-input") || U.closest(e.target, ".ux4g-search-clear")) {\r\n return;\r\n }\r\n\r\n const currentSort = U.attr(th, "data-sort", "none");\r\n // Cycle: none -> asc -> desc -> asc... (skip none once sorted to avoid double clicks)\r\n const nextSort = currentSort === "asc" ? "desc" : "asc";\r\n \r\n // Reset other columns in the same table\r\n sortableCols.forEach(otherTh => {\r\n if (otherTh !== th) otherTh.setAttribute("data-sort", "none");\r\n });\r\n\r\n th.setAttribute("data-sort", nextSort);\r\n \r\n // Row sorting logic\r\n if (nextSort !== "none") {\r\n const tbody = U.qs("tbody", this.el);\r\n if (tbody) {\r\n const trs = Array.from(tbody.querySelectorAll("tr"));\r\n const thIndex = Array.from(th.parentNode.children).indexOf(th);\r\n \r\n trs.sort((a, b) => {\r\n const aCol = a.children[thIndex];\r\n const bCol = b.children[thIndex];\r\n if (!aCol || !bCol) return 0;\r\n \r\n const aText = (aCol.textContent || aCol.innerText).trim();\r\n const bText = (bCol.textContent || bCol.innerText).trim();\r\n \r\n const cleanV1 = aText.replace(/[â¹$,\\s]/g, "");\r\n const cleanV2 = bText.replace(/[â¹$,\\s]/g, "");\r\n const num1 = Number(cleanV1);\r\n const num2 = Number(cleanV2);\r\n \r\n const isNum = !isNaN(num1) && !isNaN(num2) && cleanV1 !== "" && cleanV2 !== "";\r\n \r\n if (isNum) {\r\n return nextSort === "asc" ? (num1 - num2) : (num2 - num1);\r\n } else {\r\n const comp = aText.localeCompare(bText, undefined, { numeric: true, sensitivity: \'base\' });\r\n return nextSort === "asc" ? comp : -comp;\r\n }\r\n });
1\r\n \r\n trs.forEach(tr => tbody.appendChild(tr));\r\n }\r\n }\r\n\r\n U.dispatch(this.el, "ux4g.table.sort", { column: th, direction: nextSort });\r\n });\r\n });\r\n }\r\n\r\n _bindResize() {\r\n // Check if this table instance has resize enabled\r\n if (!this.el.classList.contains("ux4g-table-resizable")) return;\r\n const resizableCols = U.qsa("th", this.el);\r\n\r\n resizableCols.forEach(th => {\r\n // Skip last child to avoid out-of-bounds drag or provide a cleaner UX edge\r\n if (th === th.parentNode.lastElementChild) return;\r\n\r\n const handle = document.createElement("div");\r\n handle.className = "ux4g-table-resize-handle";\r\n th.appendChild(handle);\r\n\r\n let startX, startWidth;\r\n const onMouseMove = (e) => {\r\n const newWidth = Math.max(40, startWidth + (e.clientX - startX));\r\n th.style.width = `${newWidth}px`;\r\n th.style.minWidth = `${newWidth}px`;\r\n };\r\n\r\n const onMouseUp = () => {\r\n handle.classList.remove("is-resizing");\r\n handle.classList.remove("ux4g-is-resizing");\r\n U.off(document, "mousemove", onMouseMove);\r\n U.off(document, "mouseup", onMouseUp);\r\n U.dispatch(this.el, "ux4g.table.resize", { column: th, width: th.offsetWidth });\r\n };\r\n\r\n U.on(handle, "mousedown", (e) => {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n startX = e.clientX;\r\n startWidth = th.offsetWidth || th.getBoundingClientRect().width;\r\n handle.classList.add("ux4g-is-resizing");\r\n U.on(document, "mousemove", onMouseMove);\r\n U.on(document, "mouseup", onMouseUp);\r\n });\r\n });\r\n }\r\n\r\n _bindSelection() {\r\n // Find a select-all checkbox in the head\r\n const selectAll = U.qs("thead .ux4g-checkbox", this.el);\r\n if (!selectAll) return;\r\n\r\n const rowCheckboxes = U.qsa("tbody .ux4g-checkbox", this.el);\r\n if (!rowCheckboxes.length) return;\r\n \r\n const updateState = () => {\r\n let checkedCount = 0;\r\n rowCheckboxes.forEach(cb => {\r\n const tr = U.closest(cb, "tr");\r\n if (cb.checked) {\r\n checkedCount++;\r\n if (tr) tr.classList.add("ux4g-is-selected");\r\n } else {\r\n if (tr) tr.classList.remove("ux4g-is-selected");\r\n }\r\n });\r\n\r\n if (checkedCount === 0) {\r\n selectAll.checked = false;\r\n selectAll.indeterminate = false;\r\n } else if (checkedCount === rowCheckboxes.length) {\r\n selectAll.checked = true;\r\n selectAll.indeterminate = false;\r\n } else {\r\n selectAll.checked = false;\r\n selectAll.indeterminate = true;\r\n }\r\n };\r\n\r\n // Listen to select-all\r\n U.on(selectAll, "change", (e) => {\r\n const isChecked = e.target.checked;\r\n rowCheckboxes.forEach(cb => {\r\n cb.checked = isChecked;\r\n });\r\n updateState();\r\n });\r\n\r\n // Listen to individual row checkboxes\r\n rowCheckboxes.forEach(cb => {\r\n U.on(cb, "change", updateState);\r\n \r\n // Also allow row click to toggle checkbox, skipping if clicking on interactable elements\r\n const tr = U.closest(cb, "tr");\r\n if (tr && tr.classList.contains("ux4g-table-interactive")) {\r\n U.on(tr, "click", (e) => {\r\n if (e.target.tagName !== "INPUT" && e.target.tagName !== "BUTTON" && !U.closest(e.target, "button") && !U.closest(e.target, "a")) {\r\n cb.checked = !cb.checked;\r\n updateState();\r\n }\r\n });\r\n }\r\n });\r\n\r\n // Initialize state on load\r\n updateState();\r\n }\r\n\r\n static getOrCreate(el) {\r\n let inst = getI(el, "table");\r\n if (!inst) { inst = new Table(el); setI(el, "table", inst); }\r\n return inst;\r\n }\r\n }\r\n\r\n // -----------------------------\r\n // List\r\n // -----------------------------\r\n class List {\r\n constructor(el) {\r\n this.el = el;\r\n this._bind();\r\n }\r\n\r\n _bind() {\r\n U.on(this.el, "click", (e) => {\r\n const item = U.closest(e.target, ".ux4g-list-item-row") || U.closest(e.target, ".ux4g-list-select-item");\r\n if (!item || item.disabled) return;\r\n\r\n const checkbox = U.qs(\'input[type="checkbox"]\', item);\r\n const radio = U.qs(\'input[type="radio"]\', item);\r\n const switchInput = U.qs(\'.ux4g-switch-input\', item);\r\n\r\n if (radio && e.target !== radio && !U.closest(e.target, ".ux4g-radio-control")) {\r\n return;\r\n }
1\r\n\r\n const isMulti = (this.el.id === "ux4g-multiselect-list") || \r\n this.el.classList.contains("ux4g-multiselect") || \r\n this.el.classList.contains("ux4g-list-multiselect") ||\r\n (switchInput !== null);\r\n\r\n // If clicking on a label (but NOT the input itself), let the native browser behavior trigger the input click\r\n if (e.target.tagName !== \'INPUT\' && (e.target.tagName === \'LABEL\' || U.closest(e.target, \'label\'))) {\r\n return;\r\n }\r\n\r\n // If clicking on input directly, don\'t double toggle\r\n if (e.target.tagName === \'INPUT\') {\r\n const inputChecked = e.target.checked;\r\n \r\n if (!isMulti) {\r\n const allItems = U.qsa(".ux4g-list-item-row, .ux4g-list-select-item", this.el);\r\n allItems.forEach(i => {\r\n if (i !== item) i.classList.remove("active");\r\n // Also ensure other inputs are unchecked\r\n if (i !== item) {\r\n const otherInp = U.qs(\'input\', i);\r\n if (otherInp) otherInp.checked = false;\r\n }\r\n });\r\n item.classList.toggle("active", inputChecked);\r\n } else {\r\n item.classList.toggle("active", inputChecked);\r\n }\r\n return;\r\n }\r\n\r\n if (isMulti) {\r\n const isActive = item.classList.toggle("active");\r\n if (checkbox) {\r\n checkbox.checked = isActive;\r\n checkbox.dispatchEvent(new Event("change", { bubbles: true }));\r\n }\r\n if (switchInput) {\r\n switchInput.checked = isActive;\r\n switchInput.dispatchEvent(new Event("change", { bubbles: true }));\r\n }\r\n } else {\r\n // Single selection\r\n const wasActive = item.classList.contains("active");\r\n const allItems = U.qsa(".ux4g-list-item-row, .ux4g-list-select-item", this.el);\r\n \r\n // Clear all first\r\n allItems.forEach(i => {\r\n i.classList.remove("active");\r\n const cb = U.qs(\'input[type="checkbox"]\', i);\r\n const rb = U.qs(\'input[type="radio"]\', i);\r\n const sw = U.qs(\'.ux4g-switch-input\', i);\r\n if (cb) cb.checked = false;\r\n if (rb) rb.checked = false;\r\n if (sw) sw.checked = false;\r\n });\r\n\r\n // Toggle: only add if it wasn\'t already active\r\n if (!wasActive) {\r\n item.classList.add("active");\r\n if (checkbox) {\r\n checkbox.checked = true;\r\n checkbox.dispatchEvent(new Event("change", { bubbles: true }));\r\n }\r\n if (radio) {\r\n radio.checked = true;\r\n radio.dispatchEvent(new Event("change", { bubbles: true }));\r\n }\r\n if (switchInput) {\r\n switchInput.checked = true;\r\n switchInput.dispatchEvent(new Event("change", { bubbles: true }));\r\n }\r\n }\r\n }\r\n \r\n U.dispatch(this.el, "ux4g.list.change", { item, active: item.classList.contains("active") });\r\n });\r\n }\r\n\r\n static getOrCreate(el) {\r\n let inst = getI(el, "list");\r\n if (!inst) { inst = new List(el); setI(el, "list", inst); }\r\n return inst;\r\n }\r\n }\r\n\r\n // -----------------------------\r\n // Upload\r\n // -----------------------------\r\n class Upload {\r\n constructor(el) {\r\n this.el = el;\r\n this.input = el.querySelector(\'[data-ux-upload-input], .ux4g-upload-input\');\r\n this.dropzone = el.querySelector(\'.ux4g-upload-panel\');\r\n this.fileList = el.querySelector(\'.ux4g-upload-file-list\');\r\n this.errorMsg = el.querySelector(\'.ux4g-upload-error-msg\');\r\n this.errorText = el.querySelector(\'.ux4g-upload-error-text\');\r\n this.moreButton = el.querySelector(\'.ux4g-upload-more\');\r\n this.heading = el.querySelector(\'.ux4g-upload-heading\');\r\n this.defaultHeading = this.heading ? this.heading.textContent.trim() : \'\';\r\n this.files = [];\r\n this.retryItem = null;\r\n this.dragDepth = 0;\r\n this.stateClasses = [\r\n \'ux4g-upload-state-default\',\r\n \'ux4g-upload-state-default-vle\',\r\n \'ux4g-upload-state-selecting\',\r\n \'ux4g-upload-state-scanning\',\r\n \'ux4g-upload-state-uploaded\',\r\n \'ux4g-upload-state-uploaded-vle\',\r\n \'ux4g-upload-state-error\'\r\n ];\r\n this.maxSizeMB = U.num(U.data(el, \'max-size\', 5), 5);\r\n this.accept = (U.attr(this.input, \'accept\', \'\') || \'\')\r\n .split(\',\')\r\n .map(s => s.trim().toLowerCase())\r\n .filter(Boolean);\r\n this._bind();\r\n this._syncInitialState();\r\n }\r\n\r\n _bind() {\r\n U.on(this.el, \'click\', e => {\r\n if (U.closest(e.target, \'[data-ux-upload-trigger]\')) {\r\n this._openPicker(null);\r\n return;\r\n }\r\n if (U.closest(e.target, \'.ux4g-upload-file-remove\')) {\r\n const item = U.closest(e.target, \'.ux4g-upload-file-item\');\r\n this._removeFile(item);\r\n return;\r\n }
1\r\n if (U.closest(e.target, \'.ux4g-upload-file-retry\')) {\r\n const item = U.closest(e.target, \'.ux4g-upload-file-item-error\');\r\n this._openPicker(item);\r\n return;\r\n }\r\n if (U.closest(e.target, \'.ux4g-upload-more\')) {\r\n this._openPicker(null);\r\n }\r\n });\r\n\r\n U.on(this.dropzone, \'keydown\', e => {\r\n if (e.key === \' \' || e.key === \'Enter\') {\r\n e.preventDefault();\r\n this._openPicker();\r\n }\r\n });\r\n\r\n U.on(this.input, \'click\', () => {\r\n this.input.setAttribute(\'data-clicked\', \'true\');\r\n });\r\n\r\n U.on(this.input, \'change\', e => {\r\n const retryItem = this.retryItem;\r\n this.retryItem = null;\r\n this._addFiles(Array.from(e.target.files || []), retryItem);\r\n });\r\n\r\n U.on(this.dropzone, \'dragenter\', e => {\r\n e.preventDefault();\r\n this.dragDepth += 1;\r\n this._setState(\'selecting\');\r\n });\r\n\r\n U.on(this.dropzone, \'dragover\', e => {\r\n e.preventDefault();\r\n this._setState(\'selecting\');\r\n });\r\n\r\n U.on(this.dropzone, \'dragleave\', e => {\r\n e.preventDefault();\r\n this.dragDepth = Math.max(0, this.dragDepth - 1);\r\n const nextTarget = e.relatedTarget;\r\n if (this.dragDepth === 0 || !nextTarget || !this.dropzone.contains(nextTarget)) {\r\n this._clearActive();\r\n }\r\n });\r\n\r\n U.on(this.dropzone, \'drop\', e => {\r\n e.preventDefault();\r\n this.dragDepth = 0;\r\n this._clearActive();\r\n this._addFiles(Array.from((e.dataTransfer && e.dataTransfer.files) || []));\r\n });\r\n }\r\n\r\n _openPicker(retryItem = null) {\r\n if (!this.input) return;\r\n this.retryItem = retryItem;\r\n this.input.setAttribute(\'data-clicked\', \'true\');\r\n this.input.click();\r\n }\r\n\r\n _setState(state) {\r\n this.el.classList.remove(\'ux4g-upload-state-selecting\', \'ux4g-upload-state-error\');\r\n if (state === \'selecting\') this.el.classList.add(\'ux4g-upload-state-selecting\');\r\n if (state === \'error\') this.el.classList.add(\'ux4g-upload-state-error\');\r\n this._syncDragHeading(state === \'selecting\');\r\n }\r\n\r\n _clearActive() {\r\n this.el.classList.remove(\'ux4g-upload-state-selecting\');\r\n this._syncDragHeading(false);\r\n }\r\n\r\n _syncDragHeading(isDragging) {\r\n if (!this.heading) return;\r\n this.heading.textContent = isDragging ? \'Drop file here\' : this.defaultHeading;\r\n }\r\n\r\n _showError(msg, file) {\r\n this.el.classList.remove(\'ux4g-upload-state-selecting\');\r\n this.el.classList.add(\'ux4g-upload-state-error\');\r\n this._clearErrorRows();\r\n this._renderErrorFile(file, msg);\r\n if (this.errorMsg) this.errorMsg.classList.add(\'ux4g-d-none\');\r\n }\r\n\r\n _clearError(clearRows = true) {\r\n this.el.classList.remove(\'ux4g-upload-state-error\');\r\n if (this.errorMsg) this.errorMsg.classList.add(\'ux4g-d-none\');\r\n if (this.errorText) this.errorText.textContent = \'\';\r\n if (clearRows) this._clearErrorRows();\r\n }\r\n\r\n _clearErrorRows() {\r\n if (!this.fileList) return;\r\n this.fileList.querySelectorAll(\'.ux4g-upload-file-item-error[data-ux-upload-error-row="true"]\').forEach(item => item.remove());\r\n }\r\n\r\n _validate(file) {\r\n const parts = file.name.split(\'.\');\r\n const ext = parts.length > 1 ? `.${parts.pop().toLowerCase()}` : \'\';\r\n if (this.accept.length && !this.accept.includes(ext)) {\r\n return `File type not allowed: ${ext || \'unknown\'}`;\r\n }\r\n if (file.size > this.maxSizeMB * 1024 * 1024) {\r\n return `File too large. Max size: ${this.maxSizeMB} MB`;\r\n }\r\n return null;\r\n }\r\n\r\n _addFiles(incoming, retryItem = null) {\r\n let errorOccurred = false;\r\n let retryHandled = false;\r\n\r\n incoming.forEach(file => {\r\n const retryTarget = !retryHandled && retryItem && retryItem.isConnected ? retryItem : null;
1\r\n const err = this._validate(file);\r\n if (err) {\r\n errorOccurred = true;\r\n if (retryTarget) {\r\n this.el.classList.add(\'ux4g-upload-state-error\');\r\n this._renderErrorFile(file, err, retryTarget);\r\n retryHandled = true;\r\n } else {\r\n this._showError(err, file);\r\n }\r\n U.dispatch(this.el, \'ux4g.upload.error\', { file, reason: err });\r\n return;\r\n }\r\n\r\n this.files.push(file);\r\n this._renderFile(file, retryTarget);\r\n if (retryTarget) retryHandled = true;\r\n U.dispatch(this.el, \'ux4g.upload.added\', { file });\r\n });\r\n\r\n if (!errorOccurred) {\r\n const hasErrorRows = this.fileList && this.fileList.querySelector(\'.ux4g-upload-file-item-error\');\r\n this._clearError(!retryHandled);\r\n if (hasErrorRows) this.el.classList.add(\'ux4g-upload-state-error\');\r\n }\r\n this._syncHasFiles();\r\n this.input.value = \'\';\r\n }\r\n\r\n _renderFile(file, replaceItem = null) {\r\n if (!this.fileList) return;\r\n\r\n const sizeKB = file.size / 1024;\r\n const sizeLabel = sizeKB >= 1024\r\n ? `${(sizeKB / 1024).toFixed(1)} MB`\r\n : `${Math.max(1, Math.round(sizeKB))} KB`;\r\n\r\n const li = document.createElement(\'li\');\r\n li.className = \'ux4g-upload-file-item\';\r\n li.setAttribute(\'role\', \'listitem\');\r\n li.dataset.fileName = file.name;\r\n li.innerHTML = `\r\n <div class="ux4g-upload-file-row">\r\n <span class="ux4g-upload-file-leading" aria-hidden="true">\r\n <span class="ux4g-icon-outlined ux4g-upload-file-icon">token</span>\r\n </span>\r\n <span class="ux4g-upload-file-copy">\r\n <span class="ux4g-body-m-strong ux4g-upload-file-name">${this._escape(file.name)}</span>\r\n <span class="ux4g-body-s-default ux4g-upload-file-description">${sizeLabel}</span>\r\n </span>\r\n <span class="ux4g-upload-file-statusbox" aria-hidden="true">\r\n <span class="ux4g-icon-outlined ux4g-upload-file-status">done</span>\r\n </span>\r\n <button type="button" class="ux4g-upload-file-remove" aria-label="Remove ${this._escape(file.name)}">\r\n <span class="ux4g-icon-outlined" aria-hidden="true">close</span>\r\n </button>\r\n </div>\r\n `;\r\n if (replaceItem && replaceItem.isConnected) {\r\n replaceItem.replaceWith(li);\r\n } else {\r\n this.fileList.appendChild(li);\r\n }\r\n }\r\n\r\n _renderErrorFile(file, reason, replaceItem = null) {\r\n if (!this.fileList) return;\r\n\r\n const label = file && file.name ? file.name : \'Document_name.pdf\';\r\n const li = document.createElement(\'li\');\r\n li.className = \'ux4g-upload-file-item ux4g-upload-file-item-error\';\r\n li.setAttribute(\'role\', \'listitem\');\r\n li.dataset.uploadErrorRow = \'true\';\r\n li.dataset.errorReason = reason || \'\';\r\n li.innerHTML = `\r\n <div class="ux4g-upload-file-row">\r\n <span class="ux4g-upload-file-leading" aria-hidden="true">\r\n <span class="ux4g-icon-outlined ux4g-upload-file-icon">error_outline</span>\r\n </span>\r\n <span class="ux4g-upload-file-copy">\r\n <span class="ux4g-body-m-strong ux4g-upload-file-name">${this._escape(label)}</span>\r\n <span class="ux4g-body-s-default ux4g-upload-file-description">${this._escape(reason || \'Description\')}</span>\r\n </span>\r\n <button type="button" class="ux4g-upload-file-retry" aria-label="Retry upload">\r\n <span class="ux4g-icon-outlined" aria-hidden="true">replay</span>\r\n <span class="ux4g-label-l-default">Retry</span>\r\n </button>\r\n </div>\r\n `;\r\n if (replaceItem && replaceItem.isConnected) {\r\n replaceItem.replaceWith(li);\r\n }
1 else {\r\n this.fileList.appendChild(li);\r\n }\r\n }\r\n\r\n _removeFile(item) {\r\n const name = item && item.dataset.fileName;\r\n this.files = this.files.filter(f => f.name !== name);\r\n if (item) item.remove();\r\n this._syncHasFiles();\r\n U.dispatch(this.el, \'ux4g.upload.removed\', { name });\r\n }\r\n\r\n _syncHasFiles() {\r\n const hasSuccessfulRows = this.fileList && this.fileList.querySelector(\'.ux4g-upload-file-item:not(.ux4g-upload-file-item-error)\');\r\n const hasErrorRows = this.fileList && this.fileList.querySelector(\'.ux4g-upload-file-item-error\');\r\n const hasFiles = this.files.length > 0 || !!hasSuccessfulRows || !!hasErrorRows;\r\n if (this.fileList) this.fileList.classList.toggle(\'ux4g-d-none\', !hasFiles);\r\n if (this.moreButton) this.moreButton.classList.toggle(\'ux4g-d-none\', !(this.files.length > 0 || !!hasSuccessfulRows));\r\n this._deriveBaseState(hasFiles);\r\n }\r\n\r\n _syncInitialState() {\r\n if (this.fileList) {\r\n this.files = Array.from(this.fileList.querySelectorAll(\'.ux4g-upload-file-item:not(.ux4g-upload-file-item-error)\'))\r\n .map(item => ({ name: item.dataset.fileName || item.textContent.trim(), size: 0 }));\r\n }\r\n if (this.el.classList.contains(\'ux4g-upload-state-error\')) {\r\n if (this.errorMsg) this.errorMsg.classList.remove(\'ux4g-d-none\');\r\n } else {\r\n this._clearError();\r\n }\r\n this._syncHasFiles();\r\n }\r\n\r\n _deriveBaseState(hasFiles) {\r\n const variant = U.data(this.el, \'variant\', \'default\');\r\n this.stateClasses.forEach(cls => {\r\n if (cls !== \'ux4g-upload-state-selecting\' && cls !== \'ux4g-upload-state-error\') {\r\n this.el.classList.remove(cls);\r\n }\r\n });\r\n\r\n if (this.el.classList.contains(\'ux4g-upload-state-error\')) return;\r\n if (variant === \'scanning\') {\r\n this.el.classList.add(\'ux4g-upload-state-scanning\');\r\n return;\r\n }\r\n if (variant === \'default-vle\') {\r\n this.el.classList.add(hasFiles ? \'ux4g-upload-state-uploaded-vle\' : \'ux4g-upload-state-default-vle\');\r\n return;\r\n }\r\n this.el.classList.add(hasFiles ? \'ux4g-upload-state-uploaded\' : \'ux4g-upload-state-default\');\r\n }\r\n\r\n _escape(str) {\r\n return String(str).replace(/[&<>"\']/g, c => ({ \'&\': \'&\', \'<\': \'<\', \'>\': \'>\', \'"\': \'"\', "\'": \''\' }[c]));\r\n }\r\n\r\n static getOrCreate(el) {\r\n let inst = getI(el, \'upload\');\r\n if (!inst) { inst = new Upload(el); setI(el, \'upload\', inst); }\r\n return inst;\r\n }\r\n }\r\n\r\n // -----------------------------\r\n // OTP\r\n // -----------------------------\r\n class OtpInput {\r\n constructor(el) {\r\n this.el = el;\r\n this.group = U.qs(\'.ux4g-otp-group\', el);\r\n this.sourceInput = U.qs(\'.ux4g-otp-source\', el);\r\n this.resend = U.qs(\'[data-ux-otp-resend]\', el);\r\n this.status = U.qs(\'[data-ux-otp-status]\', el);\r\n this.helper = U.qs(\'[data-ux-otp-helper]\', el);\r\n this.timerTargets = U.qsa(\'[data-ux-otp-timer]\', el);\r\n this.state = U.data(el, \'state\', \'default\');\r\n this.count = Math.max(1, U.num(U.data(el, \'count\', 0), 0) || 1);\r\n this.placeholder = this.sourceInput?.getAttribute(\'placeholder\') || \'-\';\r\n this.demoErrorOnComplete = U.bool(U.data(el, \'demo-error-on-complete\', \'false\'), false);\r\n this.noSeparator = U.bool(U.data(el, \'no-separator\', \'false\'), false);\r\n this._timerId = null;\r\n this._shakeTimer = null;\r\n this._completedByUser = false;\r\n this._observer = null;\r\n this._demoErrorTimer = null;\r\n\r\n this._renderInputs();\r\n this.inputs = U.qsa(\'.ux4g-otp-input\', el);\r\n this.length = this.count;\r\n this._syncInputs();\r\n this._observeErrorState();\r\n this._bind();\r\n this._applyVisualFocus();\r\n this._startTimers();\r\n }\r\n\r\n _renderInputs() {\r\n if (!this.group) return;\r\n\r\n const digits = this._getDigits();\r\n this.group.replaceChildren();
1\r\n if (this.sourceInput) this.group.append(this.sourceInput);\r\n\r\n for (let index = 0; index < this.count; index += 1) {\r\n const slot = document.createElement(\'div\');\r\n slot.className = \'ux4g-input ux4g-otp-slot\';\r\n\r\n const input = document.createElement(\'input\');\r\n input.className = \'ux4g-input-input ux4g-otp-input ux4g-body-m-default\';\r\n input.type = \'text\';\r\n input.setAttribute(\'aria-label\', `Digit ${index + 1}`);\r\n\r\n if (digits[index]) {\r\n input.value = digits[index];\r\n } else {\r\n input.placeholder = this.placeholder;\r\n }\r\n\r\n this._syncInputTone(input);\r\n slot.append(input);\r\n this.group.append(slot);\r\n\r\n if (index < this.count - 1 && !this.noSeparator) {\r\n const separator = document.createElement(\'span\');\r\n separator.className = \'ux4g-otp-separator ux4g-icon-outlined\';\r\n separator.setAttribute(\'aria-hidden\', \'true\');\r\n separator.textContent = \'horizontal_rule\';\r\n this.group.append(separator);\r\n }\r\n }\r\n }\r\n\r\n _getDigits() {\r\n return ((this.sourceInput?.value || \'\').replace(/\\D/g, \'\').slice(0, this.count)).split(\'\');\r\n }\r\n\r\n _bind() {\r\n this.inputs.forEach((input, index) => {\r\n U.on(input, \'focus\', () => this._setFocused(index));\r\n U.on(input, \'click\', () => this._setFocused(index));\r\n U.on(input, \'input\', e => this._onInput(e, index));\r\n U.on(input, \'keydown\', e => this._onKeydown(e, index));\r\n });\r\n }\r\n\r\n _isInteractive() {\r\n return this.state === \'default\' || this.state === \'partial-filled\' || this.state === \'all-filled\';\r\n }\r\n\r\n _syncInputs() {\r\n this.inputs.forEach((input, index) => {\r\n input.setAttribute(\'inputmode\', \'numeric\');\r\n input.setAttribute(\'pattern\', \'[0-9]*\');\r\n input.setAttribute(\'autocomplete\', index === 0 ? \'one-time-code\' : \'off\');\r\n input.setAttribute(\'maxlength\', \'1\');\r\n input.placeholder = input.value ? \'\' : this.placeholder;\r\n this._syncInputTone(input);\r\n\r\n if (!this._isInteractive()) {\r\n input.setAttribute(\'readonly\', \'readonly\');\r\n input.setAttribute(\'tabindex\', \'-1\');\r\n }\r\n\r\n if (this.state === \'locked-out\') {\r\n input.setAttribute(\'disabled\', \'disabled\');\r\n }\r\n });\r\n }\r\n\r\n _syncInputTone(input) {\r\n input.classList.toggle(\'ux4g-title-m-strong\', !!input.value);\r\n input.classList.toggle(\'ux4g-body-m-default\', !input.value);\r\n }\r\n\r\n _setFocused(index) {\r\n this.inputs.forEach((input, inputIndex) => {\r\n input.closest(\'.ux4g-otp-slot\')?.classList.toggle(\'ux4g-otp-focus\', inputIndex === index);\r\n input.classList.toggle(\'ux4g-otp-caret\', inputIndex === index && !input.value);\r\n input.placeholder = input.value ? \'\' : (inputIndex === index ? \'\' : this.placeholder);\r\n });\r\n }\r\n\r\n _syncFocusClass() {\r\n const active = this.inputs.findIndex(input => input === document.activeElement);\r\n if (active >= 0) this._setFocused(active);\r\n }\r\n\r\n _clearFocused() {\r\n this.inputs.forEach(input => {\r\n input.closest(\'.ux4g-otp-slot\')?.classList.remove(\'ux4g-otp-focus\');\r\n input.classList.remove(\'ux4g-otp-caret\');\r\n input.placeholder = input.value ? \'\' : this.placeholder;\r\n });\r\n }\r\n\r\n _applyVisualFocus() {\r\n if (this.state === \'default\') {\r\n this._setFocused(0);\r\n return;\r\n }\r\n\r\n if (this.state === \'partial-filled\') {\r\n const emptyIndex = this.inputs.findIndex(input => !input.value);\r\n this._setFocused(emptyIndex >= 0 ? emptyIndex : this.inputs.length - 1);\r\n return;\r\n }\r\n\r\n if (this.state === \'all-filled\') {\r\n this._setFocused(this.inputs.length - 1);\r\n return;\r\n }\r\n\r\n this._syncFocusClass();\r\n }\r\n\r\n _onInput(e, index) {\r\n const input = e.target;\r\n const value = (input.value || \'\').replace(/\\D/g, \'\').slice(-1);\r\n input.value = value;\r\n input.placeholder = value ? \'\' : this.placeholder;\r\n this._syncInputTone(input);\r\n this._clearDemoErrorState();\r\n\r\n this._syncSourceValue();\r\n\r\n if (!value) return;\r\n\r\n if (index < this.inputs.length - 1) {\r\n this.inputs[index + 1].focus();\r\n }\r\n\r\n this._updateStateFromValue();\r\n }\r\n\r\n _onKeydown(e, index) {\r\n const input = e.target;\r\n\r\n if (e.key === \'Backspace\') {\r\n if (input.value) {\r\n input.value = \'\';\r\n input.placeholder = this.placeholder;\r\n this._syncInputTone(input);\r\n this._clearDemoErrorState();\r\n this._syncSourceValue();\r\n this._completedByUser = false;\r\n this._updateStateFromValue();\r\n return;\r\n }\r\n\r\n if (index > 0) {\r\n const prev = this.inputs[index - 1];\r\n prev.value = \'\';\r\n prev.placeholder = this.placeholder;\r\n this._syncInputTone(prev);\r\n prev.focus();\r\n this._clearDemoErrorState();\r\n this._syncSourceValue();\r\n this._completedByUser = false;\r\n this._updateStateFromValue();\r\n }\r\n return;\r\n }\r\n\r\n if (e.key === \'ArrowLeft\' && index > 0) {\r\n e.preventDefault();\r\n this.inputs[index - 1].focus();\r\n return;\r\n }\r\n\r\n if (e.key === \'ArrowRight\' && index < this.inputs.length - 1) {\r\n e.preventDefault();\r\n this.inputs[index + 1].focus();\r\n }\r\n }
1\r\n\r\n _syncSourceValue() {\r\n if (!this.sourceInput) return;\r\n this.sourceInput.value = this.inputs.map(input => input.value).join(\'\');\r\n }\r\n\r\n _updateStateFromValue() {\r\n if (!this._isInteractive()) return;\r\n const filled = this.inputs.filter(input => input.value).length;\r\n this._completedByUser = filled === this.length;\r\n const next = filled === 0 ? \'default\' : (filled === this.length ? \'all-filled\' : \'partial-filled\');\r\n this.el.setAttribute(\'data-ux-state\', next);\r\n if (this.demoErrorOnComplete && this._completedByUser) {\r\n this._scheduleDemoErrorState();\r\n }\r\n }\r\n\r\n _isErrorState() {\r\n return this.el.classList.contains(\'ux4g-otp-error\') || U.data(this.el, \'state\', \'\') === \'error\';\r\n }\r\n\r\n _triggerShakeIfError() {\r\n if (!this.group) return;\r\n if (!this._completedByUser || this.inputs.some(input => !input.value) || !this._isErrorState()) return;\r\n\r\n this.group.classList.remove(\'ux4g-otp-shake\');\r\n void this.group.offsetWidth;\r\n this.group.classList.add(\'ux4g-otp-shake\');\r\n\r\n if (this._shakeTimer) global.clearTimeout(this._shakeTimer);\r\n this._shakeTimer = global.setTimeout(() => {\r\n this.group?.classList.remove(\'ux4g-otp-shake\');\r\n this._shakeTimer = null;\r\n }, 400);\r\n }\r\n\r\n _observeErrorState() {\r\n this._observer = new MutationObserver(() => this._triggerShakeIfError());\r\n this._observer.observe(this.el, {\r\n attributes: true,\r\n attributeFilter: [\'class\', \'data-ux-state\']\r\n });\r\n }\r\n\r\n _scheduleDemoErrorState() {\r\n if (this._demoErrorTimer) global.clearTimeout(this._demoErrorTimer);\r\n this._demoErrorTimer = global.setTimeout(() => {\r\n if (!this._completedByUser || this.inputs.some(input => !input.value)) return;\r\n this._clearFocused();\r\n if (document.activeElement && this.inputs.includes(document.activeElement)) {\r\n document.activeElement.blur();\r\n }\r\n this.el.classList.add(\'ux4g-otp-error\');\r\n this.el.setAttribute(\'data-ux-state\', \'error\');\r\n this.el.setAttribute(\'aria-invalid\', \'true\');\r\n if (this.helper) {\r\n this.helper.outerHTML = \'<span class="ux4g-otp-status" data-ux-otp-status><span class="ux4g-icon-outlined" aria-hidden="true">error</span><span>Attempt 2 of 3</span></span>\';\r\n this.helper = null;\r\n this.status = U.qs(\'[data-ux-otp-status]\', this.el);\r\n }\r\n }, 300);\r\n }\r\n\r\n _clearDemoErrorState() {\r\n if (!this.demoErrorOnComplete) return;\r\n if (this._demoErrorTimer) {\r\n global.clearTimeout(this._demoErrorTimer);\r\n this._demoErrorTimer = null;\r\n }\r\n this.el.classList.remove(\'ux4g-otp-error\');\r\n this.el.setAttribute(\'data-ux-state\', this.inputs.some(input => input.value) ? \'partial-filled\' : \'default\');\r\n this.el.removeAttribute(\'aria-invalid\');\r\n if (!this.helper && this.status) {\r\n this.status.outerHTML = \'<span class="ux4g-otp-helper" data-ux-otp-helper>Didnât receive OTP?</span>\';\r\n this.helper = U.qs(\'[data-ux-otp-helper]\', this.el);\r\n this.status = null;\r\n }\r\n }\r\n\r\n _formatTime(totalSeconds) {\r\n const safe = Math.max(0, totalSeconds);\r\n const minutes = Math.floor(safe / 60);\r\n const seconds = safe % 60;\r\n return `${String(minutes).padStart(2, \'0\')}:${String(seconds).padStart(2, \'0\')}`;\r\n }\r\n\r\n _tickTimer(node) {\r\n const total = U.num(node.getAttribute(\'data-ux-otp-seconds\'), 0);\r\n const prefix = node.getAttribute(\'data-ux-otp-prefix\') || \'\';\r\n node.textContent = `${prefix}${this._formatTime(total)}`;\r\n if (total > 0) {\r\n node.setAttribute(\'data-ux-otp-seconds\', String(total - 1));\r\n }\r\n }\r\n\r\n _startTimers() {\r\n if (!this.timerTargets.length) return;
1\r\n this.timerTargets.forEach(node => this._tickTimer(node));\r\n this._timerId = global.setInterval(() => {\r\n this.timerTargets.forEach(node => this._tickTimer(node));\r\n }, 1000);\r\n }\r\n\r\n static getOrCreate(el) {\r\n let inst = getI(el, \'otp\');\r\n if (!inst) { inst = new OtpInput(el); setI(el, \'otp\', inst); }\r\n return inst;\r\n }\r\n }\r\n\r\n // -----------------------------\r\n // SLA Progress\r\n // -----------------------------\r\n class SlaProgress {\r\n constructor(el) {\r\n this.el = el;\r\n this.valueTargets = U.qsa(\'.ux4g-sla-linear-value\', el);\r\n this.circleValue = U.qs(\'.ux4g-sla-circle-value\', el);\r\n this.circleMeta = U.qs(\'.ux4g-sla-circle-meta\', el);\r\n this.sync();\r\n }\r\n\r\n sync() {\r\n const progress = Math.min(100, Math.max(0, U.num(U.data(this.el, \'progress\', 0), 0)));\r\n this.el.style.setProperty(\'--ux4g-sla-progress\', String(progress));\r\n\r\n if (this.el.hasAttribute(\'data-ux-sla-linear\')) {\r\n this.valueTargets.forEach(node => {\r\n node.textContent = `${Math.round(progress)}%`;\r\n });\r\n if (!this.el.getAttribute(\'role\')) {\r\n this.el.setAttribute(\'role\', \'progressbar\');\r\n }\r\n this.el.setAttribute(\'aria-valuemin\', \'0\');\r\n this.el.setAttribute(\'aria-valuemax\', \'100\');\r\n this.el.setAttribute(\'aria-valuenow\', String(Math.round(progress)));\r\n }\r\n\r\n if (this.el.hasAttribute(\'data-ux-sla-circle\')) {\r\n const days = U.data(this.el, \'days\', null);\r\n if (days != null && this.circleValue) {\r\n this.circleValue.textContent = String(days);\r\n }\r\n if (this.circleMeta) {\r\n this.circleMeta.textContent = Number(days) === 1 ? \'day left\' : \'days left\';\r\n }\r\n }\r\n }\r\n\r\n static getOrCreate(el) {\r\n let inst = getI(el, \'sla-progress\');\r\n if (!inst) { inst = new SlaProgress(el); setI(el, \'sla-progress\', inst); }\r\n return inst;\r\n }\r\n }\r\n\r\n class ProgressIndicator {\r\n constructor(el) {\r\n this.el = el;\r\n this.ensureStructure();\r\n this.labelTargets = U.qsa(\'[data-ux-progress-label]\', el);\r\n this.descTargets = U.qsa(\'[data-ux-progress-desc]\', el);\r\n this.endpointStart = U.qs(\'[data-ux-progress-start]\', el);\r\n this.endpointEnd = U.qs(\'[data-ux-progress-end]\', el);\r\n this.halfTrackTail = U.qs(\'.ux4g-progress-half-track-tail\', el);\r\n this.halfRoundedProgress = U.qs(\'[data-ux-progress-half-svg-progress]\', el);\r\n this.halfRoundedTrack = U.qs(\'[data-ux-progress-half-svg-track]\', el);\r\n this.halfSharpProgress = U.qs(\'[data-ux-progress-half-svg-progress-sharp]\', el);\r\n this.halfSharpTrack = U.qs(\'[data-ux-progress-half-svg-track-sharp]\', el);\r\n this.sync();\r\n }\r\n\r\n ensureStructure() {\r\n if (!this.el.hasAttribute(\'data-ux-progress-half\')) return;\r\n if (U.qs(\'.ux4g-progress-half-arc\', this.el)) return;\r\n\r\n const size = (this.el.getAttribute(\'data-ux-size\') || \'m\').toLowerCase();\r\n const shape = this.el.getAttribute(\'data-ux-shape\') || \'sharp\';\r\n const config = {\r\n s: { width: 80, height: 80, radius: 30, stroke: 10, roundedStroke: 8, labelClass: \'ux4g-label-l-strong\', descriptionClass: \'ux4g-body-xs-default\', endpoints: false },\r\n m: { width: 160, height: 160, radius: 70, stroke: 16, roundedStroke: 16, labelClass: \'ux4g-heading-m-strong\', descriptionClass: \'ux4g-body-xs-default\', endpoints: true },\r\n l: { width: 200, height: 200, radius: 90, stroke: 20, roundedStroke: 20, labelClass: \'ux4g-heading-m-strong\', descriptionClass: \'ux4g-body-xs-default\', endpoints: true },\r\n xl: { width: 240, height: 240, radius: 105, stroke: 24, roundedStroke: 24, labelClass: \'ux4g-heading-xl-strong\', descriptionClass: \'ux4g-body-s-default\', endpoints: true }\r\n }[size] || { width: 160, height: 160, radius: 70, stroke: 16, roun
1dedStroke: 16, labelClass: \'ux4g-heading-m-strong\', descriptionClass: \'ux4g-body-xs-default\', endpoints: true };\r\n\r\n if (!this.el.hasAttribute(\'data-ux-radius\')) {\r\n this.el.setAttribute(\'data-ux-radius\', String(config.radius));\r\n }\r\n\r\n const description = this.el.getAttribute(\'data-ux-description\') || \'Description\';\r\n const start = this.el.getAttribute(\'data-ux-start-label\') || \'0%\';\r\n const end = this.el.getAttribute(\'data-ux-end-label\') || \'100%\';\r\n\r\n const roundedArcMarkup = `\r\n <svg class="ux4g-progress-half-svg" viewBox="0 0 ${config.width} ${config.height}" aria-hidden="true" focusable="false">\r\n <defs>\r\n <linearGradient id="ux4g-progress-half-gradient-${size}" x1="0" y1="${config.height / 2}" x2="${config.width}" y2="${config.height / 2}" gradientUnits="userSpaceOnUse">\r\n <stop offset="0%" stop-color="var(--ux4g-progress-fill-start)" />\r\n <stop offset="100%" stop-color="var(--ux4g-progress-fill-end)" />\r\n </linearGradient>\r\n </defs>\r\n <path class="ux4g-progress-half-svg-track" d="${progressHalfRoundedArcPath(config.width, config.roundedStroke)}" data-ux-progress-half-svg-track></path>\r\n <path class="ux4g-progress-half-svg-progress" d="${progressHalfRoundedArcPath(config.width, config.roundedStroke)}" pathLength="100" data-ux-progress-half-svg-progress></path>\r\n </svg>`;\r\n const sharpArcMarkup = `\r\n <svg class="ux4g-progress-half-svg ux4g-progress-half-svg-sharp" viewB
1ox="0 0 ${config.width} ${config.height}" aria-hidden="true" focusable="false">\r\n <defs>\r\n <linearGradient id="ux4g-progress-half-gradient-sharp-${size}" x1="0" y1="${config.height / 2}" x2="${config.width}" y2="${config.height / 2}" gradientUnits="userSpaceOnUse">\r\n <stop offset="0%" stop-color="var(--ux4g-progress-fill-start)" />\r\n <stop offset="100%" stop-color="var(--ux4g-progress-fill-end)" />\r\n </linearGradient>\r\n </defs>\r\n <path class="ux4g-progress-half-svg-track ux4g-progress-half-svg-track-sharp" d="${progressHalfRoundedArcPath(config.width, config.stroke)}" data-ux-progress-half-svg-track-sharp></path>\r\n <path class="ux4g-progress-half-svg-progress ux4g-progress-half-svg-progress-sharp" d="${progressHalfRoundedArcPath(config.width, config.stroke)}" data-ux-progress-half-svg-progress-sharp></path>\r\n </svg>`;\r\n const arcMarkup = shape === \'rounded\' ? roundedArcMarkup : sharpArcMarkup;\r\n\r\n this.el.innerHTML = `<div class="ux4g-progress-half-arc" aria-hidden="true">${arcMarkup}</div><div class="ux4g-progress-half-copy"><span class="${config.labelClass}" data-ux-progress-label>50%</span><p class="ux4g-progress-half-description ${config.descriptionClass}" data-ux-progress-desc>${description}</p></div>${config.endpoints ? `<div class="ux4g-progress-half-endpoints"><span class="ux4g-body-xs-default" data-ux-progress-start>${start}</span><span class="ux4g-body-xs-default" data-ux-progress-end>${end}</span></div>` : \'\'}`;\r\n }\r\n\r\n sync() {\r\n const progress = Math.min(100, Math.max(0, U.num(U.data(this.el, \'progress\', 0), 0)));\r\n this.el.style.setProperty(\'--ux4g-progress-value\', String(progress));\r\n\r\n this.labelTargets.forEach(node => {\r\n node.textContent = `${Math.round(progress)}%`;\r\n });\r\n\r\n if (this.el.hasAttribute(\'data-ux-progress-half\')) {\r\n const radius = U.num(U.data(this.el, \'radius\', 0), 0);\r\n this.el.style.setProperty(\'--ux4g-progress-half-angle\', `${progress * 1.8}deg`);\r\n if (this.halfRoundedProgress) {\r\n const size = this.halfRoundedProgress.ownerSVGElement.viewBox.baseVal.width;\r\n const stroke = U.num(getComputedStyle(this.el).getPropertyValue(\'--ux4g-progress-half-stroke\').replace(\'px\', \'\'), 0);\r\n this.halfRoundedProgress.setAttribute(\'d\', progressHalfRoundedArcPath(size, stroke, 180, 180 + (progress * 1.8)));\r\n this.halfRoundedProgress.style.strokeDasharray = \'\';\r\n if (this.halfRoundedTrack) {\r\n if (progress >= 100) {\r\n this.halfRoundedTrack.setAttribute(\'d\', \'\');\r\n } else {\r\n const trackStartAngle = progress <= 0 ? 180 : 180 + (progress * 1.8);\r\n this.halfRoundedTrack.setAttribute(\'d\', progressHalfRoundedArcPath(size, stroke, trackStartAngle, 360));\r\n }\r\n }\r\n if (this.halfRoundedTrackCap) {\r\n this.halfRoundedTrackCap.style.display = progress < 100 ? \'block\' : \'none\';\r\n }\r\n }\r\n if (this.halfSharpProgress) {\r\n const size = this.halfSharpProgress.ownerSVGElement.viewBox.baseVal.width;\r\n const stroke = U.num(getComputedStyle(this.el).getPropertyValue(\'--ux4g-progress-half-stroke\').replace(\'px\', \'\'), 0);\r\n this.halfSharpProgress.setAttribute(\'d\', progressHalfRoundedArcPath(size, stroke, 180, 180 + (progress * 1.8)));\r\n if (this.halfSharpTrack) {\r\n if (progress >= 100) {\r\n this.halfSharpTrack.setAttribute(\'d\', \'\');\r\n } else {\r\n const trackStartAngle = progress <= 0 ? 180 : 180 + (progress * 1.8);\r\n this.halfSharpTrack.setAttribute(\'d\', progressHalfRoundedArcPath(size, stroke, trackStartAngle, 360));\r\n }\r\n }\r\n }\r\n if (this.halfTrackTail) {\r\n this.halfTrackTail.hidden = true;\r\n }\r\n }\r\n\r\n const description = this.el.getAttribute(\'data-ux-description\');
1\r\n if (description) {\r\n this.descTargets.forEach(node => {\r\n node.textContent = description;\r\n });\r\n }\r\n\r\n const start = this.el.getAttribute(\'data-ux-start-label\');\r\n const end = this.el.getAttribute(\'data-ux-end-label\');\r\n if (this.endpointStart && start != null) this.endpointStart.textContent = start;\r\n if (this.endpointEnd && end != null) this.endpointEnd.textContent = end;\r\n\r\n if (!this.el.getAttribute(\'role\')) {\r\n this.el.setAttribute(\'role\', \'progressbar\');\r\n }\r\n this.el.setAttribute(\'aria-valuemin\', \'0\');\r\n this.el.setAttribute(\'aria-valuemax\', \'100\');\r\n this.el.setAttribute(\'aria-valuenow\', String(Math.round(progress)));\r\n }\r\n\r\n static getOrCreate(el) {\r\n let inst = getI(el, \'progress-indicator\');\r\n if (!inst) { inst = new ProgressIndicator(el); setI(el, \'progress-indicator\', inst); }\r\n return inst;\r\n }\r\n }\r\n\r\n function progressCircleLabelClass(size, placement) {\r\n if (placement === \'outside\') return \'ux4g-label-xl-strong\';\r\n if (size === \'xs\' || size === \'s\' || size === \'m\') return \'ux4g-label-m-strong\';\r\n if (size === \'l\') return \'ux4g-label-l-strong\';\r\n return \'ux4g-label-xl-strong\';\r\n }\r\n\r\n function progressCircleDescriptionClass(size, placement) {\r\n if (placement === \'outside\') return \'ux4g-body-s-default\';\r\n return (size === \'xl\' || size === \'2xl\' || size === \'3xl\') ? \'ux4g-body-s-default\' : \'ux4g-body-xs-default\';\r\n }\r\n\r\n function progressHalfLabelClass(size) {\r\n return size === \'s\' ? \'ux4g-label-l-strong\' : \'ux4g-heading-m-strong\';\r\n }\r\n\r\n function progressHalfRoundedArcPath(size, stroke, startAngle = 180, endAngle = 360) {\r\n const radius = (size / 2) - (stroke / 2);\r\n const center = size / 2;\r\n const start = polarPoint(center, radius, startAngle);\r\n const end = polarPoint(center, radius, endAngle);\r\n const largeArcFlag = Math.abs(endAngle - startAngle) > 180 ? 1 : 0;\r\n return `M ${start.x} ${start.y} A ${radius} ${radius} 0 ${largeArcFlag} 1 ${end.x} ${end.y}`;\r\n }\r\n\r\n function polarPoint(center, radius, angleDeg) {\r\n const angle = (angleDeg * Math.PI) / 180;\r\n return {\r\n x: center + (radius * Math.cos(angle)),\r\n y: center + (radius * Math.sin(angle))\r\n };\r\n }\r\n\r\n function buildProgressBarDemo(shape, placement, progress) {\r\n const inside = placement === \'inside\';\r\n return `<article class="ux4g-progress-bar" data-ux-progress-bar data-ux-shape="${shape}" data-ux-label-placement="${placement}" data-ux-progress="${progress}" role="progressbar" aria-label="${shape} ${placement} bar ${progress} percent"><div class="ux4g-progress-bar-track"><div class="ux4g-progress-bar-fill">${inside ? `<span class="ux4g-progress-bar-label ux4g-progress-bar-label-inside ux4g-label-s-strong" data-ux-progress-label>${progress}%</span>` : \'\'}</div></div>${inside ? \'\' : `<span class="ux4g-progress-bar-label ux4g-progress-bar-label-outside ux4g-label-s-strong" data-ux-progress-label>${progress}%</span>`}</article>`;\r\n }\r\n\r\n function buildProgressCircleDemo(shape, size, placement) {\r\n const labelClass = progressCircleLabelClass(size, placement);\r\n const showInsideDescription = placement === \'inside\' && (size === \'xl\' || size === \'2xl\' || size === \'3xl\');\r\n const descClass = progressCircleDescriptionClass(size, placement);\r\n return `<div class="ux4g-progress-size-demo"><article class="ux4g-progress-circle" data-ux-progress-circle data-ux-shape="${shape}" data-ux-size="${size}" data-ux-label-placement="${placement}" data-ux-progress="50" ${(placement === \'outside\' || showInsideDescription) ? \'data-ux-description="Description"\' : \'\'} role="progressbar" aria-label="${size} ${shape} circle ${placement} 50 percent"><div class="ux4g-progress-circle-indicator"><span class="ux4g-progress-circle-ring"></span>${placement === \'inside\' ? `<div class="ux4g-progress-circle-value-wrap"><span class="${labelClass}" data-ux-progress-label>50%</span>${showInsideDescription ? `<p class="ux4g-progress-circle-description ${descClass}" data-ux-progress-desc>Description</p>` : \'\'}</div>` : \'\'}</div>${placement === \'outside\' ? `<div class="ux4g-progress-circle-copy"><span class="${labelClass}" data-ux-progress-label>50%</span>
1<p class="ux4g-progress-circle-description ${descClass}" data-ux-progress-desc>Description</p></div>` : \'\'}</article></div>`;\r\n }\r\n\r\n function renderProgressIndicatorDemos(root = document) {\r\n const steps = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100];\r\n const barContainers = [\r\n [\'sharp-outside\', \'sharp\', \'outside\'],\r\n [\'rounded-outside\', \'rounded\', \'outside\'],\r\n [\'sharp-inside\', \'sharp\', \'inside\'],\r\n [\'rounded-inside\', \'rounded\', \'inside\']\r\n ];\r\n barContainers.forEach(([key, shape, placement]) => {\r\n const container = root.querySelector(`[data-ux-progress-demo-bars="${key}"]`);\r\n if (!container) return;\r\n container.innerHTML = steps.map(progress => buildProgressBarDemo(shape, placement, progress)).join(\'\');\r\n });\r\n\r\n const circleSizes = [\'xs\', \'s\', \'m\', \'l\', \'xl\', \'2xl\', \'3xl\'];\r\n const circleHeadings = root.querySelector(\'[data-ux-progress-demo-circle-headings]\');\r\n if (circleHeadings) circleHeadings.innerHTML = \'\';\r\n const circleRows = [\r\n [\'sharp-inside\', \'sharp\', \'inside\'],\r\n [\'sharp-outside\', \'sharp\', \'outside\'],\r\n [\'rounded-inside\', \'rounded\', \'inside\'],\r\n [\'rounded-outside\', \'rounded\', \'outside\']\r\n ];\r\n circleRows.forEach(([key, shape, placement]) => {\r\n const row = root.querySelector(`[data-ux-progress-demo-circles="${key}"]`);\r\n if (!row) return;\r\n row.innerHTML = circleSizes.map(size => buildProgressCircleDemo(shape, size, placement)).join(\'\');\r\n });\r\n\r\n }\r\n\r\n // -----------------------------\r\n // Data API init\r\n // -----------------------------\r\n function init(root = document) {\r\n renderProgressIndicatorDemos(root);\r\n\r\n // Dropdown\r\n U.qsa(\'[data-bs-toggle="dropdown"],[data-ux-toggle="dropdown"]\', root).forEach(Dropdown.getOrCreate);\r\n\r\n // Collapse\r\n U.qsa(\'[data-bs-toggle="collapse"],[data-ux-toggle="collapse"],[ux4g-toggle="collapse"]\', root).forEach(Collapse.getOrCreate);\r\n\r\n // Tabs\r\n U.qsa(\'[data-bs-toggle="tab"],[data-ux-toggle="tab"]\', root).forEach(Tab.getOrCreate);\r\n\r\n // Tooltips / Popovers\r\n U.qsa(\'[data-bs-toggle="tooltip"],[data-ux-toggle="tooltip"]\', root).forEach(el => Floating.getOrCreate(el, "tooltip"));\r\n U.qsa(\'[data-bs-toggle="popover"],[data-ux-toggle="popover"]\', root).forEach(el => Floating.getOrCreate(el, "popover"));\r\n\r\n // Toasts (wires dismiss; does not auto-show unless .show is already present)\r\n U.qsa(".toast", root).forEach(Toast.getOrCreate);\r\n\r\n // Carousels\r\n U.qsa(".carousel", root).forEach(Carousel.getOrCreate);\r\n\r\n // Scrollspy\r\n U.qsa(\'[data-bs-spy="scroll"],[data-ux-spy="scroll"]\', root).forEach(ScrollSpy.getOrCreate);\r\n\r\n // Table Interactions\r\n U.qsa(".ux4g-table", root).forEach(Table.getOrCreate);\r\n\r\n // List Interactions\r\n U.qsa(".ux4g-list", root).forEach(List.getOrCreate);\r\n\r\n // Upload\r\n U.qsa(\'[data-ux-upload]\', root).forEach(Upload.getOrCreate);\r\n\r\n // OTP\r\n U.qsa(\'[data-ux-otp]\', root).forEach(OtpInput.getOrCreate);\r\n\r\n // SLA Progress\r\n U.qsa(\'[data-ux-sla-circle],[data-ux-sla-linear]\', root).forEach(SlaProgress.getOrCreate);\r\n\r\n // Progress Indicators\r\n U.qsa(\'[data-ux-progress-bar],[data-ux-progress-circle],[data-ux-progress-half]\', root).forEach(ProgressIndicator.getOrCreate);\r\n\r\n // Validation: Ensure ux4g-multiselect-list ID is only used on .ux4g-list elements\r\n const multiselectIdEls = root.querySelectorAll(\'#ux4g-multiselect-list\');\r\n multiselectIdEls.forEach(el => {\r\n if (!el.classList.contains(\'ux4g-list\')) {\r\n console.warn(`[UX4G Validation] The ID \'ux4g-multiselect-list\' should only be used on elements with the \'ux4g-list\' class. Found on:`, el);\r\n }\r\n });\r\n }\r\n\r\n // Delegated toggles for Offcanvas, Toast & other components\r\n U.on(document, "click", (e) => {\r\n const radio = U.closest(e.target, ".ux4g-radio");\r\n if (radio && !U.closest(e.target, ".ux4g-radio-input, .ux4g-radio-control")) {\r\n e.preventDefault();\r\n return;\r\n }\r\n\r\n // UX4G offcanvas toggle\r\n const offBtn = U.closest(e.target, \'[data-bs-toggle="offcanvas"],[data-ux-toggle="offcanvas"]\');\r\n if (offBtn) {\r\n e.preventDefault();\r\n const sel = U.data(offBtn, "target") || U.attr(offBtn, "href");\r\n const el = sel && sel.startsWith("#") ? U.qs(sel) : null;\r\n if (el) Offcanvas.getOrCreate(el).toggle(offBtn);\r\n return;\r\n }\r\n\r\n // Toast close class bridge (your HTML)\r\n const closeToast = U.closest(e.target, ".close-toast");\r\n if (closeToast) {\r\n e.preventDefault();\r\n const toastEl = U.closest(closeToast, ".toast");\r\n if (toastEl) Toast.getOrCreate(toastEl).hide();\r\n return;\r\n }\r\n\r\n });\r\n\r\n // Switch keyboard support (Enter to toggle)\r\n U.on(document, "keydown", (e) => {\r\n if (e.key === "Enter") {\r\n const input = U.closest(e.target, ".ux4g-switch-input");\r\n if (input && !input.disabled) {\r\n e.preventDefault();\r\n input.checked = !input.checked;\r\n input.dispatchEvent(new Event("change", { bubbles: true }));\r\n }\r\n }\r\n });\r\n\r\n // Your demo: #liveToastBtn shows #liveToast\r\n U.on(document, "DOMContentLoaded", () => {\r\n const btn = U.qs("#liveToastBtn");\r\n if (btn) {\r\n U.on(btn, "click", () => {\r\n const toastEl = U.qs("#liveToast");\r\n if (toastEl) Toast.getOrCreate(toastEl).show();\r\n });\r\n }\r\n });\r\n\r\n U.on(document, "ux4g.upload.error", (e) => {\r\n if (typeof global.showContextAlert === "function") {\r\n global.showContextAlert("top-right", "error", "Upload Failed", e.detail.reason);\r\n }\r\n });\r\n\r\n // Auto-init\r\n if (document.readyState === "loading") {\r\n U.on(document, "DOMContentLoaded", () => init(document));\r\n } else {\r\n init(document);\r\n }\r\n\r\n // ------------------------------------------------------------------\r\n // SPA Support: Auto-initialization\r\n // ------------------------------------------------------------------\r\n global.ux4gCustomInitList = global.ux4gCustomInitList || [];\r\n\r\n const attachedGlobalListeners = new Set();\r\n \r\n // Safe wrapper for global event listeners to prevent double-binding\r\n const originalDocAddEventListener = document.addEventListener;\r\n const originalWinAddEventListener = window.addEventListener;\r\n \r\n function safeDocAddEventListener(type, listener, options) {\r\n if (type === \'DOMContentLoaded\') {\r\n return originalDocAddEventListener.call(document, type, listener, options);\r\n }
1\r\n const key = \'doc:\' + type + \':\' + listener.toString().substring(0, 100);\r\n if (!attachedGlobalListeners.has(key)) {\r\n attachedGlobalListeners.add(key);\r\n originalDocAddEventListener.call(document, type, listener, options);\r\n }\r\n }\r\n \r\n function safeWinAddEventListener(type, listener, options) {\r\n const key = \'win:\' + type + \':\' + listener.toString().substring(0, 100);\r\n if (!attachedGlobalListeners.has(key)) {\r\n attachedGlobalListeners.add(key);\r\n originalWinAddEventListener.call(window, type, listener, options);\r\n }\r\n }\r\n\r\n // Safe wrapper for querySelectorAll to prevent double-binding on elements\r\n const originalDocQSA = Document.prototype.querySelectorAll;\r\n const originalElementQSA = Element.prototype.querySelectorAll;\r\n \r\n function rewriteSelector(selector) {\r\n if (typeof selector === \'string\' && !selector.includes(\'[data-ux4g-init]\')) {\r\n return selector.split(\',\').map(s => s.trim() + \':not([data-ux4g-init])\').join(\', \');\r\n }\r\n return selector;\r\n }\r\n \r\n function tagNodes(nodes) {\r\n nodes.forEach(n => {\r\n if (n.setAttribute) n.setAttribute(\'data-ux4g-init\', \'true\');\r\n });\r\n return nodes;\r\n }\r\n\r\n function safeDocQSA(selector) {\r\n const newSelector = rewriteSelector(selector);\r\n const nodes = originalDocQSA.call(this, newSelector);\r\n return selector !== newSelector ? tagNodes(nodes) : nodes;\r\n }\r\n\r\n function safeElementQSA(selector) {\r\n const newSelector = rewriteSelector(selector);\r\n const nodes = originalElementQSA.call(this, newSelector);\r\n return selector !== newSelector ? tagNodes(nodes) : nodes;\r\n }\r\n\r\n global.ux4gCustomInit = function() {\r\n // Mock the environment\r\n document.addEventListener = safeDocAddEventListener;\r\n window.addEventListener = safeWinAddEventListener;\r\n Document.prototype.querySelectorAll = safeDocQSA;\r\n Element.prototype.querySelectorAll = safeElementQSA;\r\n document.querySelectorAll = safeDocQSA;\r\n\r\n // Run all registered initializers\r\n if (global.ux4gCustomInitList) {\r\n global.ux4gCustomInitList.forEach(fn => {\r\n try { fn(); } catch (e) {}\r\n });\r\n }\r\n\r\n // Restore the environment (always runs, even if an init function throws)\r\n document.addEventListener = originalDocAddEventListener;\r\n window.addEventListener = originalWinAddEventListener;\r\n Document.prototype.querySelectorAll = originalDocQSA;\r\n Element.prototype.querySelectorAll = originalElementQSA;\r\n document.querySelectorAll = originalDocQSA;\r\n };\r\n\r\n if (typeof MutationObserver !== "undefined") {\r\n const observer = new MutationObserver((mutations) => {\r\n cancelAnimationFrame(global._ux4gInitRaf);\r\n global._ux4gInitRaf = requestAnimationFrame(() => {\r\n for (let i = 0; i < mutations.length; i++) {\r\n for (let j = 0; j < mutations[i].addedNodes.length; j++) {\r\n const node = mutations[i].addedNodes[j];\r\n if (node.nodeType === 1) {\r\n init(node);\r\n }\r\n }\r\n }\r\n global.ux4gCustomInit();\r\n });\r\n });\r\n \r\n const startObserving = () => {\r\n if (document.body) {\r\n observer.observe(document.body, { childList: true, subtree: true });\r\n } else {\r\n setTimeout(startObserving, 50);\r\n }\r\n };\r\n startObserving();\r\n }\r\n\r\n // Expose API\r\n global.ux4g = {\r\n version: "1.1.0",\r\n U,\r\n getI,\r\n setI,\r\n escapeHtml,\r\n init,\r\n Dropdown,\r\n Collapse,\r\n Offcanvas,\r\n Toast,\r\n Carousel,\r\n Popover: {\r\n getOrCreate(el) { return Floating.getOrCreate(el, "popover"); }\r\n },\r\n Tab,\r\n ScrollSpy,\r\n Table,\r\n List,\r\n Upload,\r\n OtpInput,\r\n Theme: {\r\n get() {\r\n return document.documentElement.getAttribute("data-theme") || (window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");\r\n },\r\n set(theme) {\r\n document.documentElement.setAttribute("data-theme", theme);\r\n window.dispatchEvent(new CustomEvent("ux4g.theme.changed", { detail: { theme } }));\r\n },\r\n toggle() {\r\n const next = this.get() === "dark" ? "light" : "dark";\r\n this.set(next);\r\n }\r\n }\r\n };\r\n\r\n // Auto-init theme if not set\r\n U.on(document, "DOMContentLoaded", () => {\r\n if (!document.documentElement.hasAttribute("data-theme")) {\r\n const preferred = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";\r\n document.documentElement.setAttribute("data-theme", preferred);\r\n }\r\n });\r\n\r\n})(typeof window !== "undefined" ? window : this);\r\n\r\n',document.head.appendChild(e);let t=document.createElement("script");t.setAttribute("data-ux4g-runtime","custom"),t.textContent='/* ========================================================= tooltips js ========================================================= */\r\n(function (ux4g) {\r\n if (!ux4g) return;\r\n const U = ux4g.U;\r\n const getI = ux4g.getI;\r\n const setI = ux4g.setI;\r\n const escapeHtml = ux4g.escapeHtml;\r\n\r\n class Floating {\r\n constructor(el, kind) {\r\n this.el = el;\r\n this.kind = kind; // tooltip | popover\r\n this._open = false;\r\n this._floating = null;\r\n\r\n this.placement = U.data(el, "placement", kind === "tooltip" ? "top" : "right");\r\n this.offset = U.num(U.data(el, "offset", 8), 8);\r\n this.trigger = U.data(el, "trigger", kind === "tooltip" ? "hover focus" : "click");\r\n this.html = U.bool(U.data(el, "html", "false"), false);\r\n\r\n this._bind();\r\n }\r\n\r\n _getContent() {\r\n const content = U.data(this.el, "content");\r\n if (content != null) return this.html ? String(content) : escapeHtml(content);\r\n\r\n if (this.kind === "popover") {\r\n const title = U.data(this.el, "title") || this.el.getAttribute("title") || "";\r\n const body = this.el.getAttribute("data-content") || "";\r\n const t = this.html ? String(title) : escapeHtml(title);\r\n const b = this.html ? String(body) : escapeHtml(body);\r\n return `<div class="ux4g-popover-header">${t}</div><div class="ux4g-popover-body">${b}</div>`;\r\n }\r\n\r\n const t = this.el.getAttribute("title") || "";\r\n return this.html ? String(t) : escapeHtml(t);\r\n }\r\n\r\n _create() {\r\n if (this._floating) return;\r\n const div = document.createElement("div");\r\n div.className = this.kind === "tooltip" ? "ux4g-tooltip" : "ux4g-popover";\r\n div.setAttribute("role", this.kind === "tooltip" ? "tooltip" : "dialog");\r\n div.innerHTML = this._getContent() || "";\r\n document.body.appendChild(div);\r\n this._floating = div;\r\n }\r\n\r\n show() {\r\n if (this._open) return;\r\n this._open = true;\r\n\r\n // Prevent native tooltip doubling\r\n if (this.kind === "tooltip") {\r\n const t = this.el.getAttribute("title");\r\n if (t != null) {\r\n this.el.setAttribute("data-ux-original-title", t);\r\n this.el.removeAttribute("title");\r\n }\r\n }\r\n\r\n this._create();\r\n this._floating.style.display = "block";\r\n this._floating.classList.add("show");\r\n const update = () => {\r\n if (!this._open) return;
1\r\n if (!this.el.isConnected) {\r\n this.hide();\r\n if (this._floating) {\r\n this._floating.remove();\r\n this._floating = null;\r\n }\r\n return;\r\n }\r\n U.placeFloating(this.el, this._floating, this.placement, this.offset);\r\n this._raf = requestAnimationFrame(update);\r\n };\r\n this._onWin = update;\r\n this._raf = requestAnimationFrame(update);\r\n U.on(window, "scroll", this._onWin, { passive: true });\r\n U.on(window, "resize", this._onWin);\r\n\r\n U.dispatch(this.el, `ux4g.${this.kind}.shown`, {});\r\n }\r\n\r\n hide() {\r\n if (!this._open) return;\r\n this._open = false;\r\n\r\n if (this._floating) {\r\n this._floating.classList.remove("show");\r\n this._floating.style.display = "none";\r\n }\r\n\r\n if (this.kind === "tooltip") {\r\n const ot = this.el.getAttribute("data-ux-original-title");\r\n if (ot != null) {\r\n this.el.setAttribute("title", ot);\r\n this.el.removeAttribute("data-ux-original-title");\r\n }\r\n }\r\n\r\n if (this._raf) {\r\n cancelAnimationFrame(this._raf);\r\n this._raf = null;\r\n }\r\n\r\n if (this._onWin) {\r\n U.off(window, "scroll", this._onWin);\r\n U.off(window, "resize", this._onWin);\r\n this._onWin = null;\r\n }\r\n\r\n U.dispatch(this.el, `ux4g.${this.kind}.hidden`, {});\r\n }\r\n\r\n toggle() { this._open ? this.hide() : this.show(); }\r\n\r\n _bind() {\r\n const triggers = String(this.trigger).split(/\\s+/).filter(Boolean);\r\n\r\n if (triggers.includes("hover")) {\r\n U.on(this.el, "mouseenter", () => this.show());\r\n U.on(this.el, "mouseleave", () => this.hide());\r\n }\r\n if (triggers.includes("focus")) {\r\n U.on(this.el, "focus", () => this.show());\r\n U.on(this.el, "blur", () => this.hide());\r\n }\r\n if (triggers.includes("click")) {\r\n U.on(this.el, "click", (e) => { e.preventDefault(); this.toggle(); });\r\n\r\n U.on(document, "click", (e) => {\r\n if (!this._open) return;\r\n if (this.el.contains(e.target) || (this._floating && this._floating.contains(e.target))) return;\r\n this.hide();\r\n });\r\n\r\n U.on(document, "keydown", (e) => {\r\n if (!this._open) return;\r\n if (e.key === "Escape") this.hide();\r\n });\r\n }\r\n }\r\n\r\n static getOrCreate(el, kind) {\r\n const key = kind;\r\n let inst = getI(el, key);\r\n if (!inst) { inst = new Floating(el, kind); setI(el, key, inst); }\r\n return inst;\r\n }\r\n }\r\n\r\n function repositionManualTooltip(tooltip) {\r\n if (!tooltip || tooltip.dataset.uxAdjusted === "true") return;\r\n\r\n const wrapper = tooltip.closest(\'.ux4g-tooltip-wrapper\');\r\n if (!wrapper) return;\r\n\r\n // We need to know where it *will* be once it\'s fully hovered\r\n const vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);\r\n const vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0);\r\n\r\n const originalTransition = tooltip.style.transition;\r\n const originalDisplay = tooltip.style.display;\r\n const originalOpacity = tooltip.style.opacity;\r\n\r\n // Temporarily disable transition and force show to measure target state\r\n tooltip.style.transition = \'none\';\r\n tooltip.style.display = \'flex\';\r\n tooltip.style.opacity = \'0\';\r\n\r\n // Force reflow\r\n tooltip.offsetHeight;\r\n\r\n let rect = tooltip.getBoundingClientRect();\r\n const wrapperRect = wrapper.getBoundingClientRect();\r\n const padding = 18;\r\n\r\n // Save original class to restore on mouseout\r\n if (!tooltip.dataset.uxOriginalClass) {\r\n tooltip.dataset.uxOriginalClass = tooltip.className;\r\n }\r\n\r\n let className = tooltip.className;
1\r\n let changed = false;\r\n\r\n // Vertical flip\r\n if (className.includes(\'-top-\') && rect.top < padding) {\r\n if (wrapperRect.bottom + rect.height + padding <= vh) {\r\n className = className.replace(\'-top-\', \'-bottom-\');\r\n changed = true;\r\n }\r\n } else if (className.includes(\'-bottom-\') && rect.bottom > vh - padding) {\r\n if (wrapperRect.top - rect.height - padding >= 0) {\r\n className = className.replace(\'-bottom-\', \'-top-\');\r\n changed = true;\r\n }\r\n }\r\n\r\n // Horizontal flip\r\n if (className.includes(\'-left-\') && !className.includes(\'-top-left\') && !className.includes(\'-bottom-left\') && rect.left < padding) {\r\n if (wrapperRect.right + rect.width + padding <= vw) {\r\n className = className.replace(\'-left-\', \'-right-\');\r\n changed = true;\r\n }\r\n } else if (className.includes(\'-right-\') && !className.includes(\'-top-right\') && !className.includes(\'-bottom-right\') && rect.right > vw - padding) {\r\n if (wrapperRect.left - rect.width - padding >= 0) {\r\n className = className.replace(\'-right-\', \'-left-\');\r\n changed = true;\r\n }\r\n }\r\n\r\n if (changed) {\r\n tooltip.className = className;\r\n tooltip.dataset.uxAdjusted = "true";\r\n rect = tooltip.getBoundingClientRect(); // Re-measure after class change\r\n }\r\n\r\n let shiftX = 0;\r\n let shiftY = 0;\r\n\r\n // Horizontal adjustment for top/bottom tooltips if still out of bounds\r\n if (className.includes(\'-top-\') || className.includes(\'-bottom-\')) {\r\n if (rect.left < padding) {\r\n shiftX = padding - rect.left;\r\n } else if (rect.right > vw - padding) {\r\n shiftX = (vw - padding) - rect.right;\r\n }\r\n }\r\n\r\n // Vertical adjustment for left/right tooltips if still out of bounds\r\n if (className.includes(\'-left-\') || className.includes(\'-right-\')) {\r\n if (!className.includes(\'-top-\') && !className.includes(\'-bottom-\')) {\r\n if (rect.top < padding) {\r\n shiftY = padding - rect.top;\r\n } else if (rect.bottom > vh - padding) {\r\n shiftY = (vh - padding) - rect.bottom;\r\n }\r\n }\r\n }\r\n\r\n if (shiftX !== 0 || shiftY !== 0) {\r\n const computedStyle = window.getComputedStyle(tooltip);\r\n const currentTransform = computedStyle.transform === \'none\' ? \'\' : computedStyle.transform;\r\n // Apply the fix\r\n tooltip.style.transform = `${currentTransform} translate(${shiftX}px, ${shiftY}px)`;\r\n tooltip.dataset.uxAdjusted = "true";\r\n }\r\n\r\n // Restore state (but keep the transform)\r\n tooltip.style.display = originalDisplay;\r\n tooltip.style.opacity = originalOpacity;\r\n // Delay restoring transition slightly to avoid "flying" into place from old position\r\n setTimeout(() => {\r\n tooltip.style.transition = originalTransition;\r\n }, 50);\r\n }\r\n\r\n // Initialize\r\n window.ux4gCustomInitList = window.ux4gCustomInitList || []; window.ux4gCustomInitList.push(() => {\r\n // Data API init\r\n U.qsa(\'[data-bs-toggle="tooltip"],[data-ux-toggle="tooltip"]\').forEach(el => Floating.getOrCreate(el, "tooltip"));\r\n\r\n // Auto-fix manual tooltips on hover\r\n document.body.addEventListener(\'mouseover\', (e) => {\r\n const wrapper = e.target.closest(\'.ux4g-tooltip-wrapper\');\r\n if (wrapper) {\r\n const tooltip = wrapper.querySelector(\'.ux4g-tooltip\');\r\n if (tooltip) repositionManualTooltip(tooltip);\r\n }\r\n });\r\n\r\n document.body.addEventListener(\'mouseout\', (e) => {\r\n const wrapper = e.target.closest(\'.ux4g-tooltip-wrapper\');\r\n if (wrapper && !wrapper.contains(e.relatedTarget)) {\r\n const tooltip = wrapper.querySelector(\'.ux4g-tooltip\');\r\n if (tooltip) {\r\n tooltip.style.transform = \'\';\r\n if (tooltip.dataset.uxOriginalClass) {\r\n tooltip.className = tooltip.dataset.uxOriginalClass;\r\n delete tooltip.dataset.uxOriginalClass;\r\n }\r\n delete tooltip.dataset.uxAdjusted;\r\n }\r\n }\r\n });\r\n });\r\n\r\n // Assign to global\r\n ux4g.Tooltip = { getOrCreate(el) { return Floating.getOrCreate(el, "tooltip"); } };\r\n\r\n /**\r\n * Helper to ensure menus stay within viewport and break out of clipping bounds\r\n */\r\n ux4g.repositionMenu = function (container, menu) {\r\n if (!menu) return;\r\n\r\n // Temporarily reset to absolute to calculate natural layout sizes\r\n menu.style.position = "absolute";\r\n menu.style.width = ""; \r\n menu.style.top = "";\r\n menu.style.bottom = "";\r\n menu.style.left = "";\r\n menu.style.right = "";\r\n menu.style.margin = "0";\r\n\r\n // Measure natural absolute width BEFORE fixing position\r\n const absoluteWidth = menu.offsetWidth;
1\r\n\r\n // Switch to fixed to break out of overflow boundaries\r\n menu.style.position = "fixed";\r\n menu.style.width = absoluteWidth + "px"; // lock width\r\n\r\n const vh = window.innerHeight;\r\n const vw = window.innerWidth;\r\n const containerRect = container.getBoundingClientRect();\r\n \r\n let offsetStr = getComputedStyle(menu).getPropertyValue(\'--ux4g-dropdown-menu-offset-y\') ||\r\n getComputedStyle(menu).getPropertyValue(\'--ux4g-combobox-menu-offset-y\') || \'6px\';\r\n let offset = parseInt(offsetStr, 10) || 6;\r\n\r\n // A fixed descendant is positioned against a transformed ancestor rather\r\n // than the viewport. Keep nested menus local to floating overlays so the\r\n // viewport coordinates below are not applied a second time.\r\n const floatingAncestor = container.closest(\'.ux4g-popover, .ux4g-tooltip\');\r\n if (floatingAncestor) {\r\n menu.style.position = "absolute";\r\n menu.style.top = (containerRect.height + offset) + "px";\r\n menu.style.bottom = "auto";\r\n menu.style.left = "0";\r\n menu.style.right = "auto";\r\n\r\n let menuRect = menu.getBoundingClientRect();\r\n if (menuRect.bottom > vh && containerRect.top > menuRect.height + offset) {\r\n menu.style.top = "auto";\r\n menu.style.bottom = (containerRect.height + offset) + "px";\r\n }\r\n\r\n menuRect = menu.getBoundingClientRect();\r\n if (menuRect.right > vw) {\r\n menu.style.left = "auto";\r\n menu.style.right = "0";\r\n }\r\n return;\r\n }\r\n\r\n let top = containerRect.bottom + offset;\r\n let left = containerRect.left;\r\n\r\n menu.style.top = top + "px";\r\n menu.style.left = left + "px";\r\n\r\n const menuRect = menu.getBoundingClientRect();\r\n\r\n // Vertical Flip\r\n if (menuRect.bottom > vh && containerRect.top > menuRect.height) {\r\n menu.style.top = "auto";\r\n menu.style.bottom = (vh - containerRect.top + offset) + "px";\r\n } else {\r\n menu.style.top = top + "px";\r\n menu.style.bottom = "auto";\r\n }\r\n\r\n // Horizontal Shift\r\n const updatedRect = menu.getBoundingClientRect();\r\n if (updatedRect.right > vw) {\r\n menu.style.left = "auto";\r\n menu.style.right = (vw - containerRect.right) + "px";\r\n } else {\r\n menu.style.left = left + "px";\r\n menu.style.right = "auto";\r\n }\r\n };\r\n\r\n // Update open menus on resize/scroll\r\n const repositionAllMenus = () => {\r\n document.querySelectorAll(\'.ux4g-dropdown.is-open, .ux4g-combobox.is-open, .ux4g-breadcrumb-dropdown .show + .show\').forEach(el => {\r\n let container = el;\r\n let menu = el.querySelector(\'.ux4g-dropdown-menu, .ux4g-combobox-menu, .ux4g-breadcrumb-menu\');\r\n if (el.classList.contains(\'ux4g-breadcrumb-toggle\')) {\r\n container = el.parentElement;\r\n menu = container.querySelector(\'.ux4g-breadcrumb-menu\');\r\n }\r\n if (container && menu) ux4g.repositionMenu(container, menu);\r\n });\r\n };\r\n\r\n window.addEventListener(\'resize\', repositionAllMenus);\r\n window.addEventListener(\'scroll\', repositionAllMenus, true); // true for capture phase to catch all scrolls\r\n\r\n /**\r\n * Common Filter Core Logic\r\n */\r\n ux4g.filterCore = {\r\n /**\r\n * @param {string} text - The item text to check\r\n * @param {string} query - The search query\r\n * @param {string} mode - \'contains\', \'starts-with\', or \'starts-with-term\'\r\n */\r\n matches: function (text, query, mode) {\r\n if (!query) return true;\r\n const t = String(text).toLowerCase();\r\n const q = String(query).toLowerCase().trim();\r\n\r\n switch (mode) {\r\n case "starts-with":\r\n return t.startsWith(q);\r\n case "starts-with-term": {\r\n // Split query into terms; ALL terms must match the start of some word in the text\r\n const qTerms = q.split(/\\s+/).filter(Boolean);\r\n if (qTerms.length === 0) return true;\r\n const tWords = t.split(/\\s+/);\r\n return qTerms.every((qTerm) => tWords.some((w) => w.startsWith(qTerm)));\r\n }\r\n case "contains":\r\n default:\r\n return t.includes(q);\r\n }\r\n },\r\n\r\n /**\r\n * @param {string} originalText - The text to highlight\r\n * @param {string} query - The search query\r\n * @param {string} mode - \'contains\', \'starts-with\', or \'starts-with-term\'\r\n */\r\n highlight: function (originalText, query, mode) {\r\n if (!query) return originalText;\r\n const q = query.trim();\r\n const escaped = q.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\\\$&");\r\n\r\n let regex;\r\n switch (mode) {\r\n case "starts-with":\r\n regex = new RegExp(`^(${escaped})`, "i");\r\n break;\r\n case "starts-with-term": {\r\n // Highlight each query term individually at word boundaries\r\n const qTerms = q.split(/\\s+/).filter(
1Boolean);\r\n if (qTerms.length === 0) return originalText;\r\n const escapedTerms = qTerms.map((t) => t.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\\\$&"));\r\n return originalText.replace(new RegExp(`\\\\b(${escapedTerms.join("|")})`, "gi"), "<strong>$1</strong>");\r\n }\r\n case "contains":\r\n default:\r\n regex = new RegExp(`(${escaped})`, "i");\r\n break;\r\n }\r\n\r\n return originalText.replace(regex, "<strong>$1</strong>");\r\n }\r\n };\r\n\r\n})(window.ux4g);\r\n\r\n/* ========================================================= breadcrumb js ========================================================= */\r\nwindow.ux4gCustomInitList = window.ux4gCustomInitList || []; window.ux4gCustomInitList.push(() => {\r\n const dropdowns = document.querySelectorAll(".ux4g-breadcrumb-dropdown");\r\n if (!dropdowns.length) return;\r\n\r\n const closeDropdown = (dropdown) => {\r\n const toggle = dropdown.querySelector(".ux4g-breadcrumb-toggle");\r\n const menu = dropdown.querySelector(".ux4g-breadcrumb-menu");\r\n\r\n if (!toggle || !menu) return;\r\n\r\n toggle.classList.remove("show");\r\n menu.classList.remove("show");\r\n toggle.setAttribute("aria-expanded", "false");\r\n };\r\n\r\n const openDropdown = (dropdown) => {\r\n const toggle = dropdown.querySelector(".ux4g-breadcrumb-toggle");\r\n const menu = dropdown.querySelector(".ux4g-breadcrumb-menu");\r\n\r\n if (!toggle || !menu) return;\r\n\r\n toggle.classList.add("show");\r\n menu.classList.add("show");\r\n toggle.setAttribute("aria-expanded", "true");\r\n if (window.ux4g && window.ux4g.repositionMenu) {\r\n window.ux4g.repositionMenu(dropdown, menu);\r\n }\r\n };\r\n\r\n dropdowns.forEach((dropdown) => {\r\n const toggle = dropdown.querySelector(".ux4g-breadcrumb-toggle");\r\n const menu = dropdown.querySelector(".ux4g-breadcrumb-menu");\r\n\r\n if (!toggle || !menu) return;\r\n\r\n toggle.addEventListener("click", (e) => {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n\r\n const isOpen = menu.classList.contains("show");\r\n\r\n dropdowns.forEach((d) => closeDropdown(d));\r\n\r\n if (!isOpen) openDropdown(dropdown);\r\n });\r\n\r\n menu.addEventListener("click", () => closeDropdown(dropdown));\r\n });\r\n\r\n document.addEventListener("click", () => {\r\n dropdowns.forEach((d) => closeDropdown(d));\r\n });\r\n\r\n document.addEventListener("keydown", (e) => {\r\n if (e.key === "Escape") dropdowns.forEach((d) => closeDropdown(d));\r\n });\r\n});\r\n\r\n\r\n\r\n/* ========================================================= list js ========================================================= */\r\n\r\n// Draggable js\r\n\r\nwindow.ux4gCustomInitList = window.ux4gCustomInitList || []; window.ux4gCustomInitList.push(() => {\r\n\r\n const list = document.querySelector(".ux4g-list-draggable");\r\n let draggedItem = null;\r\n\r\n // enable drag only from icon\r\n document.querySelectorAll(".ux4g-icon-outlined, .ux4g-icon").forEach(icon => {\r\n\r\n icon.addEventListener("dragstart", (e) => {\r\n draggedItem = icon.closest(".ux4g-list-item");\r\n draggedItem.classList.add("dragging");\r\n\r\n e.dataTransfer.effectAllowed = "move";\r\n });\r\n\r\n });\r\n\r\n document.querySelectorAll(".ux4g-list-item").forEach(item => {\r\n\r\n item.addEventListener("dragover", (e) => {\r\n e.preventDefault();\r\n const dragging = document.querySelector(".dragging");\r\n\r\n if (dragging === item) return;\r\n\r\n const rect = item.getBoundingClientRect();\r\n const offset = e.clientY - rect.top;\r\n\r\n if (offset > rect.height / 2) {\r\n item.after(dragging);\r\n } else {\r\n item.before(dragging);\r\n }\r\n });\r\n\r\n item.addEventListener("dragend", () => {\r\n draggedItem.classList.remove("dragging");\r\n draggedItem = null;\r\n });\r\n\r\n });\r\n\r\n});\r\n\r\n/* ========================================================= Modal js ========================================================= */\r\n\r\n// Single delegated click handler â works with any framework, no re-init needed\r\ndocument.addEventListener(\'click\', (e) => {\r\n const openBtn = e.target.closest(\'[data-modal-target], [data-target], [data-bs-target], [data-ux-target], [ux4g-target], [data-bs-toggle="modal"], [data-ux-toggle="modal"], .open-modal-btn, .modal
1-backdrop-btn-trigger\');\r\n if (openBtn) {\r\n let targetSelector = openBtn.getAttribute(\'data-modal-target\') ||\r\n openBtn.getAttribute(\'data-target\') ||\r\n openBtn.getAttribute(\'data-bs-target\') ||\r\n openBtn.getAttribute(\'data-ux-target\') ||\r\n openBtn.getAttribute(\'ux4g-target\') ||\r\n openBtn.getAttribute(\'href\');\r\n if (targetSelector && targetSelector.indexOf(\'#\') !== 0 && targetSelector.indexOf(\'.\') !== 0) {\r\n targetSelector = \'#\' + targetSelector;\r\n }\r\n const targetModal = targetSelector ? document.querySelector(targetSelector) : null;\r\n if (targetModal) {\r\n targetModal.classList.add(\'is-open\');\r\n document.body.style.overflow = \'hidden\';\r\n }\r\n return;\r\n }\r\n\r\n const closeBtn = e.target.closest(\'[data-close-modal], [data-bs-dismiss="modal"], [data-ux-dismiss="modal"], [data-dismiss="modal"], [ux4g-dismiss="modal"], .close-modal-btn, .ux4g-modal-close, .ux4g-modal-close-two\');\r\n if (closeBtn) {\r\n // Traverse parent tree and remove is-open from all parent backdrop or modal wrappers\r\n let parent = closeBtn.parentElement;
1\r\n let closedAny = false;\r\n while (parent) {\r\n if (parent.classList && (parent.classList.contains(\'ux4g-modal-backdrop\') || parent.classList.contains(\'ux4g-modal\'))) {\r\n if (parent.classList.contains(\'is-open\')) {\r\n parent.classList.remove(\'is-open\');\r\n closedAny = true;\r\n }\r\n }\r\n parent = parent.parentElement;\r\n }\r\n // Fallback if is-open was not found on parent tree\r\n if (!closedAny) {\r\n const modal = closeBtn.closest(\'.ux4g-modal-backdrop, .ux4g-modal\');\r\n if (modal) modal.classList.remove(\'is-open\');\r\n }\r\n if (document.querySelectorAll(\'.ux4g-modal-backdrop.is-open, .ux4g-modal.is-open\').length === 0) {\r\n document.body.style.overflow = \'\';\r\n }\r\n return;\r\n }\r\n\r\n const openModal = e.target.closest(\'.ux4g-modal-backdrop.is-open, .ux4g-modal.is-open\');\r\n if (openModal && !e.target.closest(\'.ux4g-modal-box\')) {\r\n const modals = document.querySelectorAll(\'.ux4g-modal-backdrop.is-open, .ux4g-modal.is-open\');\r\n modals.forEach(m => m.classList.remove(\'is-open\'));\r\n if (document.querySelectorAll(\'.ux4g-modal-backdrop.is-open, .ux4g-modal.is-open\').length === 0) {\r\n document.body.style.overflow = \'\';\r\n }\r\n }\r\n});\r\n\r\n// Escape key to close all open modals â registered once, never duplicates\r\ndocument.addEventListener(\'keydown\', (e) => {\r\n if (e.key === \'Escape\') {\r\n const openModals = document.querySelectorAll(\'.ux4g-modal-backdrop.is-open, .ux4g-modal.is-open\');\r\n openModals.forEach(modal => modal.classList.remove(\'is-open\'));\r\n document.body.style.overflow = \'\';\r\n }\r\n});\r\n\r\n/* ========================================================= clear seach btn js ========================================================= */\r\n\r\n// if (!input || !clearBtn) return;\r\n\r\n// input.addEventListener("input", () => {\r\n// clearBtn.classList.toggle("ux4g-show-clear", input.value.trim() !== "");\r\n// });\r\n\r\n// clearBtn.addEventListener("click", () => {\r\n// input.value = "";\r\n// clearBtn.classList.remove("ux4g-show-clear");\r\n// input.focus();\r\n// });\r\n// });\r\n// });\r\n\r\nwindow.ux4gCustomInitList = window.ux4gCustomInitList || []; window.ux4gCustomInitList.push(() => {\r\n document.querySelectorAll(".ux4g-search-container").forEach((searchWrap) => {\r\n const input = searchWrap.querySelector(".ux4g-search-input");\r\n const clearBtn = searchWrap.querySelector(".ux4g-search-clear");\r\n const list = searchWrap.querySelector(".ux4g-search-list");\r\n const filterMode = searchWrap.getAttribute("ux4g-search-filter") || "contains";\r\n\r\n if (!input) return;\r\n\r\n const toggleSearchState = () => {\r\n searchWrap.classList.toggle("ux4g-has-value", input.value.trim() !== "");\r\n handleSearchFilter();\r\n };\r\n\r\n const handleSearchFilter = () => {\r\n if (!list) return;\r\n\r\n // Only show list if there\'s a value, the component is active (focused),\r\n // or it\'s forced by \'show-empty\' class\r\n const isActive = searchWrap.classList.contains("ux4g-is-active");\r\n const hasValue = input.value.trim() !== "";\r\n const showEmpty = searchWrap.classList.contains("ux4g-search-show-empty");\r\n\r\n if (isActive || hasValue || showEmpty) {\r\n list.style.display = "block";\r\n } else {\r\n list.style.display = "none";\r\n }\r\n\r\n const value = input.value;\r\n const options = list.querySelectorAll(".ux4g-list-item");\r\n const isSearchable = input.id && input.id.trim() !== "";\r\n let visibleCount = 0;\r\n\r\n options.forEach(option => {\r\n // Store original text\r\n const labelNode = option.querySelector(".ux4g-list-item-start");\r\n if (labelNode) {\r\n labelNode.style.gap = "0"; // Remove gap that causes space between highlight and text\r\n }\r\n const originalText = option.dataset.originalText || (labelNode ? labelNode.textContent.trim() : option.textContent.trim());\r\n if (!option.dataset.originalText) option.dataset.originalText = originalText;\r\n\r\n const isMatch = isSearchable ? window.ux4g.filterCore.matches(originalText, value, filterMode) : true;\r\n option.style.display = isMatch ? "" : "none";\r\n\r\n if (labelNode) {\r\n const trimmedQuery = value.trim();\r\n if (isSearchable && trimmedQuery && isMatch) {\r\n labelNode.innerHTML = window.ux4g.filterCore.highlight(originalText, trimmedQuery, filterMode);\r\n } else {\r\n labelNode.textContent = originalText;\r\n }\r\n }\r\n if (isMatch) visibleCount++;\r\n });\r\n\r\n // Toggle No Results Message\r\n let noResults = list.querySelector(".ux4g-search-no-results");\r\n if (visibleCount === 0 && value.trim() !== "") {\r\n if (!noResults) {\r\n noResults = document.createElement("li");\r\n noResults.className = "ux4g-search-no-results ux4g-p-s ux4g-text-center ux4g-text-muted";\r\n noResults.style.listStyle = "none";\r\n noResults.textContent = "No results found";\r\n list.appendChild(noResults);\r\n }\r\n noResults.style.display = "";\r\n } else if (noResults) {\r\n noResults.style.display = "none";\r\n }\r\n\r\n // Optionally show list only when there\'s a value or interaction\r\n list.style.display = (value.trim() === "" && visibleCount === 0 && !searchWra
1p.classList.contains("ux4g-search-show-empty")) ? "none" : "";\r\n\r\n // If list is hidden, don\'t show no results either\r\n if (list.style.display === "none" && noResults) noResults.style.display = "none";\r\n };\r\n\r\n input.addEventListener("input", toggleSearchState);\r\n\r\n if (clearBtn) {\r\n clearBtn.addEventListener("click", () => {\r\n input.value = "";\r\n toggleSearchState();\r\n input.focus();\r\n });\r\n }\r\n\r\n input.addEventListener("focus", () => {\r\n searchWrap.classList.add("ux4g-is-active");\r\n handleSearchFilter();\r\n });\r\n\r\n input.addEventListener("blur", (e) => {\r\n // Small timeout to allow potential clicks on the list item itself\r\n setTimeout(() => {\r\n if (!searchWrap.contains(document.activeElement)) {\r\n searchWrap.classList.remove("ux4g-is-active");\r\n handleSearchFilter();\r\n }\r\n }, 200);\r\n });\r\n\r\n // Close when clicking outside\r\n document.addEventListener("mousedown", (e) =>
1 {\r\n if (!searchWrap.contains(e.target)) {\r\n searchWrap.classList.remove("ux4g-is-active");\r\n handleSearchFilter();\r\n }\r\n });\r\n\r\n // Initial state\r\n toggleSearchState();\r\n });\r\n});\r\n\r\n// clear input text\r\nwindow.ux4gCustomInitList = window.ux4gCustomInitList || []; window.ux4gCustomInitList.push(() => {\r\n const toggle = (i) => i.closest(".ux4g-input-container")?.classList.toggle("ux4g-has-value", i.value.length > 0);\r\n\r\n document.addEventListener("input", (e) => e.target.tagName === "INPUT" && toggle(e.target));\r\n\r\n document.addEventListener("click", (e) => {\r\n const btn = e.target.closest(\'[aria-label="Clear input"], .ux4g-input-clear\');\r\n if (btn) {\r\n const input = btn.closest(".ux4g-input-container")?.querySelector("input");\r\n if (input && !input.disabled && !input.readOnly) {\r\n input.value = "";\r\n toggle(input);\r\n input.dispatchEvent(new Event("input", { bubbles: true }));\r\n input.focus();\r\n }\r\n }\r\n });\r\n\r\n document.querySelectorAll(".ux4g-input-container input").forEach(toggle);\r\n});\r\n\r\n/* ========================================================= textarea counter js ========================================================= */\r\nwindow.ux4gCustomInitList = window.ux4gCustomInitList || []; window.ux4gCustomInitList.push(() => {\r\n const updateCounter = (textarea) => {\r\n const wrapper = textarea.closest(".ux4g-textarea");\r\n if (!wrapper) return;\r\n\r\n const counter = wrapper.querySelector(".ux4g-textarea-counter");\r\n if (!counter) return;\r\n\r\n const maxLength = textarea.getAttribute("maxlength") || 0;\r\n const currentLength = textarea.value.length;\r\n\r\n // Update text content with standard "0 / 200" format \r\n // Uses textContent to avoid XSS vectors or accidental HTML\r\n counter.textContent = `${currentLength} / ${maxLength}`;\r\n };\r\n\r\n // Add listener for all input events globally (event delegation)\r\n document.addEventListener("input", (e) => {\r\n if (e.target && e.target.matches && e.target.matches(".ux4g-textarea-input")) {\r\n updateCounter(e.target);\r\n }\r\n });\r\n\r\n // Initialize all textareas with counters on page load\r\n document.querySelectorAll(".ux4g-textarea-input").forEach((textarea) => {\r\n updateCounter(textarea);\r\n });\r\n});\r\n\r\n\r\n\r\n// ux4g drawer js\r\n\r\n/* ---------------- */\r\n/* CLOSE DRAWER FUNCTION */\r\n/* ---------------- */\r\n\r\nfunction closeDrawer() {\r\n const openDrawer = document.querySelector(".ux4g-drawer.ux4g-drawer-open");\r\n const openOverlay = document.querySelector(".ux4g-drawer-overlay.ux4g-drawer-open");\r\n\r\n if (!openDrawer || !openOverlay) return;\r\n\r\n openDrawer.classList.remove("ux4g-drawer-open");\r\n openOverlay.classList.remove("ux4g-drawer-open");\r\n\r\n document.body.classList.remove("ux4g-drawer-lock");\r\n}\r\n\r\n/* ---------------- */\r\n/* DELEGATED CLICK HANDLERS */\r\n/* ---------------- */\r\n\r\ndocument.addEventListener("click", (e) => {\r\n // OPEN DRAWER\r\n const openTrigger = e.target.closest("[data-drawer]");\r\n if (openTrigger) {\r\n const drawerId = openTrigger.getAttribute("data-drawer");\r\n const drawer = document.getElementById(drawerId);\r\n \r\n if (drawer) {\r\n const overlay = drawer.closest(".ux4g-drawer-overlay");\r\n\r\n document.querySelectorAll(".ux4g-drawer-open")\r\n .forEach(el => el.classList.remove("ux4g-drawer-open"));\r\n\r\n if (overlay) overlay.classList.add("ux4g-drawer-open");\r\n drawer.classList.add("ux4g-drawer-open");\r\n\r\n document.body.classList.add("ux4g-drawer-lock");\r\n }\r\n return;\r\n }\r\n\r\n // CLOSE VIA BUTTONS\r\n const closeTrigger = e.target.closest("[data-drawer-close]");\r\n if (closeTrigger) {\r\n closeDrawer();\r\n return;\r\n }\r\n\r\n /* CLOSE VIA OVERLAY CLICK */\r\n const overlay = e.target.closest(".ux4g-drawer-overlay");\r\n if (overlay && !e.target.closest(".ux4g-drawer")) {\r\n closeDrawer();\r\n }\r\n});
1\r\n\r\n/* ---------------- */\r\n/* CLOSE VIA ESC KEY */\r\n/* ---------------- */\r\n\r\ndocument.addEventListener("keydown", (e) => {\r\n if (e.key === "Escape") {\r\n closeDrawer();\r\n }\r\n});\r\n\r\n\r\n// ux4g table checkbox selection\r\n\r\nconst getTableRowCheckboxes = (table) => Array.from(\r\n table.querySelectorAll(\'tbody .ux4g-table-selection-col .ux4g-checkbox-input[type="checkbox"]\')\r\n).filter(input => !input.disabled);\r\n\r\nconst syncTableSelectionState = (table) => {\r\n if (!table) return;\r\n const rowCheckboxes = getTableRowCheckboxes(table);\r\n const selectAll = table.querySelector(\'thead .ux4g-checkbox-input[type="checkbox"]\');\r\n\r\n rowCheckboxes.forEach(input => {\r\n const row = input.closest(\'tr\');\r\n if (row) row.classList.toggle(\'ux4g-is-selected\', input.checked);\r\n });\r\n\r\n if (!selectAll) return;\r\n const selectedCount = rowCheckboxes.filter(input => input.checked).length;\r\n selectAll.checked = rowCheckboxes.length > 0 && selectedCount === rowCheckboxes.length;\r\n selectAll.indeterminate = selectedCount > 0 && selectedCount < rowCheckboxes.length;\r\n};\r\n\r\nconst initTableSelection = (root = document) => {\r\n root.querySelectorAll(\'.ux4g-table\').forEach(syncTableSelectionState);\r\n};\r\n\r\ndocument.addEventListener(\'change\', (event) => {\r\n const checkbox = event.target.closest?.(\'.ux4g-table .ux4g-checkbox-input[type="checkbox"]\');\r\n if (!checkbox) return;\r\n\r\n const table = checkbox.closest(\'.ux4g-table\');\r\n if (!table) return;\r\n\r\n if (checkbox.closest(\'thead\')) {\r\n getTableRowCheckboxes(table).forEach(input => {\r\n input.checked = checkbox.checked;\r\n });\r\n }\r\n\r\n syncTableSelectionState(table);\r\n window.queueMicrotask(() => syncTableSelectionState(table));\r\n window.setTimeout(() => syncTableSelectionState(table), 0);\r\n});\r\n\r\nwindow.ux4gTableSelectionInit = initTableSelection;\r\nwindow.ux4gCustomInitList = window.ux4gCustomInitList || [];\r\nwindow.ux4gCustomInitList.push(() => initTableSelection());\r\n\r\n/* ========================================================= dropdown js ========================================================= */\r\n\r\n/* ========================================================= dropdown js ========================================================= */\r\n\r\nconst ux4gDropdownInit = (root = document) => {\r\n const dropdowns = Array.from(root.querySelectorAll(".ux4g-dropdown"))\r\n .filter((dropdown) => dropdown.dataset.ux4gDropdownInitialized !== "true");\r\n if (!dropdowns.length) return;\r\n\r\n const closeDropdown = (dropdown) => {\r\n dropdown.classList.remove("is-open");\r\n const control = dropdown.querySelector(".ux4g-dropdown-control");\r\n if (control) control.setAttribute("aria-expanded", "false");\r\n };\r\n\r\n const openDropdown = (dropdown) => {\r\n dropdowns.forEach((item) => {\r\n if (item !== dropdown) closeDropdown(item);\r\n });\r\n dropdown.classList.add("is-open");\r\n const control = dropdown.querySelector(".ux4g-dropdown-control");\r\n if (control) control.setAttribute("aria-expanded", "true");\r\n\r\n const menu = dropdown.querySelector(".ux4g-dropdown-menu");\r\n if (menu && window.ux4g && window.ux4g.repositionMenu) {\r\n window.ux4g.repositionMenu(dropdown, menu);\r\n }\r\n };\r\n\r\n const setControlText = (dropdown, value) => {\r\n const hasValue = Boolean(value && String(value).trim());\r\n dropdown.classList.toggle("has-value", hasValue);\r\n\r\n const searchInput = dropdown.querySelector("[ux4g-dropdown-search]");\r\n if (searchInput) {\r\n if (dropdown.classList.contains("ux4g-dropdown-single")) {\r\n searchInput.value = value || "";\r\n } else {\r\n searchInput.value = "";\r\n }\r\n // Trigger filter logic to stay in sync\r\n searchInput.dispatchEvent(new Event("input", { bubbles: true }));\r\n return;\r\n }\r\n\r\n const valueNode = dropdown.querySelector("[ux4g-dropdown-value]");\r\n if (!valueNode) return;\r\n const placeholder = valueNode.getAttribute("data-placeholder") || "Please select..";\r\n valueNode.textContent = hasValue ? value : placeholder;\r\n valueNode.classList.toggle("is-placeholder", !hasValue);\r\n };\r\n\r\n const getInputLabel = (input) => {\r\n if (!input) return "";\r\n if (input.dataset.label) return input.dataset.label;\r\n if (input.value) return input.value;\r\n const option = input.closest(".ux4g-dropdown-option");\r\n const text = option?.querySelector(".ux4g-checkbox-label, .ux4g-dropdown-option-title");\r\n return text?.textContent?.trim() || "";\r\n };\r\n\r\n const getChoiceLabel = (choice) => {\r\n if (!choice) return "";\r\n return (choice.getAttribute("ux4g-dropdown-choice") || choice.textContent || "").trim();\r\n };\r\n\r\n const applySingleSelection = (dropdown, value) => {\r\n const chipsNode = dropdown.querySelector("[ux4g-dropdown-chips]");\r\n dropdown.classList.remove("has-selection");\r\n setControlText(dropdown, value);\r\n if (!chipsNode) return;\r\n chipsNode.innerHTML = "";\r\n };\r\n\r\n const setSingleSelectedOption = (dropdown, selectedOption) => {\r\n const options = dropdown.querySelectorAll(".ux4g-dropdown-single-option");\r\n options.forEach((option) => {\r\n const isSelected = option === selectedOption;\r\n if (isSelected) {\r\n option.classList.add("is-selected", "active");\r\n option.setAttribute("aria-selected", "true");\r\n } else {\r\n option.classList.remove("is-selected", "active");\r\n option.setAttribute("aria-selected", "false");\r\n }\r\n const listItem = option.closest(".ux4g-list-item");\r\n if (listItem) listItem.setAttribute("aria-selected", String(isSelected));\r\n });\r\n };\r\n\r\n const renderMultiSelection = (dropdown) => {\r\n const chipsNode = dropdown.querySelector("[ux4g-dropdown-chips]");\r\n const checkedInputs = Array.from(\r\n dropdown.querySelectorAll(".ux4g-dropdown-option-input:checked")\r\n );\r\n const options = Array.from(dropdown.querySelectorAll(".ux4g-dropdown-option"));\r\n\r\n const hasSelection = checkedInputs.length > 0;\r\n dropdown.classList.toggle("has-selection", hasSelection && Boolean(chipsNode));\r\n options.forEach((option) => {\r\n const input = option.querySelector(".ux4g-dropdown-option-input");\r\n const isSelected = Boolean(input?.checked);\r\n option.classList.toggle("is-selected", isSelected);\r\n option.classList.toggle("active", isSelected);\r\n option.setAttribute("aria-selected", String(isSelected));\r\n });\r\n\r\n setControlText(dropdown, "");\r\n if (!chipsNode) return;\r\n chipsNode.innerHTML = "";\r\n if (!chipsNode || !hasSelection) return;
1\r\n\r\n let chipSizeClass = "ux4g-input-chip-sm";\r\n if (dropdown.classList.contains("ux4g-dropdown-sm")) chipSizeClass = "ux4g-input-chip-xs";\r\n if (dropdown.classList.contains("ux4g-dropdown-lg")) chipSizeClass = "ux4g-input-chip";\r\n\r\n checkedInputs.forEach((input) => {\r\n const label = getInputLabel(input);\r\n if (!label) return;\r\n\r\n const chip = document.createElement("span");\r\n chip.className = `${chipSizeClass} ux4g-dropdown-chip`;\r\n chip.setAttribute("role", "button");\r\n chip.setAttribute("tabindex", "0");\r\n chip.setAttribute("aria-label", `Remove ${label}`);\r\n chip.setAttribute("data-input-id", input.id);\r\n chip.innerHTML = `<span class="ux4g-icon-outlined" aria-hidden="true">token</span><span>${label}</span><span class="ux4g-icon-outlined" aria-hidden="true">close</span>`;\r\n\r\n const closeBtn = chip.querySelector(".ux4g-icon-outlined:last-child");\r\n\r\n const clearSelection = (event) => {\r\n event.preventDefault();\r\n event.stopPropagation();\r\n input.checked = false;\r\n renderMultiSelection(dropdown);\r\n };\r\n\r\n chip.addEventListener("mousedown", (event) => {\r\n event.preventDefault();\r\n event.stopPropagation();\r\n });\r\n\r\n if (closeBtn) {\r\n closeBtn.addEventListener("click", clearSelection);\r\n }\r\n\r\n chip.addEventListener("keydown", (event) => {\r\n if (event.key === "Enter" || event.key === " ") {\r\n clearSelection(event);\r\n }\r\n });\r\n\r\n chipsNode.appendChild(chip);\r\n });\r\n };\r\n\r\n dropdowns.forEach((dropdown, index) => {\r\n const control = dropdown.querySelector(".ux4g-dropdown-control");\r\n const menu = dropdown.querySelector(".ux4g-dropdown-menu");\r\n if (!control || !menu) return;\r\n dropdown.dataset.ux4gDropdownInitialized = "true";\r\n\r\n if (!control.id) {\r\n control.id = `ux4g-dropdown-control-${index + 1}`;\r\n }\r\n\r\n const popupRole = menu.getAttribute("role") === "menu" ? "menu" : "listbox";\r\n control.setAttribute("aria-haspopup", popupRole);\r\n control.setAttribute("aria-expanded", "false");\r\n menu.setAttribute("aria-labelledby", control.id);\r\n\r\n const isSingle = dropdown.classList.contains("ux4g-dropdown-single");\r\n const isMulti = dropdown.classList.contains("ux4g-dropdown-multi");\r\n\r\n const dropdownInputs = Array.from(menu.querySelectorAll(".ux4g-dropdown-option-input"));\r\n dropdownInputs.forEach((input, inputIndex) => {\r\n if (!input.id) {\r\n input.id = `${control.id}-option-${inputIndex + 1}`;\r\n }\r\n });\r\n\r\n control.addEventListener("click", (event) => {\r\n // Prevent interactions if disabled\r\n if (control.disabled || control.getAttribute("aria-disabled") === "true") {\r\n event.preventDefault();\r\n event.stopPropagation();\r\n return;\r\n }\r\n\r\n if (event.target.closest("[ux4g-dropdown-search]")) {\r\n if (!dropdown.classList.contains("is-open")) {\r\n openDropdown(dropdown);\r\n }\r\n return;\r\n }\r\n event.preventDefault();\r\n event.stopPropagation();\r\n if (dropdown.classList.contains("is-open")) closeDropdown(dropdown);\r\n else openDropdown(dropdown);\r\n });\r\n\r\n menu.addEventListener("click", (event) => {\r\n const choice = event.target.closest("[ux4g-dropdown-choice]");\r\n if (!choice) return;\r\n\r\n // Stop propagation to prevent global list JS from re-adding \'active\' class\r\n event.stopPropagation();\r\n\r\n const value = getChoiceLabel(choice);\r\n if (!value) return;\r\n\r\n if (isSingle) {\r\n setSingleSelectedOption(dropdown, choice);\r\n applySingleSelection(dropdown, value);\r\n } else if (!isMulti) {\r\n setSingleSelectedOption(dropdown, choice); // Also set option for regular single selection\r\n applySingleSelection(dropdown, value);\r\n }\r\n\r\n closeDropdown(dropdown);\r\n });\r\n\r\n menu.addEventListener("change", (event) => {\r\n const input = event.target.closest(".ux4g-dropdown-option-input");\r\n if (!input) return;\r\n\r\n if (isMulti) {\r\n renderMultiSelection(dropdown);\r\n return;\r\n }\r\n\r\n if (input.checked && !isMulti) {\r\n menu.querySelectorAll(".ux4g-dropdown-option-input").forEach((item) => {\r\n if (item !== input) item.checked = false;\r\n });\r\n }\r\n\r\n applySingleSelection(dropdown, input.checked ? getInputLabel(input) : "");\r\n closeDropdown(dropdown);\r\n });\r\n\r\n menu.addEventListener("click", (event) =>
1 {\r\n const row = event.target.closest(".ux4g-list-item-row");\r\n if (!row) return;\r\n\r\n const isActionDropdown =\r\n dropdown.classList.contains("ux4g-dropdown-button") ||\r\n dropdown.classList.contains("ux4g-dropdown-overflow");\r\n\r\n if (isActionDropdown && !row.hasAttribute("ux4g-dropdown-choice")) {\r\n closeDropdown(dropdown);\r\n }\r\n });\r\n\r\n if (isMulti) {\r\n const searchInput = dropdown.querySelector("[ux4g-dropdown-search]");\r\n if (searchInput) {\r\n const icon = searchInput.previousElementSibling;\r\n if (icon && icon.classList.contains("ux4g-icon-outlined") && !searchInput.closest(".ux4g-dropdown-input-wrap")) {\r\n const wrapper = document.createElement("span");\r\n wrapper.className = "ux4g-dropdown-input-wrap";\r\n icon.parentNode.insertBefore(wrapper, icon);\r\n wrapper.appendChild(icon);\r\n wrapper.appendChild(searchInput);\r\n }\r\n }\r\n renderMultiSelection(dropdown);\r\n return;\r\n }\r\n\r\n const preselectedSingle = menu.querySelector(\r\n ".ux4g-dropdown-single-option.is-selected, .ux4g-dropdown-single-option[aria-selected=\'true\']"\r\n );\r\n if (preselectedSingle) {\r\n setSingleSelectedOption(dropdown, preselectedSingle);\r\n applySingleSelection(dropdown, getChoiceLabel(preselectedSingle));\r\n return;\r\n }\r\n\r\n const preselectedChoice = menu.querySelector("[ux4g-dropdown-choice][aria-selected=\'true\']");\r\n if (preselectedChoice) {\r\n applySingleSelection(dropdown, getChoiceLabel(preselectedChoice));\r\n return;\r\n }\r\n\r\n const preselectedInput = menu.querySelector(".ux4g-dropdown-option-input:checked");\r\n applySingleSelection(dropdown, preselectedInput ? getInputLabel(preselectedInput) : "");\r\n });\r\n\r\n document.addEventListener("click", (event) => {\r\n // Dynamically query open dropdowns to support frameworks that recreate DOM nodes\r\n const openDropdowns = document.querySelectorAll(".ux4g-dropdown.is-open");\r\n openDropdowns.forEach((dropdown) => {\r\n if (!dropdown.contains(event.target)) {\r\n dropdown.classList.remove("is-open");\r\n const control = dropdown.querySelector(".ux4g-dropdown-control");\r\n if (control) control.setAttribute("aria-expanded", "false");\r\n }\r\n });\r\n });\r\n\r\n document.addEventListener("keydown", (event) => {\r\n if (event.key === "Escape") {\r\n const openDropdowns = document.querySelectorAll(".ux4g-dropdown.is-open");\r\n openDropdowns.forEach((dropdown) => {\r\n dropdown.classList.remove("is-open");\r\n const control = dropdown.querySelector(".ux4g-dropdown-control");\r\n if (control) control.setAttribute("aria-expanded", "false");\r\n });\r\n }\r\n });\r\n};\r\n\r\nwindow.ux4gDropdownInit = ux4gDropdownInit;\r\nwindow.ux4gCustomInitList = window.ux4gCustomInitList || [];\r\nwindow.ux4gCustomInitList.push(() => ux4gDropdownInit(document));\r\n\r\n\r\n/* =========================================\r\nDROPDOWN SEARCH\r\n========================================= */\r\n\r\nwindow.ux4gCustomInitList = window.ux4gCustomInitList || []; window.ux4gCustomInitList.push(() => {\r\n document.querySelectorAll("[ux4g-dropdown-search]").forEach((input) => {\r\n\r\n const dropdown = input.closest(".ux4g-dropdown");\r\n const menu = dropdown.querySelector(".ux4g-dropdown-menu");\r\n\r\n input.addEventListener("focus", () => {\r\n dropdown.classList.add("is-open");\r\n if (window.ux4g && window.ux4g.repositionMenu) {\r\n window.ux4g.repositionMenu(dropdown, menu);\r\n }\r\n });\r\n\r\n input.addEventListener("input", () => {\r\n const value = input.value;\r\n const filterMode = dropdown.getAttribute("ux4g-dropdown-filter") || "contains";\r\n\r\n dropdown.classList.add("is-open");\r\n if (window.ux4g && window.ux4g.repositionMenu) {\r\n window.ux4g.repositionMenu(dropdown, menu);\r\n }\r\n\r\n const options = dropdown.querySelectorAll(\r\n ".ux4g-dropdown-single-option, .ux4g-dropdown-option"\r\n );\r\n\r\n options.forEach(option => {\r\n const originalText = option.dataset.originalText || option.textContent.trim();\r\n if (!option.dataset.originalText) option.dataset.originalText = originalText;\r\n\r\n const isMatch = window.ux4g.filterCore.matches(originalText, value, filterMode);\r\n option.style.display = isMatch ? "" : "none";\r\n\r\n // Optional: Add highlighting for Dropdown Search too\r\n const labelNode = option.querySelector(".ux4g-checkbox-label, .ux4g-dropdown-option-title, .ux4g-list-item-start");\r\n if (labelNode) {\r\n if (value && isMatch) {\r\n labelNode.innerHTML = window.ux4g.filterCore.highlight(originalText, value, filterMode);\r\n } else {\r\n labelNode.textContent = originalText;\r\n }\r\n }\r\n });\r\n });\r\n\r\n // Sync disabled state\r\n const control = dropdown.querySelector(".ux4g-dropdown-control");\r\n if (control && (control.disabled || control.getAttribute("aria-disabled") === "true")) {\r\n input.disabled = true;\r\n }\r\n });\r\n});\r\n\r\n/* ========================================================= combobox js ========================================================= */\r\n\r\nwindow.ux4gCustomInitList = window.ux4gCustomInitList || []; window.ux4gCustomInitList.push(() => {\r\n const comboboxs = Array.from(document.querySelectorAll(".ux4g-combobox"));\r\n if (!comboboxs.length) return;\r\n\r\n const closeCombobox = (combobox) => {\r\n combobox.classList.remove("is-open");\r\n const control = combobox.querySelector(".ux4g-combobox-control");\r\n if (control) control.setAttribute("aria-expanded", "false");\r\n };\r\n\r\n const openCombobox = (combobox) => {\r\n comboboxs.forEach((item) => {\r\n if (item !== combobox) closeCombobox(item);\r\n });\r\n combobox.classList.add("is-open");\r\n const control = combobox.querySelector(".ux4g-combobox-control");\r\n if (control) control.setAttribute("aria-expanded", "true");\r\n\r\n const menu = combobox.querySelector(".ux4g-combobox-menu");\r\n if (menu && window.ux4g && window.ux4g.repositionMenu) {\r\n window.ux4g.repositionMenu(combobox, menu);\r\n }\r\n };\r\n\r\n const setControlText = (combobox, value) => {\r\n const searchInput = combobox.querySelector("[ux4g-combobox-search]");\r\n if (searchInput) {\r\n if (combobox.classList.contains("ux4g-combobox-single")) {\r\n searchInput.value = value || "";\r\n } else {\r\n searchInput.value = "";\r\n }\r\n return;\r\n }\r\n\r\n const valueNode = combobox.querySelector("[ux4g-combobox-value]");\r\n const hasValue = Boolean(value && String(value).trim());\r\n if (!valueNode) return;\r\n const placeholder = valueNode.getAttribute("data-placeholder") || "Please select..";\r\n valueNode.textContent = hasValue ? value : placeholder;\r\n valueNode.classList.toggle("is-placeholder", !hasValue);\r\n };\r\n\r\n const getInputLabel = (input) => {\r\n if (!input) return "";\r\n if (input.dataset.label) return input.dataset.label;\r\n if (input.value) return input.value;\r\n const option = input.closest(".ux4g-combobox-option");\r\n const text = option?.querySelector(".ux4g-checkbox-label, .ux4g-combobox-option-title");\r\n return text?.textContent?.trim() || "";\r\n };\r\n\r\n const getChoiceLabel = (choice) => {\r\n if (!choice) return "";\r\n return (choice.getAttribute("ux4g-combobox-choice") || choice.textContent || "").trim();\r\n };\r\n\r\n const applySingleSelection = (combobox, value) => {\r\n const chipsNode = combobox.querySelector("[ux4g-combobox-chips]");\r\n combobox.classList.remove("has-selection");\r\n setControlText(combobox, value);\r\n if (!chipsNode) return;\r\n chipsNode.innerHTML = "";\r\n };\r\n\r\n const setSingleSelectedOption = (combobox, selectedOption) => {\r\n const options = combobox.querySelectorAll(".ux4g-combobox-single-option");\r\n options.forEach((option) => {\r\n const isSelected = option === selectedOption;\r\n option.classList.toggle("is-selected", isSelected);\r\n option.classList.toggle("active", isSelected);\r\n option.setAttribute("aria-selected", String(isSelected));\r\n const listItem = option.closest(".ux4g-list-item");\r\n if (listItem) listItem.setAttribute("aria-selected", String(isSelected));\r\n });\r\n };\r\n\r\n const renderMultiSelection = (combobox) => {\r\n const chipsNode = combobox.querySelector("[ux4g-combobox-chips]");\r\n const checkedInputs = Array.from(\r\n combobox.querySelectorAll(".ux4g-combobox-option-input:checked")\r\n );\r\n const options = Array.from(combobox.querySelectorAll(".ux4g-combobox-option"));\r\n\r\n const hasSelection = checkedInputs.length > 0;\r\n combobox.classList.toggle("has-selection", hasSelection && Boolean(chipsNode));\r\n options.forEach((option) => {\r\n const input = option.querySelector(".ux4g-combobox-option-input");\r\n const isSelected = Boolean(input?.checked);\r\n option.classList.toggle("is-selected", isSelected);\r\n option.classList.toggle("active", isSelected);\r\n option.setAttribute("aria-selected", String(isSelected));\r\n });\r\n\r\n setControlText(combobox, "");\r\n if (!chipsNode) return;\r\n chipsNode.style.display = "contents";\r\n chipsNode.innerHTML = "";\r\n if (!chipsNode || !hasSelection) return;
1\r\n\r\n let chipSizeClass = "ux4g-input-chip-sm";\r\n if (combobox.classList.contains("ux4g-combobox-sm")) chipSizeClass = "ux4g-input-chip-xs";\r\n if (combobox.classList.contains("ux4g-combobox-lg")) chipSizeClass = "ux4g-input-chip";\r\n\r\n checkedInputs.forEach((input) => {\r\n const label = getInputLabel(input);\r\n if (!label) return;\r\n\r\n const chip = document.createElement("span");\r\n chip.className = `${chipSizeClass} ux4g-combobox-chip`;\r\n chip.setAttribute("role", "button");\r\n chip.setAttribute("tabindex", "0");\r\n chip.setAttribute("aria-label", `Remove ${label}`);\r\n chip.setAttribute("data-input-id", input.id);\r\n chip.innerHTML = `<span class="ux4g-icon-outlined" aria-hidden="true">token</span><span>${label}</span><span class="ux4g-icon-outlined" aria-hidden="true">close</span>`;\r\n\r\n const closeBtn = chip.querySelector(".ux4g-icon-outlined:last-child");\r\n\r\n const clearSelection = (event) => {\r\n event.preventDefault();\r\n event.stopPropagation();\r\n input.checked = false;\r\n renderMultiSelection(combobox);\r\n };\r\n\r\n chip.addEventListener("mousedown", (event) => {\r\n event.preventDefault();\r\n event.stopPropagation();\r\n });\r\n\r\n if (closeBtn) {\r\n closeBtn.addEventListener("click", clearSelection);\r\n }\r\n\r\n chip.addEventListener("keydown", (event) => {\r\n if (event.key === "Enter" || event.key === " ") {\r\n clearSelection(event);\r\n }\r\n });\r\n\r\n chipsNode.appendChild(chip);\r\n });\r\n };\r\n\r\n const DEBOUNCE_DELAY = 150;\r\n const debounce = (fn, delay) => {\r\n let t;\r\n return (...args) => {\r\n clearTimeout(t);\r\n t = setTimeout(() => fn(...args), delay);\r\n };\r\n };\r\n\r\n function toggleNoResults(menu, show) {\r\n let el = menu.querySelector(".ux4g-combobox-no-results");\r\n if (show) {\r\n if (!el) {\r\n el = document.createElement("div");\r\n el.className = "ux4g-combobox-no-results ux4g-p-s ux4g-text-center ux4g-text-muted";\r\n el.textContent = "No results found";\r\n menu.appendChild(el);\r\n }\r\n el.style.display = "";\r\n } else if (el) {\r\n el.style.display = "none";\r\n }\r\n }\r\n\r\n comboboxs.forEach((combobox, index) => {\r\n const control = combobox.querySelector(".ux4g-combobox-control");\r\n const menu = combobox.querySelector(".ux4g-combobox-menu");\r\n const input = combobox.querySelector("[ux4g-combobox-search]");\r\n const caret = combobox.querySelector(".ux4g-combobox-caret");\r\n\r\n if (!control || !menu) return;\r\n\r\n // Ensure all focusable descendants inside a <button> control are removed\r\n // from tab order to avoid nested-interactive accessibility violations.\r\n if (control.tagName === "BUTTON") {\r\n control.querySelectorAll("input, textarea, select, button").forEach((el) => {\r\n el.setAttribute("tabindex", "-1");\r\n });\r\n }\r\n\r\n if (!control.id) {\r\n control.id = `ux4g-combobox-control-${index + 1}`;\r\n }\r\n\r\n const popupRole = menu.getAttribute("role") === "menu" ? "menu" : "listbox";\r\n control.setAttribute("aria-haspopup", popupRole);\r\n control.setAttribute("aria-expanded", "false");\r\n menu.setAttribute("aria-labelledby", control.id);\r\n\r\n const isSingle = combobox.classList.contains("ux4g-combobox-single");\r\n const isMulti = combobox.classList.contains("ux4g-combobox-multi");\r\n const filterMode = combobox.getAttribute("ux4g-combobox-filter") || "contains";\r\n\r\n const comboboxInputs = Array.from(menu.querySelectorAll(".ux4g-combobox-option-input"));\r\n comboboxInputs.forEach((input, inputIndex) => {\r\n if (!input.id) {\r\n input.id = `${control.id}-option-${inputIndex + 1}`;\r\n }\r\n });\r\n\r\n const options = isMulti\r\n ? Array.from(combobox.querySelectorAll(".ux4g-combobox-option"))\r\n : Array.from(combobox.querySelectorAll(".ux4g-combobox-single-option"));\r\n\r\n // Store original text for filtering\r\n options.forEach((option) => {\r\n let labelNode = isMulti\r\n ? option.querySelector(".ux4g-checkbox-label")\r\n : option.querySelector(".ux4g-list-item-start");\r\n\r\n option.dataset.originalText = labelNode\r\n ? labelNode.textContent.trim()\r\n : option.textContent.trim();\r\n });\r\n\r\n const handleFilter = () => {\r\n if (!input) return;\r\n const value = input.value;\r\n let visibleCount = 0;\r\n\r\n options.forEach((option) => {\r\n const originalText = option.dataset.originalText;\r\n const isMatch = window.ux4g.filterCore.matches(originalText, value, filterMode);\r\n\r\n option.style.display = isMatch ? "" : "none";\r\n\r\n const labelNode = isMulti\r\n ? option.querySelector(".ux4g-checkbox-label")\r\n : option.querySelector(".ux4g-list-item-start");\r\n\r\n if (labelNode) {\r\n if (value && isMatch) {\r\n labelNode.innerHTML = `<span>${window.ux4g.filterCore.highlight(originalText, value, filterMode)}</span>`;\r\n } else {\r\n labelNode.textContent = originalText;\r\n }\r\n }\r\n if (isMatch) visibleCount++;\r\n });\r\n toggleNoResults(menu, visibleCount === 0 && value.trim());\r\n };\r\n\r\n if (input) {\r\n input.addEventListener("focus", () => openCombobox(combobox));\r\n input.addEventListener("input", debounce(() => {\r\n openCombobox(combobox);\r\n if (isSingle && input.value.trim() === "") {\r\n setSingleSelectedOption(combobox, null);\r\n applySingleSelection(combobox, "");\r\n }\r\n handleFilter();\r\n }, DEBOUNCE_DELAY));\r\n }\r\n\r\n if (caret) {\r\n caret.addEventListener("click", (e) => {\r\n e.stopPropagation();\r\n combobox.classList.contains("is-open") ? closeCombobox(combobox) : openCombobox(combobox);\r\n });\r\n }\r\n\r\n control.addEventListener("click", (event) => {\r\n if (control.disabled || control.getAttribute("aria-disabled") === "true") {\r\n event.preventDefault();\r\n event.stopPropagation();\r\n return;\r\n }\r\n\r\n if (event.target.closest("[ux4g-combobox-search]")) {\r\n if (!combobox.classList.contains("is-open")) openCombobox(combobox);\r\n return;\r\n }\r\n event.preventDefault();\r\n event.stopPropagation();\r\n if (combobox.classList.contains("is-open")) closeCombobox(combobox);\r\n else openCombobox(combobox);\r\n });\r\n\r\n menu.addEventListener("click", (event) => {\r\n const choice = event.target.closest("[ux4g-combobox-choice]");\r\n if (!choice) {\r\n // Handle Action Combobox closing\r\n const row
1= event.target.closest(".ux4g-list-item-row");\r\n if (row) {\r\n const isActionCombobox =\r\n combobox.classList.contains("ux4g-combobox-button") ||\r\n combobox.classList.contains("ux4g-combobox-overflow");\r\n if (isActionCombobox) {\r\n closeCombobox(combobox);\r\n }\r\n }\r\n return;\r\n }\r\n event.stopPropagation();\r\n\r\n const value = getChoiceLabel(choice);\r\n if (!value) return;\r\n\r\n if (isSingle) {\r\n setSingleSelectedOption(combobox, choice);\r\n applySingleSelection(combobox, value);\r\n handleFilter(); // Reset highlight\r\n } else if (!isMulti) {\r\n applySingleSelection(combobox, value);\r\n }\r\n closeCombobox(combobox);\r\n });\r\n\r\n menu.addEventListener("change", (event) => {\r\n const inputEl = event.target.closest(".ux4g-combobox-option-input");\r\n if (!inputEl) return;\r\n\r\n if (isMulti) {\r\n renderMultiSelection(combobox);\r\n handleFilter();\r\n return;\r\n }\r\n\r\n if (inputEl.checked && !isMulti) {\r\n menu.querySelectorAll(".ux4g-combobox-option-input").forEach((item) => {\r\n if (item !== inputEl) item.checked = false;\r\n });\r\n }\r\n\r\n applySingleSelection(combobox, inputEl.checked ? getInputLabel(inputEl) : "");\r\n closeCombobox(combobox);\r\n });\r\n\r\n if (isMulti) {\r\n if (input) {\r\n const icon = input.previousElementSibling;\r\n if (icon && icon.classList.contains("ux4g-icon-outlined") && !input.closest(".ux4g-combobox-input-wrap")) {\r\n const wrapper = document.createElement("span");\r\n wrapper.className = "ux4g-combobox-input-wrap";\r\n icon.parentNode.insertBefore(wrapper, icon);\r\n wrapper.appendChild(icon);\r\n wrapper.appendChild(input);\r\n }\r\n }\r\n renderMultiSelection(combobox);\r\n return;\r\n }\r\n\r\n const preselectedSingle = menu.querySelector(\r\n ".ux4g-combobox-single-option.is-selected, .ux4g-combobox-single-option[aria-selected=\'true\']"\r\n );\r\n if (preselectedSingle) {\r\n setSingleSelectedOption(combobox, preselectedSingle);\r\n applySingleSelection(combobox, getChoiceLabel(preselectedSingle));\r\n return;\r\n }\r\n\r\n const preselectedChoice = menu.querySelector("[ux4g-combobox-choice][aria-selected=\'true\']");\r\n if (preselectedChoice) {\r\n applySingleSelection(combobox, getChoiceLabel(preselectedChoice));\r\n return;\r\n }\r\n\r\n const preselectedInput = menu.querySelector(".ux4g-combobox-option-input:checked");\r\n if (preselectedInput) {\r\n applySingleSelection(combobox, getInputLabel(preselectedInput));\r\n }\r\n });\r\n\r\n document.addEventListener("click", (event) => {\r\n const openComboboxes = document.querySelectorAll(".ux4g-combobox.is-open");\r\n openComboboxes.forEach((combobox) => {\r\n if (!combobox.contains(event.target)) {\r\n combobox.classList.remove("is-open");\r\n const control = combobox.querySelector(".ux4g-combobox-control");\r\n if (control) control.setAttribute("aria-expanded", "false");\r\n }\r\n });\r\n });\r\n\r\n document.addEventListener("keydown", (event) => {\r\n if (event.key === "Escape") {\r\n const openComboboxes = document.querySelectorAll(".ux4g-combobox.is-open");\r\n openComboboxes.forEach((combobox) => {\r\n combobox.classList.remove("is-open");\r\n const control = combobox.querySelector(".ux4g-combobox-control");\r\n if (control) control.setAttribute("aria-expanded", "false");\r\n });\r\n }\r\n });\r\n});\r\n\r\n\r\n/* ========================================================= ux4g Tabs ========================================================= */\r\n\r\n\r\n(function (global) {\r\n \'use strict\';\r\n\r\n class UX4GTab {\r\n\r\n constructor(rootEl, options = {}) {\r\n if (!(rootEl instanceof HTMLElement)) {\r\n throw new Error(\'UX4GTab: rootEl must be an HTMLElement\');\r\n }\r\n if (rootEl.__ux4gTabInstance) {\r\n return rootEl.__ux4gTabInstance;\r\n }\r\n\r\n this.root = rootEl;\r\n this.options = Object.assign({ onChange: null }, options);\r\n rootEl.__ux4gTabInstance = this;\r\n\r\n this._detectConfig();\r\n this._cleanInitialState();\r\n this._bindEvents();\r\n }\r\n\r\n /* Detect elements */\r\n _detectConfig() {\r\n this.list = this.root.querySelector(\'.ux4g-tab-list\');\r\n this.items = Array.from(\r\n this.list.querySelectorAll(\'.ux4g-tab-item:not(.ux4g-tab-more)\')\r\n );\r\n this.moreBtns = Array.from(\r\n this.list.querySelectorAll(\'.ux4g-tab-more\')\r\n );\r\n this.panels = Array.from(\r\n this.root.querySelectorAll(\'.ux4g-tab-panel\')\r\n );\r\n }\r\n\r\n /* Clean any static open state */\r\n _cleanInitialState() {\r\n this.root.querySelectorAll(\'.ux4g-tab-dropdown-list.is-open\')\r\n .forEach(d => d.classList.remove(\'is-open\'));\r\n }\r\n\r\n /* Bind events */\r\n _bindEvents() {\r\n\r\n /* Regular tabs */\r\n this.items.forEac
1h(item => {\r\n item.addEventListener(\'click\', () => this._activateItem(item));\r\n item.addEventListener(\'keydown\', (e) => {\r\n if (e.key === \'Enter\') {\r\n e.preventDefault();\r\n this._activateItem(item);\r\n }\r\n });\r\n });\r\n\r\n /* More dropdown */\r\n this.moreBtns.forEach(moreBtn => {\r\n const dropdown = moreBtn.querySelector(\'.ux4g-tab-dropdown-list\');\r\n if (!dropdown) return;\r\n\r\n moreBtn.addEventListener(\'click\', (e) => {\r\n const isDropdownClick = e.target.closest(\'.ux4g-tab-dropdown-list\');\r\n\r\n if (!isDropdownClick) {\r\n e.stopPropagation();\r\n this._toggleDropdown(dropdown);\r\n }\r\n });\r\n\r\n // Add Enter key support to open dropdown\r\n moreBtn.addEventListener(\'keydown\', (e) => {\r\n if (e.key === \'Enter\') {\r\n e.preventDefault();\r\n const isDropdownClick = e.target.closest(\'.ux4g-tab-dropdown-list\');\r\n if (!isDropdownClick) {\r\n e.stopPropagation();\r\n this._toggleDropdown(dropdown);\r\n }\r\n }\r\n });\r\n\r\n dropdown.querySelectorAll(\'.ux4g-tab-dropdown-item, .ux4g-list-item\')\r\n .forEach(dItem => {\r\n dItem.addEventListener(\'click\', (e) => {\r\n e.stopPropagation();\r\n this._activateDropdownItem(dItem, moreBtn);\r\n });\r\n dItem.addEventListener(\'keydown\', (e) => {\r\n if (e.key === \'Enter\') {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n this._activateDropdownItem(dItem, moreBtn);\r\n }\r\n });\r\n });\r\n\r\n // Also ensure ANY click inside the dropdown list closes it\r\n dropdown.addEventListener(\'click\', (e) => {\r\n this._closeAllDropdowns();\r\n });\r\n });\r\n\r\n /* Outside click (scoped) */\r\n document.addEventListener(\'click\', (e) => {\r\n if (!e.target.closest(\'.ux4g-tab-more\')) {\r\n this._closeAllDropdowns();\r\n }\r\n });\r\n }\r\n\r\n /* Activate normal tab */\r\n _activateItem(item) {\r\n if (item.classList.contains(\'ux4g-tab-item-disabled\')) return;\r\n\r\n this._resetActive();\r\n item.classList.add(\'active\');\r\n\r\n const panelId = item.dataset.panel;\r\n if (panelId) this._showPanel(panelId);\r\n\r\n this._closeAllDropdowns();\r\n this._emitChange(panelId);\r\n }\r\n\r\n /* Activate dropdown item */\r\n _activateDropdownItem(dItem, moreBtn) {\r\n this._resetActive();\r\n\r\n moreBtn.classList.add(\'active\');\r\n dItem.classList.add(\'active\');\r\n\r\n const panelId = dItem.dataset.panel;\r\n if (panelId) this._showPanel(panelId);\r\n\r\n this._closeAllDropdowns();\r\n this._emitChange(panelId);\r\n }\r\n\r\n /* Reset active states */\r\n _resetActive() {\r\n this.list.querySelectorAll(\'.ux4g-tab-item\')\r\n .forEach(i => i.classList.remove(\'active\'));\r\n\r\n this.root.querySelectorAll(\'.ux4g-tab-dropdown-item, .ux4g-list-item\')\r\n .forEach(i => i.classList.remove(\'active\'));\r\n }\r\n\r\n /* Show panel */\r\n _showPanel(panelId) {\r\n this.panels.forEach(p => p.classList.remove(\'active\'));\r\n const target = this.root.querySelector(\'#\' + panelId);\r\n if (target) target.classList.add(\'active\');\r\n }\r\n\r\n /* Toggle dropdown */\r\n _toggleDropdown(dropdown) {\r\n const isOpen = dropdown.classList.contains(\'is-open\');\r\n this._closeAllDropdowns();\r\n\r\n if (!isOpen) {\r\n dropdown.classList.add(\'is-open\');\r\n }\r\n }\r\n\r\n /* Close dropdowns (scoped globally to all tabs) */\r\n _closeAllDropdowns() {\r\n document.querySelectorAll(\'.ux4g-tab-dropdown-list.is-open\')\r\n .forEach(d => d.classList.remove(\'is-open\'));\r\n }\r\n\r\n /* Emit change */\r\n _emitChange(panelId) {\r\n if (typeof this.options.onChange === \'function\') {\r\n this.options.onChange(panelId);\r\n }\r\n }\r\n\r\n /* Init all */\r\n static initAll(scope = document) {\r\n return Array.from(scope.querySelectorAll(\'[data-ux4g-tab]\'))\r\n .map(el => new UX4GTab(el));\r\n }\r\n }\r\n\r\n global.UX4GTab = UX4GTab;\r\n\r\n})(window);\r\n\r\n/* Auto init */\r\nwindow.ux4gCustomInitList = window.ux4gCustomInitList || []; window.ux4gCustomInitList.push(() => {\r\n UX4GTab.initAll();\r\n});\r\n\r\n/* ========================================================= slider js ========================================================= */\r\n\r\nwindow.ux4gCustomInitList = window.ux4gCustomInitList || []; window.ux4gCustomInitList.push(() => {\r\n\r\n /* =========================================================\r\n HELPERS\r\n ========================================================= */\r\n\r\n const pct = (val, min, max) => ((val - min) / (max - min)) * 100;\r\n\r\n const buildSteps = (container, min, max, step) => {\r\n if (!container) return;\r\n\r\n let html = "";\r\n\r\n for (let v = min; v <= max; v += step) {\r\n const p = pct(v, min, max);\r\n\r\n html += `\r\n <div class="ux4g-slider-step" data-value="${v}" style="left:${p}%">
1\r\n <span class="ux4g-slider-step-mark"></span>\r\n <span class="ux4g-slider-step-label">${v}</span>\r\n </div>\r\n `;\r\n }\r\n\r\n container.innerHTML = html;\r\n };\r\n\r\n /* =========================================================\r\n SINGLE SLIDERS\r\n ========================================================= */\r\n\r\n const singleSliders = document.querySelectorAll(\r\n ".ux4g-slider:not(.ux4g-slider-dual)"\r\n );\r\n\r\n singleSliders.forEach((slider) => {\r\n\r\n const input = slider.querySelector(".ux4g-slider-input");\r\n const fill = slider.querySelector(".ux4g-slider-fill");\r\n const thumb = slider.querySelector(".ux4g-slider-thumb");\r\n const steps = slider.querySelector(".ux4g-slider-steps");\r\n\r\n if (!input) return;\r\n\r\n const valueBox =\r\n slider.closest(".ux4g-slider-field")?.querySelector(\r\n ".ux4g-slider-range-box"\r\n );\r\n const valueBadge =\r\n slider.closest(".ux4g-slider-field")?.querySelector(\r\n ".ux4g-slider-value-badge"\r\n );\r\n\r\n const min = parseFloat(input.min) || 0;\r\n const max = parseFloat(input.max) || 100;\r\n const step = parseFloat(input.step) || 10;\r\n\r\n /* Helper: Make badge editable */\r\n const initBadgeEdit = (badge, targetInput) => {\r\n if (!badge) return;\r\n badge.setAttribute("contenteditable", "true");\r\n badge.style.cursor = "text";\r\n\r\n // Strict number only entry\r\n badge.addEventListener("keypress", (e) => {\r\n if (!/[0-9]/.test(e.key) && e.key !== "Enter") {\r\n e.preventDefault();\r\n }\r\n });\r\n\r\n badge.addEventListener("keydown", (e) => {\r\n if (e.key === "Enter") {\r\n e.preventDefault();\r\n badge.blur();\r\n }\r\n });\r\n\r\n badge.addEventListener("blur", () => {\r\n let raw = badge.textContent.replace(/[^0-9]/g, "");\r\n let val = parseInt(raw, 10);\r\n if (isNaN(val)) val = parseFloat(targetInput.value);\r\n\r\n // Clamp 0-100\r\n val = Math.min(max, Math.max(min, val));\r\n\r\n targetInput.value = val;\r\n // Trigger input event to update everything\r\n targetInput.dispatchEvent(new Event("input"));\r\n\r\n // Re-apply suffix if needed\r\n const suffix = badge.textContent.includes("%") ? "%" : "";\r\n badge.textContent = val + suffix;\r\n });\r\n };\r\n\r\n if (valueBox) initBadgeEdit(valueBox, input);\r\n\r\n /* Build Steps */\r\n\r\n buildSteps(steps, min, max, step);\r\n\r\n const stepEls = slider.querySelectorAll(".ux4g-slider-step");\r\n\r\n const update = () => {\r\n\r\n const val = parseFloat(input.value);\r\n const p = pct(val, min, max);\r\n\r\n if (fill) fill.style.width = p + "%";\r\n if (thumb) thumb.style.left = p + "%";\r\n\r\n // Only update text containers if not currently editing\r\n const containers = [valueBadge, valueBox];\r\n containers.forEach(container => {\r\n if (container && document.activeElement !== container) {\r\n const suffix = container.textContent.includes("%") ? "%" : "";\r\n container.textContent = val + suffix;\r\n }\r\n });\r\n\r\n stepEls.forEach((el) => {\r\n const sv = parseFloat(el.dataset.value);\r\n el.classList.toggle("is-active", sv <= val);\r\n });\r\n\r\n };\r\n\r\n input.addEventListener("input", update);\r\n\r\n update();\r\n\r\n });\r\n\r\n /* =========================================================\r\n DUAL RANGE SLIDERS\r\n ========================================================= */\r\n\r\n const dualSliders = document.querySelectorAll(".ux4g-slider-dual");\r\n\r\n dualSliders.forEach((slider) => {\r\n\r\n const inputMin = slider.querySelector(".ux4g-slider-input-min");\r\n const inputMax = slider.querySelector(".ux4g-slider-input-max");\r\n\r\n const fill = slider.querySelector(".ux4g-slider-fill");\r\n const thumbMin = slider.querySelector(".ux4g-slider-thumb-min");\r\n const thumbMax = slider.querySelector(".ux4g-slider-thumb-max");\r\n\r\n const steps = slider.querySelector(".ux4g-slider-steps");\r\n\r\n const field = slider.closest(".ux4g-slider-field");\r\n\r\n const minBox = field?.querySelector(".ux4g-slider-range-box:first-of-type, .ux4g-slider-range-box");\r\n // Actually better to select all and differentiate if multiple exist\r\n const rangeBoxes = field?.querySelectorAll(".ux4g-slider-range-box");\r\n const valueBadges = field?.querySelectorAll(".ux4g-slider-value-badge");\r\n\r\n if (!inputMin || !inputMax) return;\r\n\r\n const min = parseFloat(inputMin.min) || 0;\r\n const max = parseFloat(inputMin.max) || 100;\r\n const step = parseFloat(inputMin.step) || 10;\r\n\r\n /* Helper: Make box editable */\r\n const initBoxEdit = (badge, targetInput) => {\r\n if (!badge) return;\r\n badge.setAttribute("contenteditable", "true");\r\n badge.style.cursor = "text";\r\n\r\n // Strict number only entry\r\n badge.addEventListener("keypress", (e) => {\r\n if (!/[0-9]/.test(e.key) && e.key !== "Enter") {\r\n e.preventDefault();\r\n }\r\n });\r\n\r\n badge.addEventListener("keydown", (e) => {\r\n if (e.key === "Enter") {\r\n e.preventDefault();\r\n badge.blur();\r\n }\r\n });\r\n\r\n badge.addEventListener("blur", () => {\r\n let raw = badge.textContent.replace(/[^0-9]/g, "");\r\n let val = parseInt(raw, 10);\r\n if (isNaN(val)) val = parseFloat(targetInput.value);\r\n\r\n // Clamp min-max\r\n val = Math.min(max, Math.max(min, val));\r\n\r\n targetInput.value = val;\r\n // Trigger change to validate cross limits\r\n targetInput.dispatchEvent(new Event("input"));\r\n\r\n // Re-apply suffix\r\n const suffix = badge.textContent.includes("%") ? "%" : "";\r\n badge.textContent = val + suffix;\r\n });\r\n };\r\n\r\n if (rangeBoxes?.[0]) initBoxEdit(rangeBoxes[0], inputMin);\r\n if (rangeBoxes?.[1]) initBoxEdit(rangeBoxes[1], inputMax);\r\n\r\n buildSteps(steps, min, max, step);\r\n\r\n const stepEls = slider.querySelectorAll(".ux4g-slider-step");\r\n\r\n const update = (e) => {\r\n\r\n let vMin = parseFloat(inputMin.value);\r\n let vMax = parseFloat(inputMax.value);\r\n\r\n /* Prevent crossing */\r\n\r\n if (e?.target === inputMin && vMin >= vMax) {\r\n inputMin.value = vMax - step;\r\n vMin = parseFloat(inputMin.value);\r\n }
1\r\n\r\n if (e?.target === inputMax && vMax <= vMin) {\r\n inputMax.value = vMin + step;\r\n vMax = parseFloat(inputMax.value);\r\n }\r\n\r\n const pMin = pct(vMin, min, max);\r\n const pMax = pct(vMax, min, max);\r\n\r\n if (fill) {\r\n fill.style.left = pMin + "%";\r\n fill.style.width = (pMax - pMin) + "%";\r\n }\r\n\r\n if (thumbMin) thumbMin.style.left = pMin + "%";\r\n if (thumbMax) thumbMax.style.left = pMax + "%";\r\n\r\n // Sync dual badges\r\n if (valueBadges?.[0] && document.activeElement !== valueBadges[0]) {\r\n valueBadges[0].textContent = vMin + (valueBadges[0].textContent.includes("%") ? "%" : "");\r\n }\r\n if (valueBadges?.[1] && document.activeElement !== valueBadges[1]) {\r\n valueBadges[1].textContent = vMax + (valueBadges[1].textContent.includes("%") ? "%" : "");\r\n }\r\n\r\n // Sync dual boxes\r\n if (rangeBoxes?.[0] && document.activeElement !== rangeBoxes[0]) {\r\n rangeBoxes[0].textContent = vMin + (rangeBoxes[0].textContent.includes("%") ? "%" : "");\r\n }\r\n if (rangeBoxes?.[1] && document.activeElement !== rangeBoxes[1]) {\r\n rangeBoxes[1].textContent = vMax + (rangeBoxes[1].textContent.includes("%") ? "%" : "");\r\n }\r\n\r\n /* Thumb overlap fix */\r\n\r\n if (pMax - pMin < 10) {\r\n\r\n inputMin.style.zIndex = pMin > 50 ? "5" : "3";\r\n inputMax.style.zIndex = pMin > 50 ? "3" : "5";\r\n\r\n } else {\r\n\r\n inputMin.style.zIndex = "";\r\n inputMax.style.zIndex = "";\r\n\r\n }\r\n\r\n /* Active steps */\r\n\r\n stepEls.forEach((el) => {\r\n\r\n const sv = parseFloat(el.dataset.value);\r\n\r\n el.classList.toggle(\r\n "is-active",\r\n sv >= vMin && sv <= vMax\r\n );\r\n\r\n });\r\n\r\n };\r\n\r\n inputMin.addEventListener("input", update);\r\n inputMax.addEventListener("input", update);\r\n\r\n update();\r\n\r\n });\r\n\r\n});\r\n\r\n\r\n/* ========================================================= context alert js ========================================================= */\r\n/* Trigger Toast alerts via data attributes */\r\nwindow.ux4gCustomInitList = window.ux4gCustomInitList || []; window.ux4gCustomInitList.push(() => {\r\n document.querySelectorAll(\'[data-ux4g-toggle="toast"]\').forEach(btn => {\r\n btn.addEventListener(\'click\', () => {\r\n const position = btn.dataset.ux4gPosition || \'top-right\';\r\n const variant = btn.dataset.ux4gVariant || btn.dataset.ux4gStatus;\r\n const title = btn.dataset.ux4gTitle;\r\n const body = btn.dataset.ux4gBody;\r\n showContextAlert(position, variant, title, body);\r\n });\r\n });\r\n});\r\n\r\nlet alertCount = 0;\r\n\r\n/**\r\n * Shows a Context Alert (Toast) in the specified position with animation\r\n * @param {string} position - \'top-left\', \'top-right\', \'bottom-left\', or \'bottom-right\'\r\n * @param {string} [variant] - Optional: \'info\', \'success\', \'warning\', \'error\', \'none\'\r\n * @param {string} [customTitle] - Optional custom title text\r\n * @param {string} [customBody] - Optional custom body text\r\n */\r\nfunction showContextAlert(position, variant, customTitle, customBody) {\r\n const statuses = {\r\n \'info\': { icon: \'info\', title: \'Info \' },\r\n \'success\': { icon: \'check_circle\', title: \'Success \' },\r\n \'warning\': { icon: \'warning\', title: \'Warning \' },\r\n \'error\': { icon: \'error\', title: \'Error \' },\r\n \'none\': { icon: null, title: \'Alert Title\' }\r\n };\r\n\r\n // Determine type (use variant, or cycle through if not provided)\r\n const types = Object.keys(statuses).filter(t => t !== \'none\');\r\n const type = variant || types[alertCount++ % types.length];\r\n const status = statuses[type] || statuses[\'info\'];\r\n\r\n const title = customTitle || status.title;\r\n const bodyText = customBody || `This is a ${type} alert shown at the ${position.replace(\'-\', \' \')} corner.`;\r\n\r\n const containerId = `ux4g-alert-container-${position}`;\r\n let container = document.getElementById(containerId);\r\n\r\n if (!container) {\r\n container = document.createElement(\'div\');\r\n container.id = containerId;\r\n container.className = `ux4g-alert-container ux4g-alert-${position}`;\r\n document.body.appendChild(container);\r\n }\r\n\r\n const alert = document.createElement(\'div\');\r\n const animationClass = position.includes(\'left\') ? \'ux4g-animate-left\' : \'ux4g-animate-right\';\r\n\r\n // Apply correct status class (fallback to info if variant is \'none\' for styling)\r\n const statusClass = type === \'none\' ? \'info\' : type;\r\n alert.className = `ux4g-context-alert ux4g-alert-${statusClass} ${animationClass}`;\r\n\r\n // Icon Logic: None means no icon HTML\r\n const iconHtml = status.icon ? `<i class="ux4g-icon ux4g-alert-icon">${status.icon}</i>` : \'\';\r\n\r\n alert.innerHTML = `\r\n ${iconHtml}
1\r\n <span class="ux4g-alert-title">${title}</span>\r\n <div class="ux4g-alert-actions">\r\n <button class="ux4g-alert-close" onclick="closeContextAlert(this)">\r\n <i class="ux4g-icon">close</i>\r\n </button>\r\n </div>\r\n <div class="ux4g-alert-message">${bodyText}</div>\r\n `;\r\n\r\n if (position.includes(\'bottom\')) {\r\n container.insertBefore(alert, container.firstChild);\r\n } else {\r\n container.appendChild(alert);\r\n }\r\n\r\n setTimeout(() => {\r\n if (alert.parentNode) closeAlertWithAnimation(alert);\r\n }, 5000);\r\n}\r\n\r\n/**\r\n * Handles manual close click\r\n */\r\nfunction closeContextAlert(button) {\r\n const alert = button.closest(\'.ux4g-context-alert\');\r\n if (alert) {\r\n closeAlertWithAnimation(alert);\r\n }\r\n}\r\n\r\n/**\r\n * Closes an alert with a slide-out animation\r\n */\r\nfunction closeAlertWithAnimation(alert) {\r\n if (!alert) return;\r\n\r\n const isLeft = alert.classList.contains(\'ux4g-animate-left\');\r\n alert.style.transition = \'all 0.4s cubic-bezier(0.16, 1, 0.3, 1)\';\r\n alert.style.transform = isLeft ? \'translateX(-100%)\' : \'translateX(100%)\';\r\n alert.style.opacity = \'0\';\r\n\r\n // Remove element after animation completes\r\n setTimeout(() => {\r\n if (alert.parentNode) {\r\n alert.parentNode.removeChild(alert);\r\n }\r\n }, 400);\r\n}\r\n\r\n\r\n/* ========================================================= pagination js ========================================================= */\r\nwindow.ux4gCustomInitList = window.ux4gCustomInitList || []; window.ux4gCustomInitList.push(() => {\r\n const pageSelects = document.querySelectorAll(".ux4g-page-size select");\r\n\r\n pageSelects.forEach(select => {\r\n const wrapper = select.closest(".ux4g-page-size-select-wrapper");\r\n\r\n const updateState = () => {\r\n if (wrapper) {\r\n // If there\'s a selected option that isn\'t empty (or for simplicity, any selection)\r\n if (select.value) {\r\n wrapper.classList.add("has-value");\r\n } else {\r\n wrapper.classList.remove("has-value");\r\n }\r\n }\r\n };\r\n\r\n select.addEventListener("change", updateState);\r\n // Do not run on load, so we show the placeholder icon by default\r\n });\r\n});\r\n\r\n\r\n// NPS and Emoji Button interactions\r\n// Using global event delegation so it works seamlessly with dynamic HTML, React, Angular, etc.\r\ndocument.addEventListener(\'click\', (e) => {\r\n // 1. NPS interaction\r\n const npsBtn = e.target.closest(\'.feedback-nps-button\');\r\n if (npsBtn) {\r\n const container = npsBtn.closest(\'.ux4g-feedback-nps-wrapper\') || npsBtn.parentElement;\r\n const siblings = Array.from(container.querySelectorAll(\'.feedback-nps-button\'));\r\n const clickedIndex = siblings.indexOf(npsBtn);\r\n\r\n const isHighestActive = npsBtn.classList.contains(\'active\') &&\r\n (clickedIndex === siblings.length - 1 || !siblings[clickedIndex + 1]?.classList.contains(\'active\'));\r\n\r\n if (isHighestActive) {\r\n siblings.forEach(s => s.classList.remove(\'active\'));\r\n container.removeAttribute(\'data-nps-rating\');\r\n } else {\r\n container.setAttribute(\'data-nps-rating\', clickedIndex);\r\n siblings.forEach((s, i) => {\r\n if (i <= clickedIndex) {\r\n s.classList.add(\'active\');\r\n } else {\r\n s.classList.remove(\'active\');\r\n }\r\n });\r\n }\r\n return;\r\n }\r\n\r\n // 2. Emoji interaction\r\n const emojiBtn = e.target.closest(\'.feedback-emoji-button\');\r\n if (emojiBtn) {\r\n const wasActive = emojiBtn.classList.contains(\'active\');\r\n const container = emojiBtn.closest(\'.ux4g-d-flex\') || document;\r\n container.querySelectorAll(\'.feedback-emoji-button\').forEach(b => b.classList.remove(\'active\'));\r\n\r\n if (!wasActive) {\r\n emojiBtn.classList.add(\'active\');\r\n }\r\n return;\r\n }\r\n\r\n // 3. Star interaction\r\n const starBtn = e.target.closest(\'.ux4g-feedback-star\');\r\n if (starBtn) {\r\n const container = starBtn.parentElement;\r\n const siblings = Array.from(container.querySelectorAll(\'.ux4g-feedback-star\'));\r\n const clickedIndex = siblings.indexOf(starBtn);\r\n\r\n const isOnlyActive = starBtn.classList.contains(\'active\') &&\r\n (clickedIndex === siblings.length - 1 || !siblings[clickedIndex + 1]?.classList.contains(\'active\'));
1\r\n\r\n if (isOnlyActive) {\r\n siblings.forEach(s => s.classList.remove(\'active\'));\r\n container.removeAttribute(\'data-rating\');\r\n } else {\r\n container.setAttribute(\'data-rating\', clickedIndex + 1);\r\n siblings.forEach((s, i) => {\r\n if (i <= clickedIndex) {\r\n s.classList.add(\'active\');\r\n } else {\r\n s.classList.remove(\'active\');\r\n }\r\n });\r\n }\r\n\r\n // Toggle Submit button state based on rating\r\n const feedbackCard = starBtn.closest(\'.ux4g-feedback\');\r\n if (feedbackCard) {\r\n const submitBtn = feedbackCard.querySelector(\'.ux4g-btn-primary\');\r\n if (submitBtn) {\r\n const hasRating = container.hasAttribute(\'data-rating\') && container.getAttribute(\'data-rating\') !== \'\';\r\n if (hasRating) {\r\n submitBtn.removeAttribute(\'disabled\');\r\n } else {\r\n submitBtn.setAttribute(\'disabled\', \'\');\r\n }\r\n }\r\n }\r\n return;\r\n }\r\n\r\n // 4. Submit button showing success card delegator\r\n const submitBtn = e.target.closest(\'.ux4g-feedback:not(.ux4g-text-center) .ux4g-btn-primary\');\r\n if (submitBtn) {\r\n const ratingCard = submitBtn.closest(\'.ux4g-feedback\');\r\n if (ratingCard) {\r\n const container = ratingCard.parentElement;\r\n if (container) {\r\n const successCard = container.querySelector(\'.ux4g-feedback.ux4g-text-center\');\r\n if (successCard) {\r\n e.preventDefault();\r\n ratingCard.style.display = \'none\';\r\n successCard.style.display = \'flex\';\r\n return;\r\n }\r\n }\r\n }\r\n }\r\n\r\n // 5. Close button redirect delegator\r\n const closeBtn = e.target.closest(\'.feedback-submitted-close-btn\');\r\n if (closeBtn) {\r\n e.preventDefault();\r\n window.location.href = \'./global-service-discovery.html\';\r\n return;\r\n }\r\n\r\n // 6. Submit and Skip Reset interaction\r\n const resetBtn = e.target.closest(\'.ux4g-feedback .ux4g-btn-primary, .ux4g-feedback .ux4g-btn-text-primary\');\r\n if (resetBtn) {\r\n const feedbackContainer = resetBtn.closest(\'.ux4g-feedback\');\r\n if (feedbackContainer) {\r\n feedbackContainer.querySelectorAll(\'textarea\').forEach(textarea => {\r\n textarea.value = \'\';\r\n });\r\n feedbackContainer.querySelectorAll(\'.active\').forEach(activeEl => {\r\n activeEl.classList.remove(\'active\');\r\n });\r\n }\r\n }\r\n});\r\n\r\n// rating enabled btn js\r\nwindow.ux4gCustomInitList = window.ux4gCustomInitList || []; window.ux4gCustomInitList.push(() => {\r\n // Initialize all feedback cards\' submit buttons on load/open\r\n const syncAllFeedbackSubmitButtons = () => {\r\n document.querySelectorAll(\'.ux4g-feedback\').forEach(feedbackCard => {\r\n const starContainer = feedbackCard.querySelector(\'[data-rating]\');\r\n const submitBtn = feedbackCard.querySelector(\'.ux4g-btn-primary\');\r\n if (submitBtn) {\r\n const hasRating = starContainer && starContainer.hasAttribute(\'data-rating\') && starContainer.getAttribute(\'data-rating\') !== \'\';\r\n if (hasRating) {\r\n submitBtn.removeAttribute(\'disabled\');\r\n } else {\r\n submitBtn.setAttribute(\'disabled\', \'\');\r\n }\r\n }\r\n });\r\n };\r\n\r\n syncAllFeedbackSubmitButtons();\r\n\r\n // Also sync when modals/popups are shown/opened\r\n document.addEventListener(\'click\', (e) => {\r\n const trigger = e.target.closest(\'[data-modal-target], [data-target], .open-modal-btn, .modal-backdrop-btn-trigger\');\r\n if (trigger) {\r\n setTimeout(syncAllFeedbackSubmitButtons, 300);\r\n }\r\n });\r\n});\r\n\r\n\r\n/* ========================================================= carousel js ========================================================= */\r\nwindow.ux4gCustomInitList = window.ux4gCustomInitList || []; window.ux4gCustomInitList.push(() => {\r\n const carousels = document.querySelectorAll(".ux4g-carousel");
1\r\n\r\n carousels.forEach(carousel => {\r\n const slidesContainer = carousel.querySelector(".ux4g-carousel-slides");\r\n const slides = carousel.querySelectorAll(".ux4g-carousel-slide");\r\n const slideCount = slides.length;\r\n const prevBtn = carousel.querySelector(".ux4g-carousel-arrow-prev");\r\n const nextBtn = carousel.querySelector(".ux4g-carousel-arrow-next");\r\n const dots = carousel.querySelectorAll(".ux4g-carousel-dot");\r\n\r\n if (slideCount === 0) return;\r\n\r\n let currentIndex = 0;\r\n\r\n // Initialize: find if any slide is already marked as active\r\n slides.forEach((slide, index) => {\r\n if (slide.classList.contains("is-active")) {\r\n currentIndex = index;\r\n }\r\n });\r\n\r\n const updateCarousel = (index) => {\r\n // Handle looping\r\n if (index < 0) {\r\n index = slideCount - 1;\r\n } else if (index >= slideCount) {\r\n index = 0;\r\n }\r\n\r\n currentIndex = index;\r\n\r\n // Move slides\r\n if (slidesContainer) {\r\n slidesContainer.style.transform = `translateX(-${currentIndex * 100}%)`;\r\n }\r\n\r\n // Update slides active state\r\n slides.forEach((slide, i) => {\r\n slide.classList.toggle("is-active", i === currentIndex);\r\n slide.setAttribute("aria-hidden", i !== currentIndex);\r\n });\r\n\r\n // Update dots active state\r\n dots.forEach((dot, i) => {\r\n dot.classList.toggle("is-active", i === currentIndex);\r\n dot.setAttribute("aria-current", i === currentIndex ? "step" : "false");\r\n });\r\n };\r\n\r\n // Event Listeners\r\n if (prevBtn) {\r\n prevBtn.addEventListener("click", (e) => {\r\n e.preventDefault();\r\n updateCarousel(currentIndex - 1);\r\n });\r\n }\r\n\r\n if (nextBtn) {\r\n nextBtn.addEventListener("click", (e) => {\r\n e.preventDefault();\r\n updateCarousel(currentIndex + 1);\r\n });\r\n }\r\n\r\n dots.forEach((dot, index) => {\r\n dot.addEventListener("click", (e) => {\r\n e.preventDefault();\r\n updateCarousel(index);\r\n });\r\n });\r\n\r\n // Initial update to ensure everything is synced\r\n updateCarousel(currentIndex);\r\n });\r\n});\r\n\r\n/* ========================================================= range slider js ========================================================= */\r\ndocument.addEventListener("input", (e) => {\r\n if (e.target.classList.contains("ux4g-slider-input")) {\r\n const sliderField = e.target.closest(".ux4g-slider-field");\r\n const slider = e.target.closest(".ux4g-slider");\r\n if (!slider) return;\r\n\r\n const isDual = slider.classList.contains("ux4g-slider-dual");\r\n const fill = slider.querySelector(".ux4g-slider-fill");\r\n\r\n if (!isDual) {\r\n const thumb = slider.querySelector(".ux4g-slider-thumb");\r\n const percent = ((e.target.value - e.target.min) / (e.target.max - e.target.min)) * 100;\r\n if (fill) fill.style.width = percent + "%";\r\n if (thumb) thumb.style.left = percent + "%";\r\n\r\n if (sliderField) {\r\n const badge = sliderField.querySelector(".ux4g-slider-value-badge");\r\n if (badge) badge.textContent = e.target.value + "%";\r\n }\r\n } else {\r\n const inputMin = slider.querySelector(".ux4g-slider-input-min");\r\n const inputMax = slider.querySelector(".ux4g-slider-input-max");\r\n const thumbMin = slider.querySelector(".ux4g-slider-thumb-min");\r\n const thumbMax = slider.querySelector(".ux4g-slider-thumb-max");\r\n\r\n let min = parseFloat(inputMin.value);\r\n let max = parseFloat(inputMax.value);\r\n const rangeMin = parseFloat(inputMin.min);\r\n const rangeMax = parseFloat(inputMin.max);\r\n\r\n if (e.target.classList.contains("ux4g-slider-input-min")) {\r\n if (min > max) {\r\n min = max;\r\n inputMin.value = min;\r\n }\r\n } else {\r\n if (max < min) {\r\n max = min;\r\n inputMax.value = max;\r\n }\r\n }\r\n\r\n const left = ((min - rangeMin) / (rangeMax - rangeMin)) * 100;\r\n const width = ((max - min) / (rangeMax - rangeMin)) * 100;\r\n\r\n if (fill) {\r\n fill.style.left = left + "%";\r\n fill.style.width = width + "%";\r\n }\r\n if (thumbMin) thumbMin.style.left = left + "%";\r\n if (thumbMax) thumbMax.style.left = (left + width) + "%";\r\n\r\n if (sliderField) {\r\n const badges = sliderField.querySelectorAll(".ux4g-slider-value-badge");\r\n if (badges.length >= 2) {\r\n badges[0].textContent = min + "%";\r\n badges[1].textContent = max + "%";\r\n }\r\n }\r\n }\r\n }\r\n});\r\n\r\n\r\n/********************************* UX4G DatePicker & TimePicker JS **
1*********************************/\r\n\r\n(function (global) {\r\n "use strict";\r\n\r\n const makeKeyboardClickable = (el) => {\r\n if (!el) return;\r\n if (el.tagName !== \'BUTTON\' && el.tagName !== \'INPUT\') {\r\n if (!el.hasAttribute(\'tabindex\')) el.setAttribute(\'tabindex\', \'0\');\r\n el.addEventListener(\'keydown\', (e) => {\r\n if (e.key === \'Enter\' || e.key === \' \') {\r\n e.preventDefault();\r\n el.click();\r\n }\r\n });\r\n }\r\n };\r\n\r\n // Shared Backdrop\r\n let backdrop = document.querySelector(\'.ux4g-date-picker-backdrop\');\r\n const getBackdrop = () => {\r\n if (!backdrop) {\r\n backdrop = document.createElement(\'div\');\r\n backdrop.className = \'ux4g-date-picker-backdrop\';\r\n document.body.appendChild(backdrop);\r\n }\r\n return backdrop;\r\n };\r\n\r\n const isMobile = () => window.innerWidth <= 576;\r\n const initializedDatePickers = new WeakSet();\r\n const initializedRangeDatePickers = new WeakSet();\r\n const initializedTimePickers = new WeakSet();\r\n\r\n const adjustDropdownPosition = (dropdown, container) => {\r\n if (!dropdown || !container) return;\r\n const viewportGap = 8;\r\n dropdown.style.top = \'\';\r\n dropdown.style.bottom = \'\';\r\n dropdown.style.left = \'\';\r\n dropdown.style.right = \'\';\r\n dropdown.style.width = \'\';\r\n dropdown.style.maxHeight = \'\';\r\n dropdown.style.overflowX = \'\';\r\n dropdown.style.overflowY = \'\';\r\n dropdown.style.marginTop = \'\';\r\n dropdown.style.marginBottom = \'\';\r\n const containerRect = container.getBoundingClientRect();\r\n const dropdownRect = dropdown.getBoundingClientRect();\r\n const windowHeight = window.innerHeight || document.documentElement.clientHeight;\r\n const windowWidth = window.innerWidth || document.documentElement.clientWidth;\r\n\r\n // Anchor to the actual input rather than the container\'s bounding box. Using\r\n // the container edge can create artificial gaps when the container is made\r\n // taller for layout reasons.\r\n const trigger = container.querySelector(\'.ux4g-date-picker-input:focus, .ux4g-time-picker-input:focus\') ||\r\n container.querySelector(\'.ux4g-date-picker-input, .ux4g-time-picker-input\') ||\r\n container;\r\n const triggerRect = trigger.getBoundingClientRect();\r\n\r\n const spaceBelow = windowHeight - triggerRect.bottom - viewportGap;\r\n const spaceAbove = triggerRect.top - viewportGap;\r\n const openAbove = spaceBelow < dropdownRect.height && spaceAbove > spaceBelow;\r\n const width = Math.min(containerRect.width, windowWidth - (viewportGap * 2));\r\n const left = Math.min(\r\n Math.max(containerRect.left, viewportGap),\r\n Math.max(windowWidth - width - viewportGap, viewportGap)\r\n );\r\n\r\n // Fixed positioning escapes overflow/scroll containers (including Storybook\r\n // preview wrappers) while keeping the popup anchored to its input.\r\n dropdown.style.position = \'fixed\';\r\n dropdown.style.width = `${width}px`;\r\n dropdown.style.left = `${left}px`;\r\n dropdown.style.right = \'auto\';\r\n if (openAbove) {\r\n const topPos = Math.max(viewportGap, triggerRect.top - dropdownRect.height - viewportGap);\r\n dropdown.style.top = `${topPos}px`;\r\n } else {\r\n dropdown.style.top = `${triggerRect.bottom + viewportGap}px`;\r\n }\r\n dropdown.style.bottom = \'auto\';\r\n };\r\n\r\n const resetDropdownPosition = (dropdown) => {\r\n if (!dropdown) return;\r\n [\'position\', \'top\', \'bottom\', \'left\', \'right\', \'width\', \'maxHeight\',\r\n \'overflowX\', \'overflowY\', \'marginTop\', \'marginBottom\']\r\n .forEach(property => { dropdown.style[property] = \'\'; });\r\n };\r\n\r\n const repositionOpenDropdowns = () => {\r\n if (isMobile()) return;\r\n document.querySelectorAll(\'.ux4g-date-picker-dropdown.is-open, .ux4g-time-picker-dropdown.is-open\')\r\n .forEach(dropdown => {\r\n const container = dropdown.closest(\'.ux4g-date-range-picker, .ux4g-date-picker-container, .ux4g-time-picker-container\');\r\n adjustDropdownPosition(dropdown, container);\r\n });\r\n };\r\n\r\n window.addEventListener(\'resize\', repositionOpenDropdowns);\r\n window.addEventListener(\'scroll\', repositionOpenDropdowns, true);\r\n\r\n class DatePicker {\r\n constructor(container) {\r\n this.container = container;\r\n this.input = container.querySelector(\'.ux4g-date-picker-input\');\r\n this.dropdown = container.querySelector(\'.ux4g-date-picker-dropdown\');\r\n this.calendarGrid = container.querySelector(\'.ux4g-date-picker-grid\');\r\n this.monthLabel = container.querySelector(\'.ux4g-date-picker-current\');\r\n\r\n const navBtns = container.querySelectorAll(\'.ux4g-date-picker-nav-btn\');\r\n this.prevBtn = navBtns[0];\r\n this.nextBtn = navBtns[1];\r\n\r\n this.confirmBtn = container.querySelector(\'.ux4g-btn-primary\');\r\n this.cancelBtn = container.querySelector(\'.ux4g-btn-outline-neutral\');\r\n\r\n this.currentDate = new Date();\r\n this.viewDate = new Date(this.currentDate.getFullYear(), this.currentDate.getMonth(), 1);\r\n this.selectedDate = null;\r\n this.tempSelectedDate = null;\r\n\r\n this.isSelectingYearMonth = false;\r\n\r\n this._init();\r\n }\r\n\r\n _init() {\r\n if (!this.input || !this.dropdown) return;\r\n\r\n if (this.input.value) {\r\n const parts = this.input.value.split(\'/\');\r\n if (parts.length === 3) {\r\n this.selectedDate = new Date(parts[2], parts[1] - 1, parts[0]);\r\n this.viewDate = new Date(parts[2], parts[1] - 1, 1);\r\n this.tempSelectedDate = new Date(this.selectedDate);\r\n }\r\n }\r\n\r\n // Keyboard accessibility\r\n this.input.addEventListener(\'keydown\', (e) => {\r\n if (e.key === \'Enter\' || e.key === \' \') {\r\n e.preventDefault();\r\n this.open();\r\n }\r\n });
1\r\n\r\n this.input.addEventListener(\'focus\', (e) => {\r\n this.open();\r\n });\r\n\r\n this.input.addEventListener(\'click\', (e) => {\r\n e.stopPropagation();\r\n this.open();\r\n });\r\n\r\n if (this.prevBtn) {\r\n makeKeyboardClickable(this.prevBtn);\r\n this.prevBtn.addEventListener(\'click\', (e) => {\r\n e.stopPropagation();\r\n if (this.isSelectingYearMonth) {\r\n this.changeYearRange(-8);\r\n } else {\r\n this.changeMonth(-1);\r\n }\r\n });\r\n }\r\n\r\n if (this.nextBtn) {\r\n makeKeyboardClickable(this.nextBtn);\r\n this.nextBtn.addEventListener(\'click\', (e) => {\r\n e.stopPropagation();\r\n if (this.isSelectingYearMonth) {\r\n this.changeYearRange(8);\r\n } else {\r\n this.changeMonth(1);\r\n }\r\n });\r\n }\r\n\r\n if (this.monthLabel) {\r\n makeKeyboardClickable(this.monthLabel);\r\n this.monthLabel.addEventListener(\'click\', (e) => {\r\n e.stopPropagation();\r\n this.isSelectingYearMonth = !this.isSelectingYearMonth;\r\n this.render();\r\n setTimeout(() => {\r\n if (this.isSelectingYearMonth) {\r\n const calendarContainer = this.container.querySelector(\'.ux4g-date-picker-calendar\');\r\n let el = calendarContainer.querySelector(\'.ux4g-date-picker-year-item.is-selected\') || calendarContainer.querySelector(\'.ux4g-date-picker-year-item\');\r\n if (el) el.focus();\r\n } else {\r\n let el = this.calendarGrid.querySelector(\'.is-selected\') || this.calendarGrid.querySelector(\'.is-today\') || this.calendarGrid.querySelector(\'.ux4g-date-picker-day:not(.is-muted)\');\r\n if (el) el.focus();\r\n }\r\n }, 0);\r\n });\r\n }\r\n\r\n if (this.confirmBtn) {\r\n makeKeyboardClickable(this.confirmBtn);\r\n this.confirmBtn.addEventListener(\'click\', (e) => {\r\n e.stopPropagation();\r\n if (this.isSelectingYearMonth) {\r\n this.isSelectingYearMonth = false;\r\n this.render();\r\n setTimeout(() => {\r\n let el = this.calendarGrid.querySelector(\'.is-selected\') || this.calendarGrid.querySelector(\'.is-today\') || this.calendarGrid.querySelector(\'.ux4g-date-picker-day:not(.is-muted)\');\r\n if (el) el.focus();\r\n }, 0);\r\n } else {\r\n this.confirmSelection();\r\n }\r\n });\r\n }\r\n\r\n if (this.cancelBtn) {\r\n makeKeyboardClickable(this.cancelBtn);\r\n this.cancelBtn.addEventListener(\'click\', (e) => {\r\n e.stopPropagation();\r\n this.cancelSelection();\r\n });\r\n }\r\n\r\n document.addEventListener(\'click\', (e) => {\r\n if (!this.container.contains(e.target) && !getBackdrop().contains(e.target)) {\r\n this.close();\r\n }\r\n });\r\n\r\n getBackdrop().addEventListener(\'click\', () => {\r\n this.close();\r\n });\r\n\r\n this.render();\r\n }\r\n\r\n open() {\r\n if (this.dropdown) {\r\n this.tempSelectedDate = this.selectedDate ? new Date(this.selectedDate) : null;\r\n this.isSelectingYearMonth = false;\r\n this.dropdown.classList.add(\'is-open\');\r\n if (isMobile()) {\r\n getBackdrop().classList.add(\'is-active\');\r\n document.body.style.overflow = \'hidden\';\r\n } else {\r\n adjustDropdownPosition(this.dropdown, this.container);\r\n }\r\n this.render();\r\n }\r\n }\r\n\r\n close() {\r\n if (this.dropdown) {\r\n this.dropdown.classList.remove(\'is-open\');\r\n resetDropdownPosition(this.dropdown);\r\n getBackdrop().classList.remove(\'is-active\');\r\n document.body.style.overflow = \'\';\r\n }\r\n }\r\n\r\n confirmSelection() {\r\n this.selectedDate = this.tempSelectedDate ? new Date(this.tempSelectedDate) : null;\r\n if (this.selectedDate) {\r\n const day = String(this.selectedDate.getDate()).padStart(2, \'0\');
1\r\n const month = String(this.selectedDate.getMonth() + 1).padStart(2, \'0\');\r\n const year = this.selectedDate.getFullYear();\r\n this.input.value = `${day}/${month}/${year}`;\r\n } else {\r\n this.input.value = \'\';\r\n }\r\n this.close();\r\n }\r\n\r\n cancelSelection() {\r\n this.tempSelectedDate = this.selectedDate ? new Date(this.selectedDate) : null;\r\n this.close();\r\n }\r\n\r\n changeMonth(delta) {\r\n this.viewDate.setMonth(this.viewDate.getMonth() + delta);\r\n this.render();\r\n }\r\n\r\n changeYearRange(delta) {\r\n this.viewDate.setFullYear(this.viewDate.getFullYear() + delta);\r\n this.render();\r\n }\r\n\r\n render() {\r\n if (this.isSelectingYearMonth) {\r\n this.renderYearMonthSelection();\r\n } else {\r\n this.renderCalendar();\r\n }\r\n }\r\n\r\n renderCalendar() {\r\n const year = this.viewDate.getFullYear();\r\n const month = this.viewDate.getMonth();\r\n const monthNames = ["January", "February", "March", "April", "May", "June",\r\n "July", "August", "September", "October", "
1November", "December"\r\n ];\r\n\r\n if (this.monthLabel) {\r\n this.monthLabel.innerHTML = `${monthNames[month]} ${year} <span class="ux4g-icon-outlined ux4g-fs-18">keyboard_arrow_down</span>`;\r\n }\r\n\r\n const calendarHtml = `\r\n <div class="ux4g-date-picker-weekdays">\r\n <div class="ux4g-date-picker-weekday">Mo</div>\r\n <div class="ux4g-date-picker-weekday">Tu</div>\r\n <div class="ux4g-date-picker-weekday">We</div>\r\n <div class="ux4g-date-picker-weekday">Th</div>\r\n <div class="ux4g-date-picker-weekday">Fr</div>\r\n <div class="ux4g-date-picker-weekday">Sa</div>\r\n <div class="ux4g-date-picker-weekday">Su</div>\r\n </div>\r\n <div class="ux4g-date-picker-grid"></div>\r\n `;\r\n\r\n const calendarContainer = this.container.querySelector(\'.ux4g-date-picker-calendar\');\r\n calendarContainer.innerHTML = calendarHtml;\r\n this.calendarGrid = calendarContainer.querySelector(\'.ux4g-date-picker-grid\');\r\n\r\n const firstDayOfMonth = new Date(year, month, 1).getDay();\r\n const daysInMonth = new Date(year, month + 1, 0).getDate();\r\n let startDay = firstDayOfMonth === 0 ? 6 : firstDayOfMonth - 1;\r\n const prevMonthLastDay = new Date(year, month, 0).getDate();\r\n\r\n let html = \'\';\r\n for (let i = startDay - 1; i >= 0; i--) {\r\n html += `<div class="ux4g-date-picker-day is-muted">${prevMonthLastDay - i}</div>`;\r\n }\r\n\r\n for (let i = 1; i <= daysInMonth; i++) {\r\n const date = new Date(year, month, i);\r\n const isToday = date.toDateString() === this.currentDate.toDateString();\r\n const isSelected = this.tempSelectedDate && date.toDateString() === this.tempSelectedDate.toDateString();\r\n\r\n let classes = \'ux4g-date-picker-day\';\r\n if (isToday) classes += \' is-today\';\r\n if (isSelected) classes += \' is-selected\';\r\n\r\n html += `<div class="${classes}" data-date="${i}" tabindex="0">${i}</div>`;\r\n }\r\n\r\n const totalCells = 42;\r\n const remainingCells = totalCells - (startDay + daysInMonth);\r\n for (let i = 1; i <= remainingCells; i++) {\r\n html += `<div class="ux4g-date-picker-day is-muted">${i}</div>`;\r\n }\r\n\r\n this.calendarGrid.innerHTML = html;\r\n this.calendarGrid.querySelectorAll(\'.ux4g-date-picker-day:not(.is-muted)\').forEach(dayEl => {\r\n makeKeyboardClickable(dayEl);\r\n dayEl.addEventListener(\'click\', (e) => {\r\n e.stopPropagation();\r\n const day = e.target.dataset.date;\r\n this.selectDate(new Date(year, month, day));\r\n });\r\n });\r\n\r\n if (this.confirmBtn) {\r\n this.confirmBtn.innerHTML = \'Confirm\';\r\n this.confirmBtn.disabled = !this.tempSelectedDate;\r\n }\r\n }\r\n\r\n renderYearMonthSelection() {\r\n const currentYear = this.viewDate.getFullYear();\r\n const startYear = Math.floor(currentYear / 8) * 8;\r\n const endYear = startYear + 7;\r\n\r\n if (this.monthLabel) {\r\n this.monthLabel.innerHTML = `${startYear}-${endYear} <span class="ux4g-icon-outlined ux4g-fs-18">keyboard_arrow_down</span>`;\r\n }\r\n\r\n let html = \'<div class="ux4g-date-picker-selection-view">\';\r\n html += \'<div class="ux4g-date-picker-year-grid">\';\r\n for (let y = startYear; y <= endYear; y++) {\r\n const isSelected = y === this.viewDate.getFullYear();\r\n html += `<div class="ux4g-date-picker-year-item ${isSelected ? \'is-selected\' : \'\'}" data-year="${y}" tabindex="0">${y}</div>`;\r\n }\r\n html += \'</div>\';\r\n\r\n const monthNamesShort = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];\r\n html += \'<div class="ux4g-date-picker-month-grid">\';\r\n monthNamesShort.forEach((m, i) => {\r\n const isSelected = i === this.viewDate.getMonth();\r\n html += `<div class="ux4g-date-picker-month-item ${isSelected ? \'is-selected\' : \'\'}" data-month="${i}" tabindex="0">${m}</div>`;\r\n });\r\n html += \'</div></div>\';\r\n\r\n const calendarContainer = this.container.querySelector(\'.ux4g-date-picker-calendar\');\r\n calendarContainer.innerHTML = html;\r\n\r\n calendarContainer.querySelectorAll(\'.ux4g-date-picker-year-item\').forEach(el => {\r\n makeKeyboardClickable(el);\r\n el.addEventListener(\'click\', (e) => {\r\n e.stopPropagation();\r\n this.viewDate.setFullYear(parseInt(e.target.dataset.year));\r\n this.renderYearMonthSelection();\r\n setTimeout(() => {\r\n const selectedMonth = this.container.querySelector(\'.ux4g-date-picker-month-item.is-selected\');\r\n if (selectedMonth) selectedMonth.focus();\r\n }, 0);\r\n });\r\n });\r\n\r\n calendarContainer.querySelectorAll(\'.ux4g-date-picker-month-item\').forEach(el => {\r\n makeKeyboardClickable(el);\r\n el.addEventListener(\'click\', (e) => {\r\n e.stopPropagation();\r\n this.viewDate.setMonth(parseInt(e.target.dataset.month));\r\n this.renderYearMonthSelection();\r\n if (this.confirmBtn) setTimeout(() => this.confirmBtn.focus(), 0);\r\n });\r\n });\r\n\r\n if (this.confirmBtn) {\r\n this.confirmBtn.innerHTML = \'Select date\';\r\n this.confirmBtn.disabled = false;\r\n }\r\n }\r\n\r\n selectDate(date) {\r\n this.tempSelectedDate = date;\r\n this.render();\r\n if (this.confirmBtn && !this.confirmBtn.disabled) {\r\n setTimeout(() => this.confirmBtn.focus(), 0);\r\n }\r\n }\r\n }\r\n\r\n class RangeDatePicker {\r\n constructor(container) {\r\n this.container = container;\r\n this.inputs = container.querySelectorAll(\'.ux4g-date-picker-input\');\r\n this.dropdown = container.querySelector(\'.ux4g-date-picker-dropdown\');\r\n this.calendarGrid = container.querySelector(\'.ux4g-date-picker-grid\');\r\n this.monthLabel = container.querySelector(\'.ux4g-date-picker-current\');\r\n\r\n const navBtns = container.querySelectorAll(\'.ux4g-date-picker-nav-btn\');\r\n this.prevBtn = navBtns[0];\r\n this.nextBtn = navBtns[1];\r\n\r\n this.confirmBtn = container.querySelector(\'.ux4g-btn-primary\');\r\n this.cancelBtn = container.querySelector(\'.ux4g-btn-outline-neutral\');\r\n\r\n this.currentDate = new Date();\r\n this.viewDate = new Date(this.currentDate.getFullYear(), this.currentDate.getMonth(), 1);\r\n this.startDate = null;\r\n this.endDate = null;\r\n this.tempStartDate = null;\r\n this.tempEndDate = null;\r\n this.selectingEnd = false;\r\n this.isSelectingYearMonth = false;\r\n\r\n this._init();\r\n }\r\n\r\n _init() {\r\n if (!this.inputs.length || !this.dropdown) return;
1\r\n\r\n this.inputs.forEach(input => {\r\n // Keyboard accessibility\r\n input.addEventListener(\'keydown\', (e) => {\r\n if (e.key === \'Enter\' || e.key === \' \') {\r\n e.preventDefault();\r\n this.open();\r\n }\r\n });\r\n\r\n input.addEventListener(\'focus\', (e) => {\r\n this.open();\r\n });\r\n\r\n input.addEventListener(\'click\', (e) => {\r\n e.stopPropagation();\r\n this.open();\r\n });\r\n });\r\n\r\n if (this.prevBtn) {\r\n makeKeyboardClickable(this.prevBtn);\r\n this.prevBtn.addEventListener(\'click\', (e) => {\r\n e.stopPropagation();\r\n if (this.isSelectingYearMonth) {\r\n this.changeYearRange(-8);\r\n } else {\r\n this.changeMonth(-1);\r\n }\r\n });\r\n }\r\n\r\n if (this.nextBtn) {\r\n makeKeyboardClickable(this.nextBtn);\r\n this.nextBtn.addEventListener(\'click\', (e) => {\r\n e.stopPropagation();\r\n if (this.isSelectingYearMonth) {\r\n this.changeYearRange(8);\r\n } else {\r\n this.changeMonth(1);\r\n }\r\n });\r\n }\r\n\r\n if (this.monthLabel) {\r\n makeKeyboardClickable(this.monthLabel);\r\n this.monthLabel.addEventListener(\'click\', (e) => {\r\n e.stopPropagation();\r\n this.isSelectingYearMonth = !this.isSelectingYearMonth;\r\n this.render();\r\n setTimeout(() => {\r\n if (this.isSelectingYearMonth) {\r\n const calendarContainer = this.container.querySelector(\'.ux4g-date-picker-calendar\');\r\n let el = calendarContainer.querySelector(\'.ux4g-date-picker-year-item.is-selected\') || calendarContainer.querySelector(\'.ux4g-date-picker-year-item\');\r\n if (el) el.focus();\r\n } else {\r\n let el = this.calendarGrid.querySelector(\'.is-range-start\') || this.calendarGrid.querySelector(\'.is-selected\') || this.calendarGrid.querySelector(\'.is-today\') || this.calendarGrid.querySelector(\'.ux4g-date-picker-day:not(.is-muted)\');\r\n if (el) el.focus();\r\n }\r\n }, 0);\r\n });\r\n }\r\n\r\n if (this.confirmBtn) {\r\n makeKeyboardClickable(this.confirmBtn);\r\n this.confirmBtn.addEventListener(\'click\', (e) => {\r\n e.stopPropagation();\r\n if (this.isSelectingYearMonth) {\r\n this.isSelectingYearMonth = false;\r\n this.render();\r\n setTimeout(() => {\r\n let el = this.calendarGrid.querySelector(\'.is-range-start\') || this.calendarGrid.querySelector(\'.is-selected\') || this.calendarGrid.querySelector(\'.is-today\') || this.calendarGrid.querySelector(\'.ux4g-date-picker-day:not(.is-muted)\');\r\n if (el) el.focus();\r\n }, 0);\r\n } else {\r\n this.confirmSelection();\r\n }\r\n });\r\n }\r\n\r\n if (this.cancelBtn) {\r\n this.cancelBtn.addEventListener(\'click\', (e) => {\r\n e.stopPropagation();\r\n this.cancelSelection();\r\n });\r\n }\r\n\r\n document.addEventListener(\'click\', (e) => {\r\n if (!this.container.contains(e.target) && !getBackdrop().contains(e.target)) {\r\n this.close();\r\n }\r\n });\r\n\r\n getBackdrop().addEventListener(\'click\', () => {\r\n this.close();\r\n });\r\n\r\n this.render();\r\n }\r\n\r\n open() {\r\n if (this.dropdown) {\r\n this.tempStartDate = this.startDate ? new Date(this.startDate) : null;\r\n this.tempEndDate = this.endDate ? new Date(this.endDate) : null;\r\n this.selectingEnd = this.tempStartDate && !this.tempEndDate;\r\n this.isSelectingYearMonth = false;\r\n this.dropdown.classList.add(\'is-open\');\r\n if (isMobile()) {\r\n getBackdrop().classList.add(\'is-active\');\r\n document.body.style.overflow = \'hidden\';\r\n } else {\r\n adjustDropdownPosition(this.dropdown, this.container);\r\n }\r\n this.render();\r\n }\r\n }\r\n\r\n close() {\r\n if (this.dropdown) {\r\n this.dropdown.classList.remove(\'is-open\');\r\n resetDropdownPosition(this.dropdown);\r\n getBackdrop().classList.remove(\'is-active\');\r\n document.body.style.overflow = \'\';\r\n }\r\n }\r\n\r\n confirmSelection() {\r\n this.startDate = this.tempStartDate ? new Date(this.tempStartDate) : null;\r\n this.endDate = this.tempEndDate ? new Date(this.tempEndDate) : null;\r\n this.updateInputs();\r\n this.close();\r\n }\r\n\r\n cancelSelection() {\r\n this.tempStartDate = this.startDate ? new Date(this.tempStartDate) : null;\r\n this.tempEndDate = this.endDate ? new Date(this.endDate) : null;\r\n this.close();\r\n }\r\n\r\n changeMonth(delta) {\r\n this.viewDate.setMonth(this.viewDate.getMonth() + delta);\r\n this.render();\r\n }\r\n\r\n changeYearRange(delta) {\r\n this.viewDate.setFullYear(this.viewDate.getFullYear() + delta);\r\n this.render();\r\n }\r\n\r\n render() {\r\n if (this.isSelectingYearMonth) {\r\n this.renderYearMonthSelection();\r\n }
1 else {\r\n this.renderCalendar();\r\n }\r\n }\r\n\r\n renderCalendar() {\r\n const year = this.viewDate.getFullYear();\r\n const month = this.viewDate.getMonth();\r\n const monthNames = ["January", "February", "March", "April", "May", "June",\r\n "July", "August", "September", "October", "November", "December"\r\n ];\r\n\r\n if (this.monthLabel) {\r\n this.monthLabel.innerHTML = `${monthNames[month]} ${year} <span class="ux4g-icon-outlined ux4g-fs-18">keyboard_arrow_down</span>`;\r\n }\r\n\r\n const calendarHtml = `\r\n <div class="ux4g-date-picker-weekdays">\r\n <div class="ux4g-date-picker-weekday">Mo</div>\r\n <div class="ux4g-date-picker-weekday">Tu</div>\r\n <div class="ux4g-date-picker-weekday">We</div>\r\n <div class="ux4g-date-picker-weekday">Th</div>\r\n <div class="ux4g-date-picker-weekday">Fr</div>\r\n <div class="ux4g-date-picker-weekday">Sa</div>\r\n <div class="ux4g-date-picker-weekday">Su</div>\r\n </div>\r\n <div class="ux4g-date-picker-grid"></div>\r\n `;\r\n\r\n const calendarContainer = this.container.querySelector(\'.ux4g-date-picker-calendar\');\r\n calendarContainer.innerHTML = calendarHtml;\r\n this.calendarGrid = calendarContainer.querySelector(\'.ux4g-date-picker-grid\');\r\n\r\n const firstDayOfMonth = new Date(year, month, 1).getDay();\r\n const daysInMonth = new Date(year, month + 1, 0).getDate();\r\n let startDay = firstDayOfMonth === 0 ? 6 : firstDayOfMonth - 1;\r\n const prevMonthLastDay = new Date(year, month, 0).getDate();\r\n\r\n let html = \'\';\r\n for (let i = startDay - 1; i >= 0; i--) {\r\n html += `<div class="ux4g-date-picker-day is-muted">${prevMonthLastDay - i}</div>`;\r\n }\r\n\r\n for (let i = 1; i <= daysInMonth; i++) {\r\n const date = new Date(year, month, i);\r\n const isToday = date.toDateString() === this.currentDate.toDateString();\r\n\r\n let classes = \'ux4g-date-picker-day\';\r\n if (isToday) classes += \' is-today\';\r\n\r\n if (this.tempStartDate && date.toDateString() === this.tempStartDate.toDateString()) {\r\n classes += \' is-selected is-range-start\';\r\n } else if (this.tempEndDate && date.toDateString() === this.tempEndDate.toDateString()) {\r\n classes += \' is-selected is-range-end\';\r\n } else if (this.tempStartDate && this.tempEndDate && date > this.tempStartDate && date < this.tempEndDate) {\r\n classes += \' is-in-range\';\r\n }\r\n\r\n html += `<div class="${classes}" data-date="${i}" tabindex="0">${i}</div>`;\r\n }\r\n\r\n const totalCells = 42;\r\n const remainingCells = totalCells - (startDay + daysInMonth);\r\n for (let i = 1; i <= remainingCells; i++) {\r\n html += `<div class="ux4g-date-picker-day is-muted">${i}</div>`;\r\n }\r\n\r\n this.calendarGrid.innerHTML = html;\r\n this.calendarGrid.querySelectorAll(\'.ux4g-date-picker-day:not(.is-muted)\').forEach(dayEl => {\r\n makeKeyboardClickable(dayEl);\r\n dayEl.addEventListener(\'click\', (e) => {\r\n e.stopPropagation();\r\n const day = e.target.dataset.date;\r\n this.handleDateSelection(new Date(year, month, day));\r\n });\r\n });\r\n\r\n if (this.confirmBtn) {\r\n this.confirmBtn.innerHTML = \'Confirm\';\r\n this.confirmBtn.disabled = !this.tempStartDate || !this.tempEndDate;\r\n }\r\n }\r\n\r\n renderYearMonthSelection() {\r\n const currentYear = this.viewDate.getFullYear();\r\n const startYear = Math.floor(currentYear / 8) * 8;\r\n const endYear = startYear + 7;\r\n\r\n if (this.monthLabel) {\r\n this.monthLabel.innerHTML = `${startYear}-${endYear} <span class="ux4g-icon-outlined ux4g-fs-18">keyboard_arrow_down</span>`;\r\n }\r\n\r\n let html = \'<div class="ux4g-date-picker-selection-view">\';\r\n html += \'<div class="ux4g-date-picker-year-grid">\';\r\n for (let y = startYear; y <= endYear; y++) {\r\n const isSelected = y === this.viewDate.getFullYear();\r\n html += `<div class="ux4g-date-picker-year-item ${isSelected ? \'is-selected\' : \'\'}" data-year="${y}" tabindex="0">${y}</div>`;\r\n }\r\n html += \'</div>\';\r\n\r\n const monthNamesShort = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];\r\n html += \'<div class="ux4g-date-picker-month-grid">\';\r\n monthNamesShort.forEach((m, i) => {\r\n const isSelected = i === this.viewDate.getMonth();\r\n html += `<div class="ux4g-date-picker-month-item ${isSelected ? \'is-selected\' : \'\'}" data-month="${i}" tabindex="0">${m}</div>`;\r\n });\r\n html += \'</div></div>\';\r\n\r\n const calendarContainer = this.container.querySelector(\'.ux4g-date-picker-calendar\');\r\n calendarContainer.innerHTML = html;\r\n\r\n calendarContainer.querySelectorAll(\'.ux4g-date-picker-year-item\').forEach(el => {\r\n makeKeyboardClickable(el);\r\n el.addEventListener(\'click\', (e) => {\r\n e.stopPropagation();\r\n this.viewDate.setFullYear(parseInt(e.target.dataset.year));\r\n this.renderYearMonthSelection();\r\n setTimeout(() => {\r\n const selectedMonth = this.container.querySelector(\'.ux4g-date-picker-month-item.is-selected\');\r\n if (selectedMonth) selectedMonth.focus();\r\n }, 0);\r\n });\r\n });\r\n\r\n calendarContainer.querySelectorAll(\'.ux4g-date-picker-month-item\').forEach(el => {\r\n makeKeyboardClickable(el);\r\n el.addEventListener(\'click\', (e) => {\r\n e.stopPropagation();\r\n this.viewDate.setMonth(parseInt(e.target.dataset.month));\r\n this.renderYearMonthSelection();\r\n if (this.confirmBtn) setTimeout(() => this.confirmBtn.focus(), 0);\r\n });\r\n });\r\n\r\n if (this.confirmBtn) {\r\n this.confirmBtn.innerHTML = \'Select date\';\r\n this.confirmBtn.disabled = false;\r\n }\r\n }\r\n\r\n handleDateSelection(date) {\r\n if (!this.tempStartDate || (this.tempStartDate && this.tempEndDate)) {\r\n this.tempStartDate = date;\r\n this.tempEndDate = null;\r\n this.selectingEnd = true;\r\n } else if (this.selectingEnd) {\r\n if (date < this.tempStartDate) {\r\n this.tempEndDate = this.tempStartDate;\r\n this.tempStartDate = date;\r\n } else {\r\n this.tempEndDate = date;\r\n }\r\n this.selectingEnd = false;\r\n }\r\n this.render();\r\n if (!this.selectingEnd && this.confirmBtn && !this.confirmBtn.disabled) {\r\n setTimeout(() => this.confirmBtn.focus(), 0);\r\n } else if (this.selectingEnd) {\r\n setTimeout(() => {\r\n const selectedEl = this.calendarGrid.querySelector(\'.is-range-start\');\r\n if (selectedEl) selectedEl.focus();\r\n }, 0);\r\n }\r\n }\r\n\r\n updateInputs() {\r\n if (this.startDate) {\r\n const d = String(this.startDate.getDate()).padStart(2, \'0\');
1\r\n const m = String(this.startDate.getMonth() + 1).padStart(2, \'0\');\r\n const y = this.startDate.getFullYear();\r\n this.inputs[0].value = `${d}/${m}/${y}`;\r\n } else {\r\n this.inputs[0].value = \'\';\r\n }\r\n if (this.endDate) {\r\n const d = String(this.endDate.getDate()).padStart(2, \'0\');\r\n const m = String(this.endDate.getMonth() + 1).padStart(2, \'0\');\r\n const y = this.endDate.getFullYear();\r\n this.inputs[1].value = `${d}/${m}/${y}`;\r\n } else {\r\n this.inputs[1].value = \'\';\r\n }\r\n }\r\n }\r\n\r\n class TimePicker {\r\n constructor(container) {\r\n this.container = container;\r\n this.input = container.querySelector(\'.ux4g-time-picker-input\');\r\n this.dropdown = container.querySelector(\'.ux4g-time-picker-dropdown\');\r\n this.hhColumn = container.querySelector(\'[data-column="hh"]\');\r\n this.mmColumn = container.querySelector(\'[data-column="mm"]\');\r\n this.ampmBtns = container.querySelectorAll(\'.ux4g-time-picker-ampm-btn\');\r\n this.confirmBtn = container.querySelector(\'.ux4g-btn-primary\');\r\n this.cancelBtn = container.querySelector(\'.ux4g-btn-outline-neutral\');\r\n\r\n this.selectedHH = null;\r\n this.selectedMM = null;\r\n this.selectedAMPM = "PM";\r\n\r\n this.tempHH = null;\r\n this.tempMM = null;\r\n\r\n this._init();\r\n }\r\n\r\n _init() {\r\n if (!this.input || !this.dropdown) return;\r\n\r\n // Keyboard accessibility\r\n this.input.addEventListener(\'keydown\', (e) => {\r\n if (e.key === \'Enter\' || e.key === \' \') {\r\n e.preventDefault();\r\n this.open();\r\n }\r\n });\r\n\r\n this.input.addEventListener(\'focus\', (e) => {\r\n this.open();\r\n });\r\n\r\n this.input.addEventListener(\'click\', (e) => {\r\n e.stopPropagation();\r\n this.open();\r\n });\r\n\r\n this.ampmBtns.forEach(btn => {\r\n makeKeyboardClickable(btn);\r\n btn.addEventListener(\'click\', (e) => {\r\n e.stopPropagation();\r\n this.selectedAMPM = e.target.dataset.value;\r\n this.updateAMPMUI();\r\n });\r\n });\r\n\r\n if (this.confirmBtn) {\r\n makeKeyboardClickable(this.confirmBtn);\r\n this.confirmBtn.addEventListener(\'click\', (e) => {\r\n e.stopPropagation();\r\n if (!this.confirmBtn.disabled) {\r\n this.confirmSelection();\r\n }\r\n });\r\n }\r\n\r\n if (this.cancelBtn) {\r\n makeKeyboardClickable(this.cancelBtn);\r\n this.cancelBtn.addEventListener(\'click\', (e) => {\r\n e.stopPropagation();\r\n this.close();\r\n });\r\n }\r\n\r\n document.addEventListener(\'click\', (e) => {\r\n if (!this.container.contains(e.target) && !getBackdrop().contains(e.target)) {\r\n this.close();\r\n }\r\n });\r\n\r\n this.renderColumns();\r\n this.updateAMPMUI();\r\n this.validate();\r\n }\r\n\r\n open() {\r\n this.tempHH = this.selectedHH;\r\n this.tempMM = this.selectedMM;\r\n\r\n this.dropdown.classList.add(\'is-open\');\r\n if (isMobile()) {\r\n getBackdrop().classList.add(\'is-active\');\r\n document.body.style.overflow = \'hidden\';\r\n } else {\r\n adjustDropdownPosition(this.dropdown, this.container);\r\n }\r\n\r\n this.renderColumns();\r\n this.scrollToSelected();\r\n this.validate();\r\n }\r\n\r\n close() {\r\n this.dropdown.classList.remove(\'is-open\');\r\n resetDropdownPosition(this.dropdown);\r\n getBackdrop().classList.remove(\'is-active\');\r\n document.body.style.overflow = \'\';\r\n }\r\n\r\n validate() {\r\n if (this.confirmBtn) {\r\n this.confirmBtn.disabled = !(this.tempHH && this.tempMM);\r\n }\r\n }\r\n\r\n confirmSelection() {\r\n this.selectedHH = this.tempHH;\r\n this.selectedMM = this.tempMM;\r\n this.input.value = `${this.selectedHH} : ${this.selectedMM} ${this.selectedAMPM}`;\r\n this.close();\r\n }\r\n\r\n updateAMPMUI() {\r\n this.ampmBtns.forEach(btn => {\r\n btn.classList.toggle(\'is-active\', btn.dataset.value === this.selectedAMPM);\r\n });\r\n }\r\n\r\n renderColumns() {\r\n // Hours (1-12)\r\n let hhHtml = \'<div class="ux4g-time-picker-col-header">HH</div>\';\r\n for (let i = 1; i <= 12; i++) {\r\n const val = String(i).padStart(2, \'0\');\r\n const isSelected = val === this.tempHH;\r\n hhHtml += `<div class="ux4g-time-picker-item ${isSelected ? \'is-selected\' : \'\'}" data-value="${val}" tabindex="0">${val}</div>`;\r\n }\r\n this.hhColumn.innerHTML = hhHtml;\r\n\r\n // Minutes (0-55, step 5)\r\n let mmHtml = \'<div class="ux4g-time-picker-col-header">MM</div>\';\r\n for (let i = 0; i < 60; i += 5) {\r\n const val = String(i).padStart(2, \'0\');\r\n const isSelected = val === this.tempMM;\r\n mmHtml += `<div class="ux4g-time-picker-item ${isSelected ? \'is-selected\' : \'\'}" data-value="${val}" tabindex="0">${val}</div>`;\r\n }\r\n this.mmColumn.innerHTML = mmHtml;\r\n\r\n // Click events\r\n this.hhColumn.querySelectorAll(\'.ux4g-time-picker-item\').forEach(el => {\r\n makeKeyboardClickable(el);\r\n el.addEventListener(\'click\', (e) => {\r\n e.stopPropagation();\r\n this.tempHH = e.target.dataset.value;\r\n this.updateColumnSelection(this.hhColumn, this.tempHH);\r\n this.validate();\r\n if (!this.confirmBtn.disabled) {\r\n setTimeout(() => this.confirmBtn.focus(), 0);\r\n } else if (!this.tempMM) {\r\n const firstMM = this.mmColumn.querySelector(\'.ux4g-time-picker-item\');\r\n if (firstMM) setTimeout(() => firstMM.focus(), 0);\r\n }\r\n });\r\n });\r\n\r\n this.mmColumn.querySelectorAll(\'.ux4g-time-picker-item\').forEach(el => {\r\n makeKeyboardClickable(el);\r\n el.addEventListener(\'click\', (e) => {\r\n e.stopPropagation();\r\n this.tempMM = e.target.dataset.value;\r\n this.updateColumnSelection(this.mmColumn, this.tempMM);\r\n this.validate();\r\n if (!this.confirmBtn.disabled) {\r\n setTimeout(() => this.confirmBtn.focus(), 0);\r\n }\r\n });\r\n });\r\n }\r\n\r\n updateColumnSelection(column, value) {\r\n column.querySelectorAll(\'.ux4g-time-picker-item\').forEach(el => {\r\n el.classList.toggle(\'is-selected\', el.dataset.value === value);\r\n });\r\n }\r\n\r\n scrollToSelected() {\r\n const columns = [this.hhColumn, this.mmColumn];\r\n columns.forEach(col => {\r\n const selected = col.querySelector(\'.is-selected\');\r\n if (selected) {\r\n col.scrollTop = selected.offsetTop - col.offsetTop - 80;\r\n }\r\n });\r\n }\r\n }\r\n\r\n const init = () => {\r\n document.querySelectorAll(\'.ux4g-date-picker-container\').forEach(container => {\r\n if (!container.closest(\'.ux4g-date-range-picker\') && !initializedDatePickers.has(container)) {\r\n initializedDatePickers.add(container);\r\n new DatePicker(container);\r\n }\r\n });\r\n document.querySelectorAll(\'.ux4g-date-range-picker\').forEach(container => {\r\n if (!initializedRangeDatePickers.has(container)) {\r\n initializedRangeDatePickers.add(container);\r\n new RangeDatePicker(container);\r\n }\r\n });\r\n document.querySelectorAll(\'.ux4g-time-picker-container\').forEach(container => {\r\n if (!initializedTimePickers.has(container)) {\r\n initializedTimePickers.add(container);\r\n new TimePicker(container);\r\n }\r\n });\r\n };\r\n\r\n window.ux4gCustomInitList = window.ux4gCustomInitList || [];\r\n window.ux4gCustomInitList.push(init);\r\n\r\n if (document.readyState === \'loading\') document.addEventListener(\'DOMContentLoaded\', init);\r\n else init();\r\n\r\n global.DatePicker = DatePicker;\r\n global.RangeDatePicker = RangeDatePicker;\r\n global.TimePicker = TimePicker;\r\n global.ux4gDateTimePickerInit = init;\r\n\r\n})(window);\r\n\r\n\r\n/********************************* Time Slot JS ***********************************/\r\n\r\nclass TimeSlotCalendar {\r\n constructor(container) {\r\n this.container = container;\r\n this.calendarGrid = container.querySelector(\'.ux4g-time-slot-compact-grid\');\r\n this.monthLabel = container.querySelector(\'.ux4g-time-slot-compact-month\');\r\n this.slotTitle = container.querySelector(\'.ux4g-time-slot-compact-desktop-header\');\r\n this.slotsList = container.querySelector(\'.ux4g-time-slot-compact-list\');\r\n this.confirmBtn = container.querySelector(\'.ux4g-btn-primary\');\r\n \r\n const navBtns = container.querySelectorAll(\'.ux4g-btn-icon\');\r\n this.prevBtn = navBtns[0];\r\n this.nextBtn = navBtns[1];\r\n\r\n this.currentDate = new Date();\r\n this.viewDate = new Date(this.currentDate.getFullYear(), this.currentDate.getMonth(), 1);\r\n this.selectedDate = new Date(2026, 3, 23); // Default from design: April 23, 2026\r\n\r\n // Mock Data for Statuses\r\n this.holidays = [\'202
16-04-09\', \'2026-04-21\'];\r\n this.weeklyOffs = [0, 6]; // Sunday, Saturday\r\n this.noSlotsDates = [\'2026-04-08\', \'2026-04-13\'];\r\n\r\n this._init();\r\n }\r\n\r\n _init() {\r\n if (this.prevBtn) {\r\n this.prevBtn.addEventListener(\'click\', () => this.changeMonth(-1));\r\n }\r\n if (this.nextBtn) {\r\n this.nextBtn.addEventListener(\'click\', () => this.changeMonth(1));\r\n }\r\n\r\n // Cancel Button Reset Logic\r\n const cancelBtn = this.container.querySelector(\'.ux4g-btn-outline-neutral\');\r\n if (cancelBtn) {\r\n cancelBtn.addEventListener(\'click\', () => {\r\n this.selectedDate = null; // Reset selection\r\n this.render(); // Re-render calendar to clear highlights\r\n if (this.slotTitle) this.slotTitle.innerText = "Select a Date"; // Reset slot header\r\n this.resetSlots(); // Clear slot selection and disable confirm button\r\n });\r\n }\r\n\r\n this.render();\r\n }\r\n\r\n changeMonth(delta) {\r\n this.viewDate.setMonth(this.viewDate.getMonth() + delta);\r\n this.render();\r\n }\r\n\r\n render() {\r\n this.renderCalendar();\r\n }\r\n\r\n renderCalendar() {\r\n const year = this.viewDate.getFullYear();
1\r\n const month = this.viewDate.getMonth();\r\n const monthNames = ["January", "February", "March", "April", "May", "June",\r\n "July", "August", "September", "October", "November", "December"\r\n ];\r\n\r\n // Update Month Label\r\n if (this.monthLabel) {\r\n this.monthLabel.innerText = `${monthNames[month]} ${year}`;\r\n }\r\n\r\n const firstDayOfMonth = new Date(year, month, 1).getDay();\r\n const daysInMonth = new Date(year, month + 1, 0).getDate();\r\n \r\n // Adjust for Monday start\r\n let startDay = firstDayOfMonth === 0 ? 6 : firstDayOfMonth - 1;\r\n const prevMonthLastDay = new Date(year, month, 0).getDate();\r\n\r\n // Clear and rebuild grid\r\n this.calendarGrid.innerHTML = \'\';\r\n\r\n // Add Weekday Names\r\n const days = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"];\r\n days.forEach(day => {\r\n const dayEl = document.createElement(\'div\');\r\n dayEl.className = \'ux4g-time-slot-day-name\';\r\n dayEl.innerText = day;\r\n this.calendarGrid.appendChild(dayEl);\r\n });\r\n\r\n // Previous Month Days\r\n for (let i = startDay - 1; i >= 0; i--) {\r\n const dateEl = document.createElement(\'div\');\r\n dateEl.className = \'ux4g-time-slot-date muted\';\r\n dateEl.innerText = prevMonthLastDay - i;\r\n this.calendarGrid.appendChild(dateEl);\r\n }\r\n\r\n // Current Month Days\r\n const todayMidnight = new Date(this.currentDate.getFullYear(), this.currentDate.getMonth(), this.currentDate.getDate());\r\n for (let i = 1; i <= daysInMonth; i++) {\r\n const date = new Date(year, month, i);\r\n const dateStr = date.getFullYear() + \'-\' + String(date.getMonth() + 1).padStart(2, \'0\') + \'-\' + String(i).padStart(2, \'0\');\r\n const isToday = date.toDateString() === this.currentDate.toDateString();\r\n const isPast = date < todayMidnight;\r\n const isSelected = this.selectedDate && date.toDateString() === this.selectedDate.toDateString();\r\n const isHoliday = this.holidays.includes(dateStr);\r\n const isWeeklyOff = this.weeklyOffs.includes(date.getDay());\r\n const isNoSlots = this.noSlotsDates.includes(dateStr);\r\n\r\n const dateEl = document.createElement(\'div\');\r\n dateEl.className = \'ux4g-time-slot-date\';\r\n if (isPast) dateEl.classList.add(\'disabled\');\r\n if (isToday) dateEl.classList.add(\'today\');\r\n if (isSelected && !isPast) dateEl.classList.add(\'selected\');\r\n if (isHoliday) dateEl.classList.add(\'holiday\');\r\n if (isWeeklyOff) dateEl.classList.add(\'weekly-off\');\r\n if (isNoSlots) dateEl.classList.add(\'no-slots\');\r\n \r\n dateEl.innerText = i;\r\n dateEl.dataset.date = i;\r\n\r\n if (!isPast) {\r\n dateEl.addEventListener(\'click\', () => {\r\n this.selectedDate = new Date(year, month, i);\r\n this.render();\r\n this.updateSlotHeader();\r\n this.resetSlots();\r\n });\r\n }\r\n\r\n this.calendarGrid.appendChild(dateEl);\r\n }\r\n\r\n // Next Month Days\r\n const totalCells = 42 + 7; // Including header row\r\n const currentCells = this.calendarGrid.children.length;\r\n const remainingCells = totalCells - currentCells;\r\n for (let i = 1; i <= remainingCells; i++) {\r\n const dateEl = document.createElement(\'div\');\r\n dateEl.className = \'ux4g-time-slot-date muted\';\r\n dateEl.innerText = i;\r\n this.calendarGrid.appendChild(dateEl);\r\n }\r\n }\r\n\r\n updateSlotHeader() {\r\n if (this.slotTitle && this.selectedDate) {\r\n const day = this.selectedDate.getDate();\r\n const monthNames = ["January", "February", "March", "April", "May", "June",\r\n "July", "August", "September", "October", "
1November", "December"\r\n ];\r\n const month = monthNames[this.selectedDate.getMonth()];\r\n const year = this.selectedDate.getFullYear();\r\n this.slotTitle.innerText = `${day}${this.getOrdinal(day)} ${month} ${year}`;\r\n }\r\n }\r\n\r\n resetSlots() {\r\n // Reset slot selection and disable confirm button\r\n const slots = this.container.querySelectorAll(\'.ux4g-time-slot-compact-slot-item:not(.disabled)\');\r\n slots.forEach(s => {\r\n s.classList.remove(\'selected\');\r\n s.style.backgroundColor = \'\';\r\n });\r\n if (this.confirmBtn) this.confirmBtn.setAttribute(\'disabled\', \'true\');\r\n \r\n // Re-attach slot selection listeners\r\n slots.forEach(slot => {\r\n slot.addEventListener(\'click\', () => {\r\n slots.forEach(s => {\r\n s.classList.remove(\'selected\');\r\n s.style.backgroundColor = \'\';\r\n });\r\n slot.classList.add(\'selected\');\r\n if (this.confirmBtn) this.confirmBtn.removeAttribute(\'disabled\');\r\n });\r\n });\r\n }\r\n\r\n getOrdinal(n) {\r\n const s = ["th", "st", "nd", "rd"];\r\n const v = n % 100;\r\n return (s[(v - 20) % 10] || s[v] || s[0]);\r\n }\r\n}\r\n\r\n// Initialize on Load\r\ndocument.addEventListener(\'DOMContentLoaded\', () => {\r\n // Initialize Compact Calendar\r\n const compactContainers = document.querySelectorAll(\'.ux4g-time-slot-compact-container, #ux4g-time-slot, [id^="ux4g-time-slot-compact"]\');\r\n compactContainers.forEach(container => {\r\n new TimeSlotCalendar(container);\r\n });\r\n\r\n // Initialize Weekly Grid Selection\r\n const weeklyGrids = document.querySelectorAll(\'.ux4g-time-slot-weekly-grid, [id^="ux4g-time-slot-weekly"]\');\r\n weeklyGrids.forEach(weeklyGrid => {\r\n const cells = weeklyGrid.querySelectorAll(\'.ux4g-time-slot-cell.available, .ux4g-time-slot-cell.limited\');\r\n const confirmBtn = weeklyGrid.parentElement.querySelector(\'.ux4g-time-slot-weekly-actions .ux4g-btn-primary\');\r\n const cancelBtn = weeklyGrid.parentElement.querySelector(\'.ux4g-time-slot-weekly-actions .ux4g-btn-outline-neutral\');\r\n\r\n cells.forEach(cell => {\r\n // Store original content to restore later\r\n const originalHTML = cell.innerHTML;\r\n\r\n cell.addEventListener(\'click\', () => {\r\n if (cell.classList.contains(\'selected\')) return;\r\n\r\n // 1. Restore all other cells to their original state\r\n cells.forEach(c => {\r\n if (c.classList.contains(\'selected\')) {\r\n c.classList.remove(\'selected\');\r\n if (c._originalContent) {\r\n c.innerHTML = c._originalContent;\r\n }\r\n }\r\n });\r\n\r\n // 2. Select this cell\r\n cell.classList.add(\'selected\');\r\n cell._originalContent = originalHTML;\r\n cell.innerHTML = `<span class="ux4g-icon-filled">check_circle</span> Selected`;\r\n\r\n // 3. Enable Confirm Button\r\n if (confirmBtn) confirmBtn.removeAttribute(\'disabled\');\r\n });\r\n });\r\n\r\n // Cancel Button logic for Weekly Grid\r\n if (cancelBtn) {\r\n cancelBtn.addEventListener(\'click\', () => {\r\n cells.forEach(c => {\r\n if (c.classList.contains(\'selected\')) {\r\n c.classList.remove(\'selected\');\r\n if (c._originalContent) {\r\n c.innerHTML = c._originalContent;\r\n }\r\n }\r\n });\r\n if (confirmBtn) confirmBtn.setAttribute(\'disabled\', \'true\');\r\n });\r\n }\r\n\r\n // Initialize Weekly Grid Mobile Navigation (Dynamic approach)\r\n const container = weeklyGrid.closest(\'.ux4g-time-slot-weekly-container\');\r\n if (container) {\r\n const mobileNav = container.querySelector(\'.ux4g-time-slot-mobile-nav\');\r\n if (mobileNav) {\r\n const mobileDateLabel = mobileNav.querySelector(\'.ux4g-time-slot-mobile-date\');\r\n const navBtns = mobileNav.querySelectorAll(\'.ux4g-btn-icon\');\r\n const prevBtn = navBtns[0];\r\n const nextBtn = navBtns[1];\r\n\r\n // 1. Dynamically assign data-day attributes to cells based on grid position\r\n // Grid has 8 columns: Time, Mon, Tue, Wed, Thu, Fri, Sat, Sun\r\n const children = weeklyGrid.children;\r\n for (let i = 0; i < children.length; i++) {\r\n const colIndex = i % 8;\r\n if (colIndex > 0) { // Skip Time column\r\n children[i].setAttribute(\'data-day\', colIndex - 1);\r\n }\r\n }\r\n\r\n // 2. Navigation Logic\r\n let activeDay = 0;\r\n weeklyGrid.setAttribute(\'data-active-day\', activeDay);\r\n\r\n const daysData = [\r\n { day: "Mon 14 Apr", status: "Today" },\r\n { day: "Tue 15 Apr", status: "" },\r\n { day: "Wed 16 Apr", status: "" },\r\n { day: "Thu 17 Apr", status: "Public Holiday" },\r\n { day: "Fri 18 Apr", status: "" },\r\n { day: "Sat 19 Apr", status: "Weekly off" },\r\n { day: "Sun 20 Apr", status: "Weekly off" }\r\n ];\r\n\r\n const updateMobileNav = (index) => {\r\n weeklyGrid.setAttribute(\'data-active-day\', index);\r\n const data = daysData[index];\r\n if (mobileDateLabel) {\r\n mobileDateLabel.innerHTML = `\r\n <strong>${data.day}</strong>
1\r\n ${data.status ? `<span class="${data.status === \'Today\' ? \'ux4g-text-success-600\' : \'ux4g-text-neutral-secondary\'}">${data.status}</span>` : \'\'}\r\n `;\r\n }\r\n };\r\n\r\n if (prevBtn) {\r\n prevBtn.addEventListener(\'click\', () => {\r\n activeDay = (activeDay > 0) ? activeDay - 1 : 6;\r\n updateMobileNav(activeDay);\r\n });\r\n }\r\n\r\n if (nextBtn) {\r\n nextBtn.addEventListener(\'click\', () => {\r\n activeDay = (activeDay < 6) ? activeDay + 1 : 0;\r\n updateMobileNav(activeDay);\r\n });\r\n }\r\n }\r\n }\r\n });\r\n});\r\n\r\n\r\n\r\n/********************************* Result list JS ***********************************/\r\n\r\n// Accordion Toggle\r\nwindow.ux4gCustomInitList = window.ux4gCustomInitList || []; window.ux4gCustomInitList.push(() => {\r\n if (window.ux4gResultListAccordionInitialized) return;\r\n window.ux4gResultListAccordionInitialized = true;\r\n \r\n document.addEventListener(\'click\', (e) => {\r\n const toggle = e.target.closest(\'.ux4g-result-list-accordion-toggle\');\r\n if (!toggle) return;\r\n\r\n let content = null;\r\n \r\n // 1. Support ID-based target via aria-controls or data-ux-target\r\n const targetId = toggle.getAttribute(\'aria-controls\') || toggle.getAttribute(\'data-ux-target\');\r\n if (targetId) {\r\n const selector = targetId.startsWith(\'#\') ? targetId : `#${targetId}`;\r\n content = document.querySelector(selector);\r\n }\r\n \r\n // 2. Fallback to structural targeting\r\n if (!content) {\r\n const card = toggle.closest(\'.ux4g-result-list\');\r\n if (card) {\r\n content = card.querySelector(\'.ux4g-result-list-content\');\r\n }\r\n }\r\n\r\n if (!content) return;\r\n\r\n const isExpanded = toggle.getAttribute(\'aria-expanded\') === \'true\';\r\n\r\n if (isExpanded) {\r\n toggle.setAttribute(\'aria-expanded\', \'false\');\r\n toggle.innerText = \'expand_more\';\r\n content.style.display = \'none\';\r\n } else {\r\n toggle.setAttribute(\'aria-expanded\', \'true\');\r\n toggle.innerText = \'expand_less\';\r\n content.style.display = \'\';\r\n }\r\n });\r\n});\r\n\r\n\r\n\r\n/********************************* Mega menu category list JS ***********************************/\r\n\r\nwindow.ux4gCustomInitList = window.ux4gCustomInitList || []; window.ux4gCustomInitList.push(() => {\r\n const categoryItems = document.querySelectorAll(\'.ux4g-mega-menu-category-item\');\r\n const contentBlocks = document.querySelectorAll(\'.ux4g-mega-menu-content\');\r\n\r\n if (!categoryItems.length || !contentBlocks.length) return;\r\n\r\n categoryItems.forEach((item, index) => {\r\n item.addEventListener(\'click\', (e) => {\r\n e.preventDefault();\r\n\r\n // Remove active class from all categories\r\n categoryItems.forEach(cat => cat.classList.remove(\'ux4g-mega-menu-category-item--active\'));\r\n // Add active class to clicked category\r\n item.classList.add(\'ux4g-mega-menu-category-item--active\');\r\n\r\n // Hide all content blocks\r\n contentBlocks.forEach(block => block.classList.remove(\'ux4g-mega-menu-content--active\'));\r\n\r\n // Show the corresponding content block by ID matching category-1, category-2, etc.\r\n const targetId = `category-${index + 1}`;\r\n const targetBlock = document.getElementById(targetId);\r\n\r\n if (targetBlock) {\r\n targetBlock.classList.add(\'ux4g-mega-menu-content--active\');\r\n }\r\n });\r\n });\r\n});\r\n\r\n/* ========================================================= checkbox js ========================================================= */\r\nwindow.ux4gCustomInitList = window.ux4gCustomInitList || []; window.ux4gCustomInitList.push(() => {\r\n\r\n // 1. Standalone indeterminate checkboxes â add data-ux4g-indeterminate attribute to set on init\r\n document.querySelectorAll(\'.ux4g-checkbox-input[data-ux4g-indeterminate]\').forEach(input => {\r\n input.indeterminate = true;\r\n });\r\n\r\n // 2. Parent-child checkbox groups â wrap the group in data-ux4g-parent-child.\r\n // Mark the select-all input with data-ux4g-select-all.\r\n // Mark each child input with data-ux4g-child.\r\n document.querySelectorAll(\'[data-ux4g-parent-child]\').forEach(container => {\r\n const parentInput = container.querySelector(\'.ux4g-checkbox-input[data-ux4g-select-all]\');\r\n const childInputs = Array.from(container.querySelectorAll(\'.ux4g-checkbox-input[data-ux4g-child]\'));\r\n\r\n if (!parentInput || !childInputs.length) return;\r\n\r\n function updateParent() {\r\n const checkedCount = childInputs.filter(cb => cb.checked).length;\r\n if (checkedCount === 0) {\r\n parentInput.checked = false;\r\n parentInput.indeterminate = false;\r\n } else if (checkedCount === childInputs.length) {\r\n parentInput.checked = true;\r\n parentInput.indeterminate = false;\r\n } else {\r\n parentInput.checked = false;\r\n parentInput.indeterminate = true;\r\n }\r\n }\r\n\r\n // Use click (not change) so we can read the children\'s state BEFORE any changes,\r\n // giving consistent "select all / unselect all" behaviour regardless of browser\r\n // handling of indeterminate â click â checked transitions.\r\n parentInput.addEventListener(\'click\', function () {\r\n const checkedCount = childInputs.filter(cb => cb.checked).length;\r\n const checkAll = checkedCount < childInputs.length;\r\n parentInput.checked = checkAll;\r\n parentInput.indeterminate = false;
1\r\n childInputs.forEach(cb => { cb.checked = checkAll; });\r\n });\r\n\r\n childInputs.forEach(cb => cb.addEventListener(\'change\', updateParent));\r\n updateParent();\r\n });\r\n});\r\n\r\n\r\n\r\n\r\n/* ========================================================= \r\n Aadhaar & PAN Validation Logic\r\n========================================================= */\r\nwindow.ux4gCustomInitList = window.ux4gCustomInitList || [];\r\nwindow.ux4gCustomInitList.push(() => {\r\n \r\n // Helper function to toggle error state\r\n const toggleErrorState = (input, isError) => {\r\n const container = input.closest(\'.ux4g-input-container\');\r\n if (!container) return;\r\n \r\n let helper = container.querySelector(\'.ux4g-input-helper\');\r\n\r\n if (isError) {\r\n container.classList.add(\'ux4g-input-error\');\r\n input.setAttribute(\'aria-invalid\', \'true\');\r\n if (helper) helper.style.display = \'flex\';\r\n } else {\r\n container.classList.remove(\'ux4g-input-error\');\r\n input.removeAttribute(\'aria-invalid\');\r\n if (helper) helper.style.display = \'none\';\r\n }\r\n };\r\n\r\n // ---------------------------------------------------------\r\n // Aadhaar Card Input Handling\r\n // ---------------------------------------------------------\r\n const handleAadhaar = (e) => {\r\n const input = e.target;\r\n if (!input.matches(\'input[id="aadhaarInput"], input[name="aadhaar"]\')) return;\r\n\r\n let val = input.value.replace(/[^\\d]/g, \'\').substring(0, 12);\r\n let formatted = \'\';\r\n for (let i = 0; i < val.length; i++) {\r\n if (i > 0 && i % 4 === 0) formatted += \' \';\r\n formatted += val[i];\r\n }\r\n if (input.value !== formatted) {\r\n input.value = formatted;\r\n }\r\n\r\n // Validate using HTML5 pattern attribute\r\n if (input.value.length === 0) {\r\n toggleErrorState(input, false);\r\n } else if (!input.checkValidity()) {\r\n toggleErrorState(input, true);\r\n } else {\r\n toggleErrorState(input, false);\r\n }\r\n };\r\n\r\n // ---------------------------------------------------------\r\n // PAN Card Input Handling\r\n // ---------------------------------------------------------\r\n const handlePan = (e) => {\r\n const input = e.target;\r\n if (!input.matches(\'input[id="panInput"], input[name="pan"]\')) return;\r\n\r\n let val = input.value.toUpperCase().replace(/[^A-Z0-9]/g, \'\').substring(0, 10);\r\n if (input.value !== val) {\r\n input.value = val;\r\n }\r\n\r\n // Validate using HTML5 pattern attribute\r\n if (input.value.length === 0) {\r\n toggleErrorState(input, false);\r\n } else if (!input.checkValidity()) {\r\n toggleErrorState(input, true);\r\n } else {\r\n toggleErrorState(input, false);\r\n }\r\n };\r\n\r\n // Use event delegation to handle dynamically created inputs (e.g. inside Playground)\r\n document.addEventListener(\'input\', (e) => {\r\n if (e.target && e.target.tagName === \'INPUT\') {\r\n handleAadhaar(e);\r\n handlePan(e);\r\n }\r\n });\r\n\r\n document.addEventListener(\'focusout\', (e) => {\r\n if (e.target && e.target.tagName === \'INPUT\') {\r\n handleAadhaar(e);\r\n handlePan(e);\r\n }\r\n });\r\n});\r\n\r\nwindow.ux4gCustomInitList = window.ux4gCustomInitList || []; window.ux4gCustomInitList.push(() => {\r\n const wrappers = document.querySelectorAll(\'.ux4g-consent-capture-wrapper\');\r\n \r\n wrappers.forEach(wrapper => {\r\n const scrollBox = wrapper.querySelector(\'.ux4g-declaration-content\');\r\n const hint = wrapper.querySelector(\'.ux4g-declaration-scroll-hint\');\r\n const checkbox = wrapper.querySelector(\'.ux4g-checkbox-input\');\r\n const nameInputGroup = wrapper.querySelector(\'.ux4g-input-container\');\r\n const nameInput = wrapper.querySelector(\'.ux4g-input-input\');\r\n const submitBtn = wrapper.querySelector(\'.ux4g-consent-decl-btn-wrap .ux4g-btn-primary\');\r\n\r\n if (!checkbox || !scrollBox || !submitBtn) return;\r\n\r\n let hasScrolledToBottom = false;\r\n\r\n function validateForm() {\r\n const isNameValid = nameInput ? nameInput.value.trim().length > 0 : true;\r\n const isChecked = checkbox.checked;\r\n\r\n if (hasScrolledToBottom && isNameValid && isChecked) {\r\n submitBtn.disabled = false;\r\n } else {\r\n submitBtn.disabled = true;\r\n }\r\n }\r\n\r\n scrollBox.addEventListener(\'scroll\', function() {\r\n if (Math.abs(this.scrollHeight - this.clientHeight - this.scrollTop) <= 10) {\r\n if (hint) {\r\n hint.style.opacity = \'0\';\r\n hint.style.pointerEvents = \'none\';\r\n }\r\n hasScrolledToBottom = true;\r\n validateForm();\r\n }\r\n });\r\n\r\n checkbox.addEventListener(\'change\', function() {\r\n if (nameInputGroup) {\r\n if (this.checked) {\r\n nameInputGroup.classList.remove(\'ux4g-d-none\');\r\n } else {\r\n nameInputGroup.classList.add(\'ux4g-d-none\');\r\n if (nameInput) nameInput.value = \'\';\r\n }\r\n }\r\n validateForm();\r\n });\r\n \r\n if (nameInput) {\r\n nameInput.addEventListener(\'input\', validateForm);\r\n }\r\n \r\n // Initial validation\r\n validateForm();\r\n });\r\n});\r\n\r\n/* ========================================================= alert js ========================================================= */\r\n\r\n// Single delegated click handler for alert close (Framework Agnostic)\r\ndocument.addEventListener(\'click\', (e) => {\r\n const closeBtn = e.target.closest(\'.ux4g-alert-close\');\r\n if (closeBtn) {\r\n const alert = closeBtn.closest(\'.ux4g-alert, .ux4g-context-alert\');\r\n if (alert) {\r\n alert.style.display = \'none\';\r\n }\r\n }\r\n});\r\n\r\n// Auto-dismiss logic using MutationObserver (Works perfectly with React/Angular)\r\nfunction initAutoDismissAlerts() {\r\n const setAutoDismiss = (alert) =>
1 {\r\n if (alert.dataset.ux4gDismissTimeout) return; // already set\r\n const time = parseInt(alert.getAttribute(\'data-auto-dismiss\'), 10);\r\n if (!isNaN(time) && time > 0) {\r\n const timerId = setTimeout(() => {\r\n alert.style.display = \'none\';\r\n }, time);\r\n alert.dataset.ux4gDismissTimeout = timerId;\r\n }\r\n };\r\n\r\n // 1. Check existing alerts on page\r\n document.querySelectorAll(\'.ux4g-alert[data-auto-dismiss], .ux4g-context-alert[data-auto-dismiss]\').forEach(setAutoDismiss);\r\n\r\n // 2. Watch for dynamically added alerts (React, Angular, etc.)\r\n const observer = new MutationObserver((mutations) => {\r\n mutations.forEach(mutation => {\r\n mutation.addedNodes.forEach(node => {\r\n if (node.nodeType === 1) { // Element node\r\n if (node.classList && (node.classList.contains(\'ux4g-alert\') || node.classList.contains(\'ux4g-context-alert\')) && node.hasAttribute(\'data-auto-dismiss\')) {\r\n setAutoDismiss(node);\r\n }\r\n // Also check children if a container was added\r\n const innerAlerts = node.querySelectorAll ? node.querySelectorAll(\'.ux4g-alert[data-auto-dismiss], .ux4g-context-alert[data-auto-dismiss]\') : [];\r\n innerAlerts.forEach(setAutoDismiss);\r\n }\r\n });\r\n });\r\n });\r\n\r\n observer.observe(document.body, { childList: true, subtree: true });\r\n}\r\n\r\nwindow.ux4gCustomInitList = window.ux4gCustomInitList || [];\r\nwindow.ux4gCustomInitList.push(initAutoDismissAlerts);\r\n\r\n\r\n/* ========================================================= reminder alert close js ========================================================= */\r\n// Delegated event listener for reminder alert close buttons so it works with dynamic frameworks (React, Angular, Next.js)\r\ndocument.addEventListener(\'click\', (e) => {\r\n const closeBtn = e.target.closest(\'.ux4g-reminder-alert .ux4g-alert-close\');\r\n if (closeBtn) {\r\n const alertBox = closeBtn.closest(\'.ux4g-reminder-alert\');\r\n if (alertBox) {\r\n alertBox.style.display = \'none\';\r\n }\r\n if (isError) {\r\n container.classList.add(\'ux4g-input-error\');\r\n input.setAttribute(\'aria-invalid\', \'true\');\r\n if (helper) helper.style.display = \'flex\';\r\n } else {\r\n container.classList.remove(\'ux4g-input-error\');\r\n input.removeAttribute(\'aria-invalid\');\r\n if (helper) helper.style.display = \'none\';\r\n }\r\n };\r\n\r\n // ---------------------------------------------------------\r\n // Aadhaar Card Input Handling\r\n // ---------------------------------------------------------\r\n const handleAadhaar = (e) => {\r\n const input = e.target;\r\n if (!input.matches(\'input[id="aadhaarInput"], input[name="aadhaar"]\')) return;\r\n\r\n let val = input.value.replace(/[^\\d]/g, \'\').substring(0, 12);\r\n let formatted = \'\';\r\n for (let i = 0; i < val.length; i++) {\r\n if (i > 0 && i % 4 === 0) formatted += \' \';\r\n formatted += val[i];\r\n }\r\n if (input.value !== formatted) {\r\n input.value = formatted;\r\n }\r\n\r\n // Validate using HTML5 pattern attribute\r\n if (input.value.length === 0) {\r\n toggleErrorState(input, false);\r\n } else if (!input.checkValidity()) {\r\n toggleErrorState(input, true);\r\n } else {\r\n toggleErrorState(input, false);\r\n }\r\n };\r\n\r\n // ---------------------------------------------------------\r\n // PAN Card Input Handling\r\n // ---------------------------------------------------------\r\n const handlePan = (e) => {\r\n const input = e.target;\r\n if (!input.matches(\'input[id="panInput"], input[name="pan"]\')) return;\r\n\r\n let val = input.value.toUpperCase().replace(/[^A-Z0-9]/g, \'\').substring(0, 10);\r\n if (input.value !== val) {\r\n input.value = val;\r\n }\r\n\r\n // Validate using HTML5 pattern attribute\r\n if (input.value.length === 0) {\r\n toggleErrorState(input, false);\r\n } else if (!input.checkValidity()) {\r\n toggleErrorState(input, true);\r\n } else {\r\n toggleErrorState(input, false);\r\n }\r\n };\r\n\r\n // Use event delegation to handle dynamically created inputs (e.g. inside Playground)\r\n document.addEventListener(\'input\', (e) => {\r\n if (e.target && e.target.tagName === \'INPUT\') {\r\n handleAadhaar(e);\r\n handlePan(e);\r\n }\r\n });
1\r\n\r\n document.addEventListener(\'focusout\', (e) => {\r\n if (e.target && e.target.tagName === \'INPUT\') {\r\n handleAadhaar(e);\r\n handlePan(e);\r\n }\r\n });\r\n});\r\n\r\nwindow.ux4gCustomInitList = window.ux4gCustomInitList || []; window.ux4gCustomInitList.push(() => {\r\n const wrappers = document.querySelectorAll(\'.ux4g-consent-capture-wrapper\');\r\n \r\n wrappers.forEach(wrapper => {\r\n const scrollBox = wrapper.querySelector(\'.ux4g-declaration-content\');\r\n const hint = wrapper.querySelector(\'.ux4g-declaration-scroll-hint\');\r\n const checkbox = wrapper.querySelector(\'.ux4g-checkbox-input\');\r\n const nameInputGroup = wrapper.querySelector(\'.ux4g-input-container\');\r\n const nameInput = wrapper.querySelector(\'.ux4g-input-input\');\r\n const submitBtn = wrapper.querySelector(\'.ux4g-consent-decl-btn-wrap .ux4g-btn-primary\');\r\n\r\n if (!checkbox || !scrollBox || !submitBtn) return;\r\n\r\n let hasScrolledToBottom = false;\r\n\r\n function validateForm() {\r\n const isNameValid = nameInput ? nameInput.value.trim().length > 0 : true;\r\n const isChecked = checkbox.checked;\r\n\r\n if (hasScrolledToBottom && isNameValid && isChecked) {\r\n submitBtn.disabled = false;\r\n } else {\r\n submitBtn.disabled = true;\r\n }\r\n }\r\n\r\n scrollBox.addEventListener(\'scroll\', function() {\r\n if (Math.abs(this.scrollHeight - this.clientHeight - this.scrollTop) <= 10) {\r\n if (hint) {\r\n hint.style.opacity = \'0\';\r\n hint.style.pointerEvents = \'none\';\r\n }\r\n hasScrolledToBottom = true;\r\n validateForm();\r\n }\r\n });\r\n\r\n checkbox.addEventListener(\'change\', function() {\r\n if (nameInputGroup) {\r\n if (this.checked) {\r\n nameInputGroup.classList.remove(\'ux4g-d-none\');\r\n } else {\r\n nameInputGroup.classList.add(\'ux4g-d-none\');\r\n if (nameInput) nameInput.value = \'\';\r\n }\r\n }\r\n validateForm();\r\n });\r\n \r\n if (nameInput) {\r\n nameInput.addEventListener(\'input\', validateForm);\r\n }\r\n \r\n // Initial validation\r\n validateForm();\r\n });\r\n});\r\n\r\n\r\n\r\n\r\n/* ========================================================= reminder alert close js ========================================================= */\r\n// Delegated event listener for reminder alert close buttons so it works with dynamic frameworks (React, Angular, Next.js)\r\ndocument.addEventListener(\'click\', (e) => {\r\n const closeBtn = e.target.closest(\'.ux4g-reminder-alert .ux4g-alert-close\');\r\n if (closeBtn) {\r\n const alertBox = closeBtn.closest(\'.ux4g-reminder-alert\');\r\n if (alertBox) {\r\n alertBox.style.display = \'none\';\r\n }\r\n }\r\n});\r\n\r\n\r\n/* ========================================================= list switch JS appended ========================================================= */\r\nwindow.ux4gCustomInitList = window.ux4gCustomInitList || []; window.ux4gCustomInitList.push(() => {\r\n document.addEventListener(\'click\', (e) => {\r\n const switchContent = e.target.closest(\'.ux4g-switch-content\');\r\n if (switchContent) {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n return;\r\n }\r\n\r\n const row = e.target.closest(\'.ux4g-list-item-row.ux4g-toggles, span.ux4g-list-item-row, .ux4g-list-item-row-fixed\');\r\n if (row && e.target.tagName !== \'INPUT\' && !e.target.closest(\'.ux4g-switch, .ux4g-checkbox, .ux4g-radio\')) {\r\n e.stopPropagation();\r\n }\r\n }, true);\r\n});\r\n/* ========================================================= toggle disable JS ========================================================= */\r\n// Generic script to enable/disable target elements based on checkbox state. Works with dynamic HTML, React, Angular, etc.\r\ndocument.addEventListener(\'change\', function(e) {\r\n if (e.target && e.target.hasAttribute(\'data-ux4g-toggle-disable\')) {\r\n const targetSelector = e.target.getAttribute(\'data-ux4g-toggle-disable\');
1\r\n if (targetSelector) {\r\n const targetEls = document.querySelectorAll(targetSelector);\r\n targetEls.forEach(el => {\r\n el.disabled = !e.target.checked;\r\n });\r\n }\r\n }\r\n});\r\n/* ========================================================= filter chip JS ========================================================= */\r\n// Generic script to toggle filter chips (active mode)\r\ndocument.addEventListener(\'click\', (e) => {\r\n const filterChip = e.target.closest(\'.ux4g-filter-chip-md, .ux4g-filter-chip-sm\');\r\n if (filterChip) {\r\n if (filterChip.hasAttribute(\'disabled\') || filterChip.classList.contains(\'ux4g-disabled\')) return;\r\n\r\n filterChip.classList.toggle(\'active\');\r\n }\r\n});\r\n\r\n// Execute all registered initialization functions (uses ux4g.js\'s safe init to prevent double-binding)\r\n// Initialize selection state in custom list dropdowns\r\nconst initCustomListDropdowns = () => {\r\n document.querySelectorAll(\'.ux4g-custom-list-dropdown\').forEach(dropdown => {\r\n const btn = dropdown.querySelector(\'.ux4g-custom-dropdown-btn\');\r\n const items = dropdown.querySelectorAll(\'.ux4g-custom-dropdown-item\');\r\n if (!btn || !items.length) return;\r\n\r\n const currentValue = btn.getAttribute(\'data-value\');\r\n const span = btn.querySelector(\'span:first-child\');\r\n const currentText = span ? span.textContent.trim() : \'\';\r\n\r\n items.forEach(item => {\r\n const itemVal = item.getAttribute(\'data-value\');\r\n const textEl = item.querySelector(\'.ux4g-custom-dropdown-text\');\r\n const itemText = textEl ? textEl.textContent.trim() : item.textContent.trim();\r\n \r\n const isSelected = (currentValue && itemVal === currentValue) || (!currentValue && currentText && itemText === currentText);\r\n if (isSelected) {\r\n item.classList.add(\'is-selected\', \'active\');\r\n item.setAttribute(\'aria-selected\', \'true\');\r\n } else {\r\n item.classList.remove(\'is-selected\', \'active\');\r\n item.setAttribute(\'aria-selected\', \'false\');\r\n }\r\n });\r\n });\r\n};\r\n\r\n// Execute all registered initialization functions\r\nconst ux4gInitAll = () => {\r\n if (window.ux4gCustomInit) {\r\n window.ux4gCustomInit();\r\n } else if (window.ux4gCustomInitList) {\r\n window.ux4gCustomInitList.forEach(fn => {\r\n if (typeof fn === \'function\') fn();\r\n });\r\n }\r\n};\r\n\r\nconst ux4gCustomInitAllCombined = () => {\r\n ux4gInitAll();\r\n initCustomListDropdowns();\r\n};\r\n\r\nif (document.readyState === \'loading\') {\r\n document.addEventListener(\'DOMContentLoaded\', ux4gCustomInitAllCombined);\r\n} else {\r\n ux4gCustomInitAllCombined();\r\n}\r\n\r\n\r\n/* ========================================================= custom list dropdown JS ========================================================= */\r\ndocument.addEventListener(\'click\', (e) => {\r\n // 1. Handle toggle button click\r\n const btn = e.target.closest(\'.ux4g-custom-dropdown-btn\');\r\n if (btn) {\r\n e.stopPropagation();\r\n const dropdown = btn.closest(\'.ux4g-custom-list-dropdown\');\r\n if (dropdown) {\r\n const menu = dropdown.querySelector(\'.ux4g-custom-dropdown-menu\');\r\n if (menu) {\r\n const isExpanded = btn.getAttribute(\'aria-expanded\') === \'true\';\r\n // Close other custom dropdowns\r\n document.querySelectorAll(\'.ux4g-custom-list-dropdown\').forEach(d => {\r\n if (d !== dropdown) {\r\n const otherBtn = d.querySelector(\'.ux4g-custom-dropdown-btn\');\r\n const otherMenu = d.querySelector(\'.ux4g-custom-dropdown-menu\');\r\n if (otherBtn) otherBtn.setAttribute(\'aria-expanded\', \'false\');\r\n if (otherMenu) otherMenu.style.display = \'none\';\r\n }\r\n });\r\n \r\n btn.setAttribute(\'aria-expanded\', String(!isExpanded));\r\n menu.style.display = isExpanded ? \'none\' : \'block\';\r\n }\r\n }\r\n return;\r\n }\r\n\r\n // 2. Handle item selection\r\n const item = e.target.closest(\'.ux4g-custom-list-dropdown .ux4g-custom-dropdown-item\');\r\n if (item) {\r\n e.stopPropagation();\r\n const dropdown = item.closest(\'.ux4g-custom-list-dropdown\');\r\n if (dropdown) {\r\n const btn = dropdown.querySelector(\'.ux4g-custom-dropdown-btn\');\r\n const menu = dropdown.querySelector(\'.ux4g-custom-dropdown-menu\');\r\n \r\n // Toggle selected class and aria-selected attribute\r\n const items = dropdown.querySelectorAll(\'.ux4g-custom-dropdown-item\');
1\r\n items.forEach(el => {\r\n if (el === item) {\r\n el.classList.add(\'is-selected\', \'active\');\r\n el.setAttribute(\'aria-selected\', \'true\');\r\n } else {\r\n el.classList.remove(\'is-selected\', \'active\');\r\n el.setAttribute(\'aria-selected\', \'false\');\r\n }\r\n });\r\n\r\n const span = btn ? btn.querySelector(\'span:first-child\') : null;\r\n if (span) {\r\n const textEl = item.querySelector(\'.ux4g-custom-dropdown-text\');\r\n span.textContent = textEl ? textEl.textContent.trim() : item.textContent.trim();\r\n }\r\n if (btn) {\r\n btn.setAttribute(\'aria-expanded\', \'false\');\r\n btn.setAttribute(\'data-value\', item.getAttribute(\'data-value\') || \'\');\r\n }\r\n if (menu) menu.style.display = \'none\';\r\n }\r\n return;\r\n }\r\n\r\n // 3. Clicked outside - close all custom dropdowns\r\n document.querySelectorAll(\'.ux4g-custom-list-dropdown\').forEach(d => {\r\n const otherBtn = d.querySelector(\'.ux4g-custom-dropdown-btn\');\r\n const otherMenu = d.querySelector(\'.ux4g-custom-dropdown-menu\');\r\n if (otherBtn) otherBtn.setAttribute(\'aria-expanded\', \'false\');\r\n if (otherMenu) otherMenu.style.display = \'none\';\r\n });\r\n});\r\n\r\n/********************************* Mega Menu Five Category list JS ***********************************/\r\nwindow.ux4gCustomInitList = window.ux4gCustomInitList || []; window.ux4gCustomInitList.push(() => {\r\n const categoryItems = document.querySelectorAll(\'.ux4g-mega-menu-five-category-item\');\r\n const contentBlocks = document.querySelectorAll(\'.ux4g-mega-menu-five-content\');\r\n\r\n if (!categoryItems.length || !contentBlocks.length) return;\r\n\r\n categoryItems.forEach((item, index) => {\r\n item.addEventListener(\'click\', (e) => {\r\n e.preventDefault();\r\n\r\n // Remove active class from all categories\r\n categoryItems.forEach(cat => cat.classList.remove(\'ux4g-mega-menu-five-category-item--active\'));\r\n // Add active class to clicked category\r\n item.classList.add(\'ux4g-mega-menu-five-category-item--active\');\r\n\r\n // Hide all content blocks\r\n contentBlocks.forEach(block => block.classList.remove(\'ux4g-mega-menu-five-content--active\'));\r\n\r\n // Show the corresponding content block by ID matching five-category-1, five-category-2, etc.\r\n const targetId = `five-category-${index + 1}`;\r\n const targetBlock = document.getElementById(targetId);\r\n\r\n if (targetBlock) {\r\n targetBlock.classList.add(\'ux4g-mega-menu-five-content--active\');\r\n }\r\n });\r\n });\r\n});\r\n',document.head.appendChild(t)}(),"undefined"!=typeof window&&"undefined"!=typeof MutationObserver){let n=null,e=new MutationObserver(e=>{let t=!1;for(let n of e)if(n.addedNodes.length>0){t=!0;break}t&&(n&&clearTimeout(n),n=setTimeout(()=>{window.ux4g?.init&&window.ux4g.init(document)},50))}),t=()=>{document.body?e.observe(document.body,{childList:!0,subtree:!0}):setTimeout(t,10)};"loading"===document.readyState?document.addEventListener("DOMContentLoaded",t):t()}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.