(function () {
"use strict";
function initSidebar() {
const shell = document.querySelector(".rio-shell");
const toggle = document.querySelector("[data-rio-sidebar-toggle]");
if (!shell || !toggle) return;
toggle.addEventListener("click", () => {
const open = shell.getAttribute("data-sidebar") === "open";
if (open) shell.removeAttribute("data-sidebar");
else shell.setAttribute("data-sidebar", "open");
});
shell.addEventListener("click", (evt) => {
const link = evt.target.closest(".rio-sidebar-link");
if (link) shell.removeAttribute("data-sidebar");
});
}
function initDropdowns() {
const dropdowns = document.querySelectorAll("[data-rio-dropdown]");
if (!dropdowns.length) return;
dropdowns.forEach((dd) => {
const toggle = dd.querySelector(".rio-dropdown-toggle");
if (!toggle) return;
toggle.addEventListener("click", (e) => {
e.stopPropagation();
const open = dd.classList.toggle("is-open");
toggle.setAttribute("aria-expanded", String(open));
});
});
document.addEventListener("click", (e) => {
dropdowns.forEach((dd) => {
if (dd.classList.contains("is-open") && !dd.contains(e.target)) {
dd.classList.remove("is-open");
const t = dd.querySelector(".rio-dropdown-toggle");
if (t) t.setAttribute("aria-expanded", "false");
}
});
});
document.addEventListener("keydown", (e) => {
if (e.key !== "Escape") return;
dropdowns.forEach((dd) => {
if (!dd.classList.contains("is-open")) return;
dd.classList.remove("is-open");
const t = dd.querySelector(".rio-dropdown-toggle");
if (t) {
t.setAttribute("aria-expanded", "false");
t.focus();
}
});
});
}
function initBulkSelect() {
const form = document.querySelector("[data-rio-bulk]");
if (!form) return;
const all = form.querySelector("[data-rio-bulk-all]");
const idsInput = form.querySelector("[data-rio-bulk-ids]");
const countEl = form.querySelector("[data-rio-bulk-count]");
const clearBtn = form.querySelector("[data-rio-bulk-clear]");
const rows = Array.from(form.querySelectorAll("[data-rio-bulk-row]"));
if (!rows.length) return;
function refresh() {
const checked = rows.filter((r) => r.checked);
const count = checked.length;
idsInput.value = checked.map((r) => r.value).join(",");
if (countEl) countEl.textContent = String(count);
form.classList.toggle("is-active", count > 0);
rows.forEach((r) => {
const tr = r.closest("tr");
if (tr) tr.classList.toggle("is-selected", r.checked);
});
if (all) {
all.checked = count > 0 && count === rows.length;
all.indeterminate = count > 0 && count < rows.length;
}
}
rows.forEach((r) => r.addEventListener("change", refresh));
if (all) {
all.addEventListener("change", () => {
rows.forEach((r) => { r.checked = all.checked; });
refresh();
});
}
if (clearBtn) {
clearBtn.addEventListener("click", () => {
rows.forEach((r) => { r.checked = false; });
refresh();
});
}
form.addEventListener("submit", (e) => {
if (!idsInput.value) e.preventDefault();
});
refresh();
}
function initRowActions() {
const menus = Array.from(document.querySelectorAll("[data-rio-row-actions]"));
if (!menus.length) return;
let openMenu = null;
function placeMenu(details) {
const toggle = details.querySelector(".rio-row-actions__toggle");
const panel = details.querySelector(".rio-row-actions__menu");
if (!toggle || !panel) return;
panel.classList.add("is-floating");
const btn = toggle.getBoundingClientRect();
const panelRect = panel.getBoundingClientRect();
const gap = 4;
const margin = 8;
let top = btn.bottom + gap;
let left = btn.right - panelRect.width;
if (top + panelRect.height > window.innerHeight - margin) {
top = btn.top - panelRect.height - gap;
}
if (left < margin) left = margin;
const maxLeft = window.innerWidth - panelRect.width - margin;
if (left > maxLeft) left = maxLeft;
panel.style.top = `${Math.max(margin, top)}px`;
panel.style.left = `${left}px`;
}
function closeMenu() {
if (!openMenu) return;
const details = openMenu;
openMenu = null;
details.removeAttribute("open");
const panel = details.querySelector(".rio-row-actions__menu");
if (panel) {
panel.classList.remove("is-floating");
panel.style.top = "";
panel.style.left = "";
}
const toggle = details.querySelector(".rio-row-actions__toggle");
if (toggle) toggle.setAttribute("aria-expanded", "false");
}
function openOne(details) {
if (openMenu && openMenu !== details) closeMenu();
details.setAttribute("open", "");
openMenu = details;
const toggle = details.querySelector(".rio-row-actions__toggle");
if (toggle) toggle.setAttribute("aria-expanded", "true");
placeMenu(details);
}
menus.forEach((details) => {
const toggle = details.querySelector(".rio-row-actions__toggle");
if (!toggle) return;
toggle.setAttribute("aria-expanded", "false");
toggle.addEventListener("click", (e) => {
e.preventDefault();
if (details.hasAttribute("open")) closeMenu();
else openOne(details);
});
details.addEventListener("keydown", (e) => {
if (e.key !== "ArrowDown" && e.key !== "ArrowUp") return;
const items = Array.from(
details.querySelectorAll(".rio-row-actions__item")
);
if (!items.length) return;
e.preventDefault();
const current = items.indexOf(document.activeElement);
const step = e.key === "ArrowDown" ? 1 : -1;
const next = (current + step + items.length) % items.length;
items[next].focus();
});
});
document.addEventListener("click", (e) => {
if (!openMenu) return;
if (!openMenu.contains(e.target)) closeMenu();
});
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && openMenu) {
const toggle = openMenu.querySelector(".rio-row-actions__toggle");
closeMenu();
if (toggle) toggle.focus();
}
});
window.addEventListener("scroll", closeMenu, true);
window.addEventListener("resize", closeMenu);
}
function initFkAutocomplete() {
const widgets = document.querySelectorAll("[data-rio-fk-autocomplete]");
widgets.forEach((widget) => {
const lookupUrl = widget.getAttribute("data-rio-fk-lookup-url");
if (!lookupUrl) return;
const search = widget.querySelector("[data-rio-fk-search]");
const idInput = widget.querySelector("[data-rio-fk-id]");
const results = widget.querySelector("[data-rio-fk-results]");
if (!search || !idInput || !results) return;
let debounce = 0;
let lastTerm = "";
function hideResults() {
results.setAttribute("hidden", "");
results.innerHTML = "";
}
function render(items) {
results.innerHTML = "";
if (!items.length) {
const empty = document.createElement("li");
empty.className = "rio-fk-autocomplete-empty";
empty.textContent = "No matches";
results.appendChild(empty);
} else {
items.forEach((item) => {
const li = document.createElement("li");
li.className = "rio-fk-autocomplete-result";
li.textContent = item.label;
li.setAttribute("role", "option");
li.setAttribute("data-id", String(item.id));
li.addEventListener("mousedown", (e) => {
e.preventDefault();
idInput.value = String(item.id);
search.value = item.label;
hideResults();
});
results.appendChild(li);
});
}
results.removeAttribute("hidden");
}
async function fetchResults(term) {
try {
const url = lookupUrl + "?q=" + encodeURIComponent(term);
const resp = await fetch(url, {
headers: { Accept: "application/json" },
credentials: "same-origin",
});
if (!resp.ok) return;
const items = await resp.json();
if (Array.isArray(items)) render(items);
} catch (_e) {
}
}
search.addEventListener("input", () => {
if (search.value !== lastTerm) idInput.value = "";
lastTerm = search.value;
window.clearTimeout(debounce);
const term = search.value.trim();
debounce = window.setTimeout(() => fetchResults(term), 250);
});
search.addEventListener("focus", () => {
if (search.value.trim().length > 0) {
window.clearTimeout(debounce);
debounce = window.setTimeout(() => fetchResults(search.value.trim()), 50);
}
});
search.addEventListener("blur", () => {
window.setTimeout(hideResults, 120);
});
search.addEventListener("keydown", (e) => {
if (e.key === "Escape") {
hideResults();
}
});
});
}
function initSearchPalette() {
const trigger = document.querySelector("[data-rio-search-trigger]");
const palette = document.querySelector("[data-rio-search-palette]");
if (!palette) return;
const dialog = palette.querySelector("[data-rio-search-palette-dialog]");
const input = palette.querySelector("[data-rio-search-palette-input]");
const list = palette.querySelector("[data-rio-search-palette-results]");
if (!dialog || !input || !list) return;
let debounceTimer = 0;
let selectedIndex = -1;
let resultItems = [];
let optionIdCounter = 0;
let groupIdCounter = 0;
function open() {
palette.setAttribute("aria-hidden", "false");
input.value = "";
list.innerHTML = "";
selectedIndex = -1;
resultItems = [];
input.removeAttribute("aria-activedescendant");
setTimeout(() => input.focus(), 0);
}
function close() {
if (palette.getAttribute("aria-hidden") === "true") return;
palette.setAttribute("aria-hidden", "true");
window.clearTimeout(debounceTimer);
input.removeAttribute("aria-activedescendant");
if (trigger) trigger.focus();
}
function isOpen() {
return palette.getAttribute("aria-hidden") === "false";
}
function setSelected(idx) {
if (resultItems.length === 0) {
selectedIndex = -1;
input.removeAttribute("aria-activedescendant");
return;
}
const n = resultItems.length;
selectedIndex = ((idx % n) + n) % n;
resultItems.forEach((el, i) => {
el.classList.toggle("is-selected", i === selectedIndex);
});
resultItems[selectedIndex].scrollIntoView({ block: "nearest" });
input.setAttribute("aria-activedescendant", resultItems[selectedIndex].id);
}
function render(results) {
list.innerHTML = "";
resultItems = [];
selectedIndex = -1;
input.removeAttribute("aria-activedescendant");
optionIdCounter = 0;
groupIdCounter = 0;
if (!results.length) {
const empty = document.createElement("li");
empty.className = "rio-search-palette__empty";
empty.textContent = "No results.";
list.appendChild(empty);
return;
}
const groups = new Map();
results.forEach((r) => {
if (!groups.has(r.model_label)) groups.set(r.model_label, []);
groups.get(r.model_label).push(r);
});
groups.forEach((rows, label) => {
const group = document.createElement("li");
group.className = "rio-search-palette__group";
const headingId = `rio-search-palette__group-${groupIdCounter++}`;
group.setAttribute("role", "group");
group.setAttribute("aria-labelledby", headingId);
const heading = document.createElement("span");
heading.className = "rio-search-palette__group-label";
heading.id = headingId;
heading.textContent = label;
group.appendChild(heading);
rows.forEach((r) => {
const a = document.createElement("a");
a.className = "rio-search-palette__result";
a.href = r.url;
a.id = `rio-search-palette__option-${optionIdCounter++}`;
a.setAttribute("role", "option");
a.setAttribute("tabindex", "-1");
const text = document.createElement("span");
text.className = "rio-search-palette__result-label";
text.textContent = r.label;
a.appendChild(text);
group.appendChild(a);
resultItems.push(a);
});
list.appendChild(group);
});
setSelected(0);
}
async function fetchResults(term) {
try {
const url = "/admin/_search?q=" + encodeURIComponent(term);
const resp = await fetch(url, {
headers: { Accept: "application/json" },
credentials: "same-origin",
});
if (!resp.ok) return;
const body = await resp.json();
if (body && Array.isArray(body.results)) render(body.results);
} catch (_e) {
}
}
if (trigger) trigger.addEventListener("click", open);
palette.addEventListener("click", (e) => {
if (e.target === palette) close();
});
input.addEventListener("input", () => {
window.clearTimeout(debounceTimer);
const term = input.value.trim();
if (term.length < 2) {
list.innerHTML = "";
resultItems = [];
selectedIndex = -1;
return;
}
debounceTimer = window.setTimeout(() => fetchResults(term), 200);
});
input.addEventListener("keydown", (e) => {
if (e.key === "ArrowDown") {
e.preventDefault();
setSelected(selectedIndex + 1);
} else if (e.key === "ArrowUp") {
e.preventDefault();
setSelected(selectedIndex - 1);
} else if (e.key === "Tab") {
e.preventDefault();
if (resultItems.length > 0) {
setSelected(selectedIndex + (e.shiftKey ? -1 : 1));
}
} else if (e.key === "Enter") {
if (selectedIndex >= 0 && resultItems[selectedIndex]) {
e.preventDefault();
window.location.href = resultItems[selectedIndex].href;
}
}
});
function focusInOtherTextInput(target) {
if (!(target instanceof Element)) return false;
if (target === input) return false;
return target.matches("input, textarea, [contenteditable], [contenteditable='true']");
}
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && isOpen()) {
e.preventDefault();
close();
return;
}
const isCmdK = (e.metaKey || e.ctrlKey) && (e.key === "k" || e.key === "K");
if (isCmdK) {
if (focusInOtherTextInput(e.target)) return;
e.preventDefault();
if (isOpen()) close();
else open();
}
});
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => {
initSidebar();
initDropdowns();
initRowActions();
initBulkSelect();
initFkAutocomplete();
initSearchPalette();
});
} else {
initSidebar();
initDropdowns();
initRowActions();
initBulkSelect();
initFkAutocomplete();
initSearchPalette();
}
})();