UNPKG

@ryanhelsing/ry-ui

Version:

Framework-agnostic, Light DOM web components. CSS is the source of truth.

1,149 lines 189 kB
class u extends HTMLElement { // Store for cleanup functions #t = []; // Store for MutationObserver #e = null; constructor() { super(); } /** * Called when element is added to DOM. * Override setup() in subclass instead of this. */ connectedCallback() { this.setAttribute("data-ry-initialized", ""), this.setup?.(); } /** * Called when element is removed from DOM. * Automatically cleans up event listeners and observers. */ disconnectedCallback() { this.#t.forEach((t) => t()), this.#t = [], this.#e && (this.#e.disconnect(), this.#e = null), this.teardown?.(); } on(t, e, s, i = {}) { const r = s.bind(this); return t.addEventListener(e, r, i), this.#t.push(() => { t.removeEventListener(e, r, i); }), r; } /** * Emit a custom event with ry: prefix. */ emit(t, e, s = {}) { const i = new CustomEvent(`ry:${t}`, { bubbles: !0, cancelable: !0, detail: e, ...s }); return this.dispatchEvent(i); } /** * Get/set component state via data attribute. */ get state() { return this.dataset.ryState ?? "closed"; } set state(t) { const e = this.state; e !== t && (this.dataset.ryState = t, this.emit("state-change", { oldState: e, newState: t })); } /** * Query within this element. */ $(t) { return this.querySelector(t); } /** * Query all within this element. */ $$(t) { return [...this.querySelectorAll(t)]; } /** * Setup a MutationObserver on this element. */ observe(t, e = { childList: !0, subtree: !0 }) { this.#e && this.#e.disconnect(), this.#e = new MutationObserver(t.bind(this)), this.#e.observe(this, e); } /** * Wait for next animation frame. */ nextFrame() { return new Promise((t) => requestAnimationFrame(t)); } /** * Trap focus within a container (for modals, dropdowns). */ trapFocus(t) { const e = [ "a[href]", "button:not([disabled])", "input:not([disabled])", "select:not([disabled])", "textarea:not([disabled])", '[tabindex]:not([tabindex="-1"])' ].join(","), s = t.querySelectorAll(e), i = s[0], r = s[s.length - 1], a = (n) => { n.key === "Tab" && (n.shiftKey ? document.activeElement === i && (n.preventDefault(), r?.focus()) : document.activeElement === r && (n.preventDefault(), i?.focus())); }; this.on(t, "keydown", a), i?.focus(); } } const P = [ // Layout "page", "header", "main", "footer", "section", "aside", "grid", "stack", "cluster", "split", "center", "nav", "logo", "actions", // Components "accordion", "accordion-item", "tabs", "tab", "modal", "dropdown", "menu", "menu-item", "drawer", "toast", "alert", "card", "button", "badge", "field", "switch", "tooltip", "select", "option", "theme-toggle", "divider", "icon", "example", "toggle-button", "slider", "knob", "number-select", "color-picker", "color-input", "gradient-picker", "tree", "tree-item", "tag", "tag-input", "hero", "stat", "feature", "feature-grid", "pricing", "pricing-card", "carousel", "combobox", "theme-panel", "testimonial", "heading", "search-list", "search-item", "search-group", "logo-bar" ], F = new RegExp( `<(/?)(${P.join("|")})(\\s|>|/)`, "g" ); function C(h) { return h.replace(F, "<$1ry-$2$3"); } function T() { document.querySelectorAll("ry").forEach((h) => { const t = C(h.innerHTML), e = document.createElement("template"); e.innerHTML = t, h.replaceWith(e.content); }), document.querySelectorAll("template[ry]").forEach((h) => { const t = C(h.innerHTML), e = document.createElement("template"); e.innerHTML = t, h.replaceWith(e.content); }); } document.readyState === "loading" ? document.addEventListener("DOMContentLoaded", T) : T(); class O extends u { setup() { this.#t(), this.$$('[data-ry-target="trigger"]').forEach((t) => { this.on(t, "click", this.#i); }), this.on(this, "keydown", this.#s), this.#e(); } #t() { this.$$("ry-accordion-item").forEach((t) => { const e = t.getAttribute("title") ?? "Untitled", s = t.hasAttribute("open"), i = t.innerHTML; t.outerHTML = ` <div data-ry-target="item" class="ry-accordion__item" data-ry-state="${s ? "open" : "closed"}"> <button data-ry-target="trigger" class="ry-accordion__trigger" aria-expanded="${s}"> ${e} <span data-ry-target="icon" class="ry-accordion__icon">&#9660;</span> </button> <div data-ry-target="panel" class="ry-accordion__panel"> <div class="ry-accordion__content">${i}</div> </div> </div> `; }); } #e() { this.$$('[data-ry-target="item"]').forEach((t, e) => { const s = t.querySelector('[data-ry-target="trigger"]'), i = t.querySelector('[data-ry-target="panel"]'); if (!s || !i) return; const r = s.id || `ry-accordion-trigger-${this.id || ""}-${e}`, a = i.id || `ry-accordion-panel-${this.id || ""}-${e}`; s.id = r, i.id = a, s.setAttribute("aria-controls", a), i.setAttribute("aria-labelledby", r), i.setAttribute("role", "region"); const n = t.dataset.ryState === "open"; s.setAttribute("aria-expanded", String(n)); }); } #i = (t) => { const s = t.currentTarget.closest('[data-ry-target="item"]'); s && this.toggle(s); }; #s = (t) => { const e = this.$$('[data-ry-target="trigger"]'), s = e.indexOf(document.activeElement); if (s === -1) return; let i; switch (t.key) { case "ArrowDown": t.preventDefault(), i = (s + 1) % e.length, e[i]?.focus(); break; case "ArrowUp": t.preventDefault(), i = (s - 1 + e.length) % e.length, e[i]?.focus(); break; case "Home": t.preventDefault(), e[0]?.focus(); break; case "End": t.preventDefault(), e[e.length - 1]?.focus(); break; } }; toggle(t) { t.dataset.ryState === "open" ? this.close(t) : this.open(t); } open(t) { this.hasAttribute("multiple") || this.$$('[data-ry-target="item"][data-ry-state="open"]').forEach((s) => { s !== t && this.close(s); }); const e = t.querySelector('[data-ry-target="trigger"]'); t.dataset.ryState = "open", e?.setAttribute("aria-expanded", "true"), this.emit("open", { item: t }); } close(t) { const e = t.querySelector('[data-ry-target="trigger"]'); t.dataset.ryState = "closed", e?.setAttribute("aria-expanded", "false"), this.emit("close", { item: t }); } openAll() { this.$$('[data-ry-target="item"]').forEach((t) => this.open(t)); } closeAll() { this.$$('[data-ry-target="item"]').forEach((t) => this.close(t)); } } customElements.define("ry-accordion", O); customElements.get("ry-accordion-item") || customElements.define("ry-accordion-item", class extends HTMLElement { }); class j extends u { #t = null; #e = 0; setup() { this.#i(); const t = this.id; t && (document.querySelectorAll(`[data-ry-modal-trigger="${t}"]`).forEach((s) => { this.on(s, "click", () => this.open()); }), document.querySelectorAll(`ry-button[modal="${t}"], [modal="${t}"]`).forEach((s) => { this.on(s, "click", () => this.open()); })), this.$$("[close]").forEach((s) => { this.on(s, "click", () => this.close()); }); const e = this.$('[data-ry-target="backdrop"]'); e && this.on(e, "click", () => this.close()), this.on(document, "keydown", this.#s), this.#e = window.innerWidth - document.documentElement.clientWidth, this.hasAttribute("data-ry-state") || (this.state = "closed"); } #i() { if (this.$('[data-ry-target="dialog"]')) return; const t = this.getAttribute("title") ?? "", s = this.$("footer")?.innerHTML ?? "", i = Array.from(this.childNodes).filter((r) => !(r instanceof HTMLElement && r.tagName === "FOOTER")).map((r) => r instanceof Element ? r.outerHTML : r.textContent).join(""); this.innerHTML = ` <div data-ry-target="backdrop" class="ry-modal__backdrop"></div> <div data-ry-target="dialog" class="ry-modal__dialog" role="dialog" aria-modal="true" aria-labelledby="ry-modal-title-${this.id}"> <div data-ry-target="header" class="ry-modal__header"> <h3 class="ry-modal__title" id="ry-modal-title-${this.id}">${t}</h3> <button data-ry-target="close" class="ry-modal__close" close aria-label="Close">&times;</button> </div> <div data-ry-target="body" class="ry-modal__body">${i}</div> ${s ? `<div data-ry-target="footer" class="ry-modal__footer">${s}</div>` : ""} </div> `; } #s = (t) => { t.key === "Escape" && this.state === "open" && this.close(); }; open() { if (this.state === "open") return; this.#t = document.activeElement, document.body.setAttribute("data-ry-scroll-lock", ""), document.body.style.setProperty("--ry-scrollbar-width", `${this.#e}px`), this.state = "open"; const t = this.$('[data-ry-target="dialog"]'); t && this.nextFrame().then(() => { this.trapFocus(t); }), this.emit("open"); } close() { this.state !== "closed" && (document.body.removeAttribute("data-ry-scroll-lock"), document.body.style.removeProperty("--ry-scrollbar-width"), this.state = "closed", this.#t instanceof HTMLElement && (this.#t.focus(), this.#t = null), this.emit("close")); } toggle() { this.state === "open" ? this.close() : this.open(); } } customElements.define("ry-modal", j); class N extends u { setup() { this.#t(), this.$$('[data-ry-target="trigger"]').forEach((t) => { this.on(t, "click", this.#i); }), this.on(this, "keydown", this.#s), this.#e(); } #t() { const t = this.$$("ry-tab"); if (t.length === 0) return; const e = t.findIndex((r) => r.hasAttribute("active")), s = e === -1 ? 0 : e, i = document.createElement("div"); i.setAttribute("data-ry-target", "list"), i.className = "ry-tabs__list", i.setAttribute("role", "tablist"), t.forEach((r, a) => { const n = r.getAttribute("title") ?? `Tab ${a + 1}`, o = a === s, l = document.createElement("button"); l.setAttribute("data-ry-target", "trigger"), l.className = "ry-tabs__trigger", l.setAttribute("role", "tab"), l.setAttribute("aria-selected", String(o)), l.textContent = n, i.appendChild(l), r.setAttribute("data-ry-target", "panel"), r.className = "ry-tabs__panel", r.setAttribute("role", "tabpanel"), r.dataset.ryState = o ? "active" : "", r.removeAttribute("title"), r.removeAttribute("active"); }), this.insertBefore(i, this.firstChild); } #e() { const t = this.$$('[data-ry-target="trigger"]'), e = this.$$('[data-ry-target="panel"], ry-tab'); t.forEach((i, r) => { const a = e[r]; if (!a) return; const n = i.id || `ry-tab-${this.id || ""}-${r}`, o = a.id || `ry-tabpanel-${this.id || ""}-${r}`; i.id = n, a.id = o, i.setAttribute("aria-controls", o), a.setAttribute("aria-labelledby", n), i.setAttribute("tabindex", i.getAttribute("aria-selected") === "true" ? "0" : "-1"); }), t.findIndex((i) => i.getAttribute("aria-selected") === "true") === -1 && t.length > 0 && this.select(0); } #i = (t) => { const e = t.currentTarget, i = this.$$('[data-ry-target="trigger"]').indexOf(e); i !== -1 && this.select(i); }; #s = (t) => { const e = this.$$('[data-ry-target="trigger"]'), s = e.indexOf(document.activeElement); if (s === -1) return; let i; switch (t.key) { case "ArrowRight": t.preventDefault(), i = (s + 1) % e.length, this.select(i), e[i]?.focus(); break; case "ArrowLeft": t.preventDefault(), i = (s - 1 + e.length) % e.length, this.select(i), e[i]?.focus(); break; case "Home": t.preventDefault(), this.select(0), e[0]?.focus(); break; case "End": t.preventDefault(), this.select(e.length - 1), e[e.length - 1]?.focus(); break; } }; select(t) { const e = this.$$('[data-ry-target="trigger"]'), s = this.$$('[data-ry-target="panel"], ry-tab'); if (t < 0 || t >= e.length) return; e.forEach((a) => { a.setAttribute("aria-selected", "false"), a.setAttribute("tabindex", "-1"); }), s.forEach((a) => { a.dataset.ryState = "", a.removeAttribute("active"); }); const i = e[t], r = s[t]; i && r && (i.setAttribute("aria-selected", "true"), i.setAttribute("tabindex", "0"), r.dataset.ryState = "active", this.emit("change", { index: t, trigger: i, panel: r })); } get selectedIndex() { return this.$$('[data-ry-target="trigger"]').findIndex((e) => e.getAttribute("aria-selected") === "true"); } } customElements.define("ry-tabs", N); customElements.get("ry-tab") || customElements.define("ry-tab", class extends HTMLElement { }); class q extends u { setup() { this.#t(); const t = this.$('[data-ry-target="trigger"]'); t && (this.on(t, "click", () => this.toggle()), t.setAttribute("aria-haspopup", "true"), t.setAttribute("aria-expanded", "false")), this.on(document, "click", this.#e), this.on(document, "keydown", this.#i), this.$$('[data-ry-target="item"], ry-menu-item').forEach((e) => { this.on(e, "click", () => this.close()); }), this.state = "closed"; } #t() { const t = this.$("ry-button:first-of-type"); t && !this.$('[data-ry-target="trigger"]') && t.setAttribute("data-ry-target", "trigger"); const e = this.$("ry-menu"); e && !e.hasAttribute("data-ry-target") && (e.setAttribute("data-ry-target", "menu"), e.classList.add("ry-dropdown__menu"), e.setAttribute("role", "menu")), this.$$("ry-menu-item").forEach((s) => { s.hasAttribute("data-ry-target") || (s.setAttribute("data-ry-target", "item"), s.classList.add("ry-dropdown__item"), s.setAttribute("role", "menuitem")); }); } #e = (t) => { const e = t.target; this.state === "open" && !this.contains(e) && this.close(); }; #i = (t) => { if (t.key === "Escape" && this.state === "open" && (this.close(), this.$('[data-ry-target="trigger"]')?.focus()), this.state === "open" && ["ArrowDown", "ArrowUp"].includes(t.key)) { t.preventDefault(); const e = this.$$('[data-ry-target="item"], ry-menu-item'), s = e.indexOf(document.activeElement); let i; t.key === "ArrowDown" ? i = s < e.length - 1 ? s + 1 : 0 : i = s > 0 ? s - 1 : e.length - 1, e[i]?.focus(); } }; open() { if (this.state === "open") return; this.#s(), this.$('[data-ry-target="trigger"]')?.setAttribute("aria-expanded", "true"), this.state = "open", this.nextFrame().then(() => { this.$('[data-ry-target="item"], ry-menu-item')?.focus(); }), this.emit("open"); } close() { if (this.state === "closed") return; this.$('[data-ry-target="trigger"]')?.setAttribute("aria-expanded", "false"), this.state = "closed", this.removeAttribute("data-ry-position"), this.emit("close"); } #s() { const t = this.$('[data-ry-target="menu"], ry-menu'); if (!t) return; const e = this.getBoundingClientRect(), s = t.scrollHeight || 240, i = window.innerHeight - e.bottom, r = e.top; i < s && r > i ? this.setAttribute("data-ry-position", "top") : this.setAttribute("data-ry-position", "bottom"); } toggle() { this.state === "open" ? this.close() : this.open(); } } customElements.define("ry-dropdown", q); customElements.get("ry-menu") || customElements.define("ry-menu", class extends HTMLElement { }); customElements.get("ry-menu-item") || customElements.define("ry-menu-item", class extends HTMLElement { }); class K extends u { setup() { this.hasAttribute("tabindex") || this.setAttribute("tabindex", "0"), this.setAttribute("role", "button"), this.on(this, "click", this.#t), this.on(this, "keydown", this.#e); } #t = (t) => { if (this.hasAttribute("disabled")) { t.preventDefault(); return; } this.emit("click", { originalEvent: t }); }; #e = (t) => { (t.key === "Enter" || t.key === " ") && (t.preventDefault(), this.click()); }; } customElements.define("ry-button", K); class z extends u { #t = null; #e = !1; #i = 0; #s = 0; setup() { if (!this.hasAttribute("resizable")) return; this.#y(), this.#t = document.createElement("div"), this.#t.setAttribute("data-ry-target", "handle"), this.#t.className = "ry-split__handle", this.#t.setAttribute("role", "separator"), this.#t.setAttribute("aria-orientation", "vertical"), this.#t.setAttribute("tabindex", "0"); const t = this.lastElementChild; t && this.insertBefore(this.#t, t), this.on(this.#t, "mousedown", this.#u), this.on(this.#t, "touchstart", this.#n), this.on(this.#t, "keydown", this.#f), this.on(this.#t, "dblclick", this.#A); } #r() { return this.lastElementChild; } #a() { const t = this.#r(); return t ? t.getBoundingClientRect().width : 300; } #o() { const t = getComputedStyle(this).getPropertyValue("--ry-split-min-width").trim(); return t ? parseFloat(t) : 100; } #l() { const t = getComputedStyle(this).getPropertyValue("--ry-split-max-width").trim(); return t ? parseFloat(t) : this.getBoundingClientRect().width * 0.8; } #c(t) { const e = this.#o(), s = this.#l(), i = Math.round(Math.max(e, Math.min(s, t))); this.style.setProperty("--ry-split-width", `${i}px`); } #u = (t) => { t.preventDefault(), this.#d(t.clientX); const e = (i) => this.#h(i.clientX), s = () => { document.removeEventListener("mousemove", e), document.removeEventListener("mouseup", s), this.#b(); }; document.addEventListener("mousemove", e), document.addEventListener("mouseup", s); }; #n = (t) => { const e = t.touches[0]; if (!e) return; t.preventDefault(), this.#d(e.clientX); const s = (r) => { const a = r.touches[0]; a && this.#h(a.clientX); }, i = () => { document.removeEventListener("touchmove", s), document.removeEventListener("touchend", i), this.#b(); }; document.addEventListener("touchmove", s, { passive: !1 }), document.addEventListener("touchend", i); }; #d(t) { this.#e = !0, this.#i = t, this.#s = this.#a(), this.setAttribute("data-ry-resizing", ""), document.body.style.cursor = "col-resize", document.body.style.userSelect = "none"; } #h(t) { if (!this.#e) return; const e = this.#i - t; this.#c(this.#s + e); } #b() { this.#e = !1, this.removeAttribute("data-ry-resizing"), document.body.style.cursor = "", document.body.style.userSelect = "", this.#p(), this.emit("resize", { width: this.#a() }); } #f = (t) => { const e = t.shiftKey ? 50 : 10; let s = this.#a(); t.key === "ArrowLeft" ? (t.preventDefault(), this.#c(s + e), this.#p()) : t.key === "ArrowRight" ? (t.preventDefault(), this.#c(s - e), this.#p()) : t.key === "Home" ? (t.preventDefault(), this.#c(this.#o()), this.#p()) : t.key === "End" && (t.preventDefault(), this.#c(this.#l()), this.#p()); }; #A = () => { this.style.removeProperty("--ry-split-width"), this.#v(), this.emit("resize", { width: this.#a() }); }; // Persistence #g() { const t = this.getAttribute("persist"); return t ? `ry-split:${t}` : null; } #p() { const t = this.#g(); if (t) try { localStorage.setItem(t, String(this.#a())); } catch { } } #y() { const t = this.#g(); if (t) try { const e = localStorage.getItem(t); if (e) { const s = parseFloat(e); isNaN(s) || this.style.setProperty("--ry-split-width", `${s}px`); } } catch { } } #v() { const t = this.#g(); if (t) try { localStorage.removeItem(t); } catch { } } } customElements.define("ry-split", z); class V extends u { #t = ["light", "dark"]; #e = 0; setup() { const t = this.getAttribute("themes"); t && (this.#t = t.split(",").map((s) => s.trim())); const e = document.documentElement.dataset.ryTheme ?? "light"; this.#e = this.#t.indexOf(e), this.#e === -1 && (this.#e = 0), this.innerHTML.trim() || (this.innerHTML = `<button data-ry-target="button" class="ry-btn ry-btn--ghost">${this.#i()}</button>`), this.on(this, "click", () => this.toggle()); } #i() { switch (this.#t[this.#e]) { case "dark": return "&#9790;"; // Moon case "ocean": return "&#127754;"; // Wave default: return "&#9728;"; } } toggle() { this.#e = (this.#e + 1) % this.#t.length; const t = this.#t[this.#e]; document.documentElement.dataset.ryTheme = t; const e = this.$('[data-ry-target="button"]'); e && (e.innerHTML = this.#i()), this.emit("theme-change", { theme: t }); } get theme() { return this.#t[this.#e] ?? "light"; } set theme(t) { const e = this.#t.indexOf(t); if (e !== -1) { this.#e = e, document.documentElement.dataset.ryTheme = t; const s = this.$('[data-ry-target="button"]'); s && (s.innerHTML = this.#i()); } } } customElements.define("ry-theme-toggle", V); class U extends u { setup() { this.hasAttribute("interactive") && (this.hasAttribute("tabindex") || this.setAttribute("tabindex", "0"), this.setAttribute("role", "link"), this.on(this, "click", this.#t), this.on(this, "keydown", this.#e)); } #t = (t) => { if (t.target.closest("a, button, ry-button")) return; const s = this.getAttribute("href"); s && (window.location.href = s), this.emit("click", { originalEvent: t }); }; #e = (t) => { (t.key === "Enter" || t.key === " ") && (t.preventDefault(), this.click()); }; } customElements.define("ry-card", U); class W extends u { setup() { this.#t(); const t = this.$("[close]"); t && this.on(t, "click", () => this.dismiss()); } #t() { if (this.$('[data-ry-target="content"]')) return; const t = this.getAttribute("variant") ?? "info", e = this.hasAttribute("dismissible"), s = this.innerHTML; this.innerHTML = ` <div data-ry-target="content" class="ry-alert__content">${s}</div> ${e ? '<button data-ry-target="close" class="ry-alert__close" close aria-label="Dismiss">&times;</button>' : ""} `, this.setAttribute("role", "alert"), this.setAttribute("data-variant", t); } dismiss() { this.state = "closed", setTimeout(() => { this.remove(); }, 200), this.emit("close"); } } customElements.define("ry-alert", W); class Y extends u { static get observedAttributes() { return ["label", "error", "hint"]; } setup() { this.#t(); } attributeChangedCallback(t, e, s) { e !== s && (t === "error" ? this.#e(s) : t === "hint" ? this.#i(s) : t === "label" && this.#s(s)); } #t() { const t = this.$("input, textarea, select"); if (!t) return; const e = t.id || `ry-field-${Math.random().toString(36).slice(2, 9)}`; if (t.id = e, !this.$('[data-ry-target="label"]')) { const a = this.getAttribute("label"); if (a) { const n = document.createElement("label"); n.setAttribute("data-ry-target", "label"), n.className = "ry-label", n.setAttribute("for", e), n.textContent = a, this.insertBefore(n, t); } } if (!this.$('[data-ry-target="error"]')) { const a = document.createElement("div"); a.setAttribute("data-ry-target", "error"), a.className = "ry-field__error", a.setAttribute("role", "alert"); const n = this.getAttribute("error"); n && (a.textContent = n), t.insertAdjacentElement("afterend", a); } if (!this.$('[data-ry-target="hint"]')) { const a = document.createElement("div"); a.setAttribute("data-ry-target", "hint"), a.className = "ry-field__hint"; const n = this.getAttribute("hint"); n && (a.textContent = n); const o = this.$('[data-ry-target="error"]'); o ? o.insertAdjacentElement("afterend", a) : t.insertAdjacentElement("afterend", a); } const s = this.$('[data-ry-target="error"]'), i = this.$('[data-ry-target="hint"]'), r = []; s && (s.id = `${e}-error`, r.push(s.id)), i && (i.id = `${e}-hint`, r.push(i.id)), r.length && t.setAttribute("aria-describedby", r.join(" ")); } #e(t) { const e = this.$('[data-ry-target="error"]'); e && (e.textContent = t ?? ""); const s = this.$("input, textarea, select"); s && (t ? s.setAttribute("aria-invalid", "true") : s.removeAttribute("aria-invalid")); } #i(t) { const e = this.$('[data-ry-target="hint"]'); e && (e.textContent = t ?? ""); } #s(t) { const e = this.$('[data-ry-target="label"]'); e && (e.textContent = t ?? ""); } } customElements.define("ry-field", Y); let X = 0; class G extends u { #t = null; static observedAttributes = ["checked", "disabled"]; setup() { this.#e(), this.#t = this.$('input[type="checkbox"]'), this.#t && this.on(this.#t, "change", this.#i), this.on(this, "keydown", this.#s); } #e() { if (this.$('[data-ry-target="track"]')) return; const t = `ry-switch-${++X}`, e = this.getAttribute("name") ?? "", s = this.hasAttribute("checked"), i = this.hasAttribute("disabled"), r = this.textContent?.trim() ?? ""; this.innerHTML = ` <label data-ry-target="wrapper" class="ry-switch__wrapper" for="${t}"> <input type="checkbox" id="${t}" data-ry-target="input" class="ry-switch__input" ${e ? `name="${e}"` : ""} ${s ? "checked" : ""} ${i ? "disabled" : ""} role="switch" aria-checked="${s}" > <span data-ry-target="track" class="ry-switch__track"> <span data-ry-target="thumb" class="ry-switch__thumb"></span> </span> ${r ? `<span data-ry-target="label" class="ry-switch__label">${r}</span>` : ""} </label> `; } #i = () => { if (!this.#t) return; const t = this.#t.checked; this.#t.setAttribute("aria-checked", String(t)), t ? this.setAttribute("checked", "") : this.removeAttribute("checked"), this.emit("change", { value: String(t), label: t ? "on" : "off" }); }; #s = (t) => { (t.key === " " || t.key === "Enter") && (t.preventDefault(), this.#t?.click()); }; get checked() { return this.#t?.checked ?? !1; } set checked(t) { this.#t && (this.#t.checked = t, this.#t.setAttribute("aria-checked", String(t)), t ? this.setAttribute("checked", "") : this.removeAttribute("checked")); } get value() { return this.#t?.value ?? ""; } set value(t) { this.#t && (this.#t.value = t); } } customElements.define("ry-switch", G); let J = 0; class Z extends u { #t = null; #e = null; #i = null; setup() { this.#i = `ry-tooltip-${++J}`, this.#s(); const t = this.firstElementChild; t && (t.setAttribute("aria-describedby", this.#i), this.on(t, "mouseenter", this.#r), this.on(t, "mouseleave", this.#o), this.on(t, "focusin", this.#r), this.on(t, "focusout", this.#o)), this.on(document, "keydown", this.#l); } #s() { this.#e = document.createElement("div"), this.#e.id = this.#i, this.#e.setAttribute("data-ry-target", "content"), this.#e.className = "ry-tooltip__content", this.#e.setAttribute("role", "tooltip"), this.#e.textContent = this.getAttribute("content") ?? "", this.appendChild(this.#e); } #r = () => { this.#t && clearTimeout(this.#t), this.#t = setTimeout(() => { this.#a(); }, 200); }; #a = () => { const t = this.getAttribute("position") ?? "top"; this.setAttribute("data-ry-position", t), this.state = "open"; }; #o = () => { this.#t && (clearTimeout(this.#t), this.#t = null), this.state = "closed"; }; #l = (t) => { t.key === "Escape" && this.state === "open" && this.#o(); }; teardown() { this.#t && clearTimeout(this.#t); } } customElements.define("ry-tooltip", Z); class Q extends u { #t = null; #e = 0; setup() { this.#i(); const t = this.id; t && document.querySelectorAll(`ry-button[drawer="${t}"], [drawer="${t}"]`).forEach((s) => { this.on(s, "click", () => this.open()); }), this.$$("[close]").forEach((s) => { this.on(s, "click", () => this.close()); }); const e = this.$('[data-ry-target="backdrop"]'); e && this.on(e, "click", () => this.close()), this.on(document, "keydown", this.#s), this.#e = window.innerWidth - document.documentElement.clientWidth, this.hasAttribute("open") ? this.state = "open" : this.hasAttribute("data-ry-state") || (this.state = "closed"); } #i() { if (this.$('[data-ry-target="panel"]')) return; const t = this.getAttribute("side") ?? "left", e = this.innerHTML; this.innerHTML = ` <div data-ry-target="backdrop" class="ry-drawer__backdrop"></div> <div data-ry-target="panel" class="ry-drawer__panel" role="dialog" aria-modal="true" data-ry-side="${t}"> <button data-ry-target="close" class="ry-drawer__close" close aria-label="Close">&times;</button> <div data-ry-target="content" class="ry-drawer__content"> ${e} </div> </div> `, this.setAttribute("data-ry-side", t); } #s = (t) => { t.key === "Escape" && this.state === "open" && this.close(); }; open() { if (this.state === "open") return; this.#t = document.activeElement, document.body.setAttribute("data-ry-scroll-lock", ""), document.body.style.setProperty("--ry-scrollbar-width", `${this.#e}px`), this.state = "open"; const t = this.$('[data-ry-target="panel"]'); t && this.nextFrame().then(() => { this.trapFocus(t); }), this.emit("open"); } close() { this.state !== "closed" && (document.body.removeAttribute("data-ry-scroll-lock"), document.body.style.removeProperty("--ry-scrollbar-width"), this.state = "closed", this.#t instanceof HTMLElement && (this.#t.focus(), this.#t = null), this.emit("close")); } toggle() { this.state === "open" ? this.close() : this.open(); } } customElements.define("ry-drawer", Q); const tt = 4e3; class m extends u { #t = null; static observedAttributes = ["variant", "duration"]; // Static container for programmatic toasts static #e = null; static #i() { return m.#e || (m.#e = document.createElement("div"), m.#e.setAttribute("data-ry-target", "toast-container"), m.#e.className = "ry-toast-container", document.body.appendChild(m.#e)), m.#e; } // Static API static show(t) { const e = document.createElement("ry-toast"); return e.textContent = t.message, t.variant && e.setAttribute("variant", t.variant), t.duration !== void 0 && e.setAttribute("duration", String(t.duration)), m.#i().appendChild(e), e; } static success(t, e) { return m.show({ message: t, variant: "success", duration: e }); } static error(t, e) { return m.show({ message: t, variant: "error", duration: e }); } static info(t, e) { return m.show({ message: t, variant: "info", duration: e }); } static warning(t, e) { return m.show({ message: t, variant: "warning", duration: e }); } setup() { this.#s(); const t = this.$("[close]"); t && this.on(t, "click", () => this.dismiss()); const e = parseInt(this.getAttribute("duration") ?? String(tt), 10); e > 0 && (this.#t = setTimeout(() => this.dismiss(), e)), requestAnimationFrame(() => { this.state = "visible"; }); } #s() { if (this.$('[data-ry-target="content"]')) return; const t = this.getAttribute("variant") ?? "info", e = this.textContent?.trim() ?? "", s = this.#r(t); this.innerHTML = ` <div data-ry-target="icon" class="ry-toast__icon">${s}</div> <div data-ry-target="content" class="ry-toast__content">${e}</div> <button data-ry-target="close" class="ry-toast__close" close aria-label="Dismiss">&times;</button> `; } #r(t) { const e = { info: "ℹ", success: "✓", warning: "⚠", error: "✕" }; return e[t] ?? e.info; } dismiss() { this.#t && (clearTimeout(this.#t), this.#t = null), this.state = "hiding", setTimeout(() => { this.remove(); }, 300), this.emit("close"); } teardown() { this.#t && clearTimeout(this.#t); } } customElements.define("ry-toast", m); const w = { // Close / X close: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6L6 18"/><path d="M6 6l12 12"/></svg>', // Chevron down "chevron-down": '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6 6-6"/></svg>', // Chevron up "chevron-up": '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 15l-6-6-6 6"/></svg>', // Chevron right "chevron-right": '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18l6-6-6-6"/></svg>', // Chevron left "chevron-left": '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 18l-6-6 6-6"/></svg>', // Check / Checkmark check: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"/></svg>', // Copy copy: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>', // Sun (light mode) sun: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="M4.93 4.93l1.41 1.41"/><path d="M17.66 17.66l1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="M6.34 17.66l-1.41 1.41"/><path d="M19.07 4.93l-1.41 1.41"/></svg>', // Moon (dark mode) moon: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"/></svg>', // Info info: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>', // Warning / Alert triangle warning: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>', // Error / X circle error: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M15 9l-6 6"/><path d="M9 9l6 6"/></svg>', // Success / Check circle success: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M9 12l2 2 4-4"/></svg>', // Search search: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg>', // Menu / Hamburger menu: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12h18"/><path d="M3 6h18"/><path d="M3 18h18"/></svg>', // Plus plus: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14"/><path d="M5 12h14"/></svg>', // Minus minus: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/></svg>', // Settings / Gear settings: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-2 2 2 2 0 01-2-2v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 01-2-2 2 2 0 012-2h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 010-2.83 2 2 0 012.83 0l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0 012-2 2 2 0 012 2v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 0 2 2 0 010 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 012 2 2 2 0 01-2 2h-.09a1.65 1.65 0 00-1.51 1z"/></svg>', // User user: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>', // Heart heart: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z"/></svg>', // Star star: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>', // Trash trash: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/></svg>', // Edit / Pencil edit: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>', // External link "external-link": '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>', // Download download: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>', // Upload upload: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>', // Folder (closed) folder: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 2H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2Z"/></svg>', // Folder open "folder-open": '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 2H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2Z"/><path d="M2 10h20"/></svg>', // File file: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/></svg>', // Gradient: solid color "gradient-solid": '<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" stroke="none"><rect x="4" y="4" width="16" height="16" rx="2"/></svg>', // Gradient: linear "gradient-linear": '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="3" y1="21" x2="21" y2="3"/><line x1="3" y1="15" x2="15" y2="3"/><line x1="9" y1="21" x2="21" y2="9"/></svg>', // Gradient: radial "gradient-radial": '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="12" cy="12" r="3"/><circle cx="12" cy="12" r="6.5"/></svg>', // Shape: circle "shape-circle": '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/></svg>', // Shape: ellipse "shape-ellipse": '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="12" rx="10" ry="6"/></svg>', // Quote quote: '<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" stroke="none"><path d="M4.583 17.321C3.553 16.227 3 15 3 13.011c0-3.5 2.457-6.637 6.03-8.188l.893 1.378c-3.335 1.804-3.987 4.145-4.247 5.621.537-.278 1.24-.375 1.929-.311C9.591 11.69 11 13.17 11 15c0 1.933-1.567 3.5-3.5 3.5-1.073 0-2.099-.49-2.917-1.179zM14.583 17.321C13.553 16.227 13 15 13 13.011c0-3.5 2.457-6.637 6.03-8.188l.893 1.378c-3.335 1.804-3.987 4.145-4.247 5.621.537-.278 1.24-.375 1.929-.311C19.591 11.69 21 13.17 21 15c0 1.933-1.567 3.5-3.5 3.5-1.073 0-2.099-.49-2.917-1.179z"/></svg>' }; function g(h) { return w[h] ?? ""; } function Ut(h, t) { w[h] = t; } function Wt(h) { Object.assign(w, h); } function Yt() { return Object.keys(w); } let et = 0; class st extends u { #t = null; #e = -1; #i = ""; #s = null; #r = /* @__PURE__ */ new Set(); // Store options reference _options = []; get #a() { return this.hasAttribute("multiple"); } static observedAttributes = ["value", "disabled"]; setup() { this.#t = `ry-select-${++et}`, this.#o(), this.hasAttribute("tabindex") || this.setAttribute("tabindex", "0"), this.on(this, "click", this.#l), this.on(this, "keydown", this.#c), this.on(document, "click", this.#u), this.#a && this.on(this, "ry:remove", this.#n), this.hasAttribute("data-ry-state") || (this.state = "closed"); const t = this.getAttribute("value"); if (t) if (this.#a) for (const e of t.split(",").map((s) => s.trim()).filter(Boolean)) this.#p(e); else this.value = t; } #o() { const t = [...this.querySelectorAll("ry-option")].map((o) => ({ value: o.getAttribute("value") ?? o.textContent?.trim() ?? "", label: o.textContent?.trim() ?? "", disabled: o.hasAttribute("disabled") })), e = this.getAttribute("placeholder") ?? "Select...", s = this.getAttribute("name") ?? "", i = this.hasAttribute("disabled"), r = this.#a, a = t.map( (o) => `<option value="${o.value}"${o.disabled ? " disabled" : ""}>${o.label}</option>` ).join(""), n = t.map((o, l) => ` <div data-ry-target="option" class="ry-select__option" role="option" id="${this.#t}-option-${l}" data-value="${o.value}" ${o.disabled ? "data-disabled" : ""} aria-disabled="${o.disabled}"> ${r ? `<span data-ry-target="check" class="ry-select__check">${g("check")}</span>` : ""} <span class="ry-select__option-label">${o.label}</span> </div> `).join(""); if (r) { const o = this.hasAttribute("clearable"); this.innerHTML = ` <div data-ry-target="trigger" class="ry-select__trigger ry-select__trigger--multi" aria-haspopup="listbox" aria-expanded="false"> <div data-ry-target="tags" class="ry-select__tags"></div> <span data-ry-target="value" class="ry-select__value">${e}</span> ${o ? `<button data-ry-target="clear" class="ry-select__clear" aria-label="Clear all" type="button">${g("close")}</button>` : ""} <span data-ry-target="arrow" class="ry-select__arrow">▾</span> </div> <div data-ry-target="dropdown" class="ry-select__dropdown" role="listbox" id="${this.#t}-listbox" tabindex="-1" aria-multiselectable="true"> ${n} </div> <select data-ry-target="native" class="ry-select__native" ${s ? `name="${s}"` : ""} multiple tabindex="-1" aria-hidden="true" ${i ? "disabled" : ""}> ${a} </select> `; const l = this.$('[data-ry-target="clear"]'); l && this.on(l, "click", (c) => { c.stopPropagation(), this.#M(); }); } else this.innerHTML = ` <div data-ry-target="trigger" class="ry-select__trigger" aria-haspopup="listbox" aria-expanded="false"> <span data-ry-target="value" class="ry-select__value">${e}</span> <span data-ry-target="arrow" class="ry-select__arrow">▾</span> </div> <div data-ry-target="dropdown" class="ry-select__dropdown" role="listbox" id="${this.#t}-listbox" tabindex="-1"> ${n} </div> <select data-ry-target="native" class="ry-select__native" ${s ? `name="${s}"` : ""} tabindex="-1" aria-hidden="true" ${i ? "disabled" : ""}> <option value="">${e}</option> ${a} </select> `; this._options = t; } #l = (t) => { if (this.hasAttribute("disabled")) return; const e = t.target, s = e.closest('[data-ry-target="option"]'); if (s instanceof HTMLElement && !s.hasAttribute("data-disabled")) { if (this.#a) { const r = s.dataset.value ?? ""; this.#p(r); } else this.#w(s), this.close(); return; } e.closest('[data-ry-target="trigger"]') && this.toggle(); }; #c = (t) => { if (this.hasAttribute("disabled")) return; const e = this.state === "open"; switch (t.key) { case "Enter": case " ": if (t.preventDefault(), e && this.#e >= 0) { const i = this.$$('[data-ry-target="option"]:not([data-disabled])')[this.#e]; if (i) if (this.#a) { const r = i.dataset.value ?? ""; this.#p(r); } else this.#w(i), this.close(); } else this.toggle(); break; case "Escape": e && (t.preventDefault(), this.close()); break; case "ArrowDown": t.preventDefault(), e ? this.#b() : this.open(); break; case "ArrowUp": t.preventDefault(), e && this.#f(); break; case "Home": e && (t.preventDefault(), this.#A()); break; case "End": e && (t.preventDefault(), this.#g()); break; case "Backspace": if (this.#a && this.#r.size > 0) { const s = [...this.#r], i = s[s.length - 1]; i && this.#p(i); } break; default: t.key.length === 1 && !t.ctrlKey && !t.metaKey && this.#d(t.key); break; } }; #u = (t) => { const e = t.target; !this.contains(e) && this.state === "open" && this.close(); }; #n = (t) => { const e = t.detail.value; this.#r.has(e) && this.#p(e); }; #d(t) { this.#s && clearTimeout(this.#s), this.#i += t.toLowerCase(); const e = this.$$('[data-ry-target="option"]:not([data-disabled])'), s = e.findIndex( (i) => i.textContent?.trim().toLowerCase().startsWith(this.#i) ); if (s >= 0) { if (this.state === "open") this.#h(s); else if (!this.#a) { const i = e[s]; i && this.#w(i); } } this.#s = setTimeout(() => { this.#i = ""; }, 500); } #h(t) { const e = this.$$('[data-ry-target="option"]:not([data-disabled])'); if (t < 0 || t >= e.length) return; this.$$('[data-ry-target="option"][data-highlighted]').forEach((r) => { r.removeAttribute("data-highlighted"); }); const s = e[t]; if (!s) return; s.setAttribute("data-highlighted", ""), s.scrollIntoView({ block: "nearest" }), this.#e = t; const i = this.$('[data-ry-target="trigger"]'); i && i.setAttribute("aria-activedescendant", s.id); } #b() { const t = this.$$('[data-ry-target="option"]:not([data-disabled])'), e = Math.min(this.#e + 1, t.length - 1); this.#h(e); } #f() { const t = Math.max(this.#e - 1, 0); this.#h(t); } #A() { this.#h(0); } #g() { const t = this.$$('[data-ry-target="option"]:not([data-disabled])'); this.#h(t.length - 1); } // --- Multi-select methods --- #p(t) { const e = this.getAttribute("max-selections"); if (this.#r.has(t)) this.#r.delete(t); else { if (e && this.#r.size >= parseInt(e, 10)) return; this.#r.add(t); } this.#y(), this.#v(), this.#m(), this.setAttribute("value", [...this.#r].join(",")), this.emit("change", { value: this.value, label: "" }); } #y() { const t =