(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 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();
initBulkSelect();
initFkAutocomplete();
initSearchPalette();
});
} else {
initSidebar();
initDropdowns();
initBulkSelect();
initFkAutocomplete();
initSearchPalette();
}
})();