сохранение состояния expanded / collapsed

This commit is contained in:
Илья Братских 2026-06-28 14:29:04 +03:00 committed by KBAHTNET
parent ad9a5df712
commit 1e8919e33c

View File

@ -1,4 +1,5 @@
const STORAGE_KEY = "ecosystem-panel-preferences"; const STORAGE_KEY = "ecosystem-panel-preferences";
const COLLAPSE_STORAGE_KEY = "ecosystem-panel-collapse-state";
// Меняй навигацию здесь: разделы -> подразделы -> элементы. // Меняй навигацию здесь: разделы -> подразделы -> элементы.
// url можно заменить на реальные адреса, tags помогают поиску. // url можно заменить на реальные адреса, tags помогают поиску.
@ -141,6 +142,7 @@ const defaultPreferences = {
const availableViews = ["cards", "list", "compact-cards", "compact-list"]; const availableViews = ["cards", "list", "compact-cards", "compact-list"];
const preferences = { ...defaultPreferences, ...readPreferences() }; const preferences = { ...defaultPreferences, ...readPreferences() };
const collapseState = readCollapseState();
const catalog = document.querySelector("[data-catalog-root]"); const catalog = document.querySelector("[data-catalog-root]");
const navTree = document.querySelector("[data-nav-tree]"); const navTree = document.querySelector("[data-nav-tree]");
const searchInput = document.querySelector("[data-search-input]"); const searchInput = document.querySelector("[data-search-input]");
@ -172,6 +174,9 @@ navTree.addEventListener("click", (event) => {
focusCatalogTarget(button.dataset.focusTarget); focusCatalogTarget(button.dataset.focusTarget);
}); });
navTree.addEventListener("toggle", handleDetailsToggle, true);
catalog.addEventListener("toggle", handleDetailsToggle, true);
settingsToggle.addEventListener("click", () => { settingsToggle.addEventListener("click", () => {
const isOpen = settingsPanel.classList.toggle("is-open"); const isOpen = settingsPanel.classList.toggle("is-open");
settingsOverlay.classList.toggle("is-open", isOpen); settingsOverlay.classList.toggle("is-open", isOpen);
@ -235,11 +240,12 @@ function createItem(number, title, type, tags = [], url = "#") {
function renderCatalog() { function renderCatalog() {
const query = normalize(searchInput.value); const query = normalize(searchInput.value);
const forceOpen = Boolean(query);
const fragment = document.createDocumentFragment(); const fragment = document.createDocumentFragment();
let visibleItems = 0; let visibleItems = 0;
ecosystemSections.forEach((section) => { ecosystemSections.forEach((section) => {
const sectionElement = createSectionElement(section); const sectionElement = createSectionElement(section, forceOpen);
let visibleGroups = 0; let visibleGroups = 0;
let sectionVisibleItems = 0; let sectionVisibleItems = 0;
@ -252,7 +258,7 @@ function renderCatalog() {
visibleGroups += 1; visibleGroups += 1;
sectionVisibleItems += filteredItems.length; sectionVisibleItems += filteredItems.length;
sectionElement.querySelector(".section-groups").append(createGroupElement(group, filteredItems)); sectionElement.querySelector(".section-groups").append(createGroupElement(group, filteredItems, forceOpen));
}); });
if (visibleGroups) { if (visibleGroups) {
@ -273,12 +279,13 @@ function renderCatalog() {
} }
function createSectionElement(section) { function createSectionElement(section, forceOpen = false) {
const sectionElement = document.createElement("details"); const sectionElement = document.createElement("details");
sectionElement.className = "catalog-section"; sectionElement.className = "catalog-section";
sectionElement.id = getSectionId(section); sectionElement.id = getSectionId(section);
sectionElement.dataset.collapseKey = sectionElement.id;
sectionElement.tabIndex = -1; sectionElement.tabIndex = -1;
sectionElement.open = true; sectionElement.open = forceOpen || getSavedOpenState(sectionElement.dataset.collapseKey);
sectionElement.innerHTML = ` sectionElement.innerHTML = `
<summary class="section-head"> <summary class="section-head">
<div> <div>
@ -296,12 +303,13 @@ function createSectionElement(section) {
return sectionElement; return sectionElement;
} }
function createGroupElement(group, items) { function createGroupElement(group, items, forceOpen = false) {
const groupElement = document.createElement("details"); const groupElement = document.createElement("details");
groupElement.className = "catalog-group"; groupElement.className = "catalog-group";
groupElement.id = getGroupId(group); groupElement.id = getGroupId(group);
groupElement.dataset.collapseKey = groupElement.id;
groupElement.tabIndex = -1; groupElement.tabIndex = -1;
groupElement.open = true; groupElement.open = forceOpen || getSavedOpenState(groupElement.dataset.collapseKey);
const cards = items.map(createCardElement); const cards = items.map(createCardElement);
groupElement.innerHTML = ` groupElement.innerHTML = `
@ -384,7 +392,8 @@ function renderNavigationTree() {
ecosystemSections.forEach((section) => { ecosystemSections.forEach((section) => {
const sectionNode = document.createElement("details"); const sectionNode = document.createElement("details");
sectionNode.className = "tree-section"; sectionNode.className = "tree-section";
sectionNode.open = true; sectionNode.dataset.collapseKey = getTreeSectionId(section);
sectionNode.open = getSavedOpenState(sectionNode.dataset.collapseKey);
const sectionSummary = document.createElement("summary"); const sectionSummary = document.createElement("summary");
sectionSummary.innerHTML = ` sectionSummary.innerHTML = `
@ -398,7 +407,8 @@ function renderNavigationTree() {
section.groups.forEach((group) => { section.groups.forEach((group) => {
const groupNode = document.createElement("details"); const groupNode = document.createElement("details");
groupNode.className = "tree-group"; groupNode.className = "tree-group";
groupNode.open = true; groupNode.dataset.collapseKey = getTreeGroupId(group);
groupNode.open = getSavedOpenState(groupNode.dataset.collapseKey);
const groupSummary = document.createElement("summary"); const groupSummary = document.createElement("summary");
groupSummary.innerHTML = ` groupSummary.innerHTML = `
@ -446,12 +456,34 @@ function focusCatalogTarget(targetId) {
target.closest(".catalog-section")?.setAttribute("open", ""); target.closest(".catalog-section")?.setAttribute("open", "");
target.closest(".catalog-group")?.setAttribute("open", ""); target.closest(".catalog-group")?.setAttribute("open", "");
persistOpenedAncestor(target.closest(".catalog-section"));
persistOpenedAncestor(target.closest(".catalog-group"));
target.scrollIntoView({ behavior: "smooth", block: "center" }); target.scrollIntoView({ behavior: "smooth", block: "center" });
target.focus({ preventScroll: true }); target.focus({ preventScroll: true });
target.classList.add("is-focused"); target.classList.add("is-focused");
window.setTimeout(() => target.classList.remove("is-focused"), 1200); window.setTimeout(() => target.classList.remove("is-focused"), 1200);
} }
function handleDetailsToggle(event) {
const details = event.target;
if (!(details instanceof HTMLDetailsElement) || !details.dataset.collapseKey) {
return;
}
collapseState[details.dataset.collapseKey] = details.open;
saveCollapseState();
}
function persistOpenedAncestor(details) {
if (!details?.dataset.collapseKey) {
return;
}
collapseState[details.dataset.collapseKey] = true;
saveCollapseState();
}
function attachCardInteractions() { function attachCardInteractions() {
document.querySelectorAll(".nav-card").forEach((card) => { document.querySelectorAll(".nav-card").forEach((card) => {
let leaveTimer; let leaveTimer;
@ -525,6 +557,14 @@ function getGroupId(group) {
return `group-${group.id}`; return `group-${group.id}`;
} }
function getTreeSectionId(section) {
return `tree-${getSectionId(section)}`;
}
function getTreeGroupId(group) {
return `tree-${getGroupId(group)}`;
}
function getItemId(item) { function getItemId(item) {
return `item-${item.number.replaceAll(".", "-")}`; return `item-${item.number.replaceAll(".", "-")}`;
} }
@ -545,6 +585,22 @@ function savePreferences(nextPreferences) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(nextPreferences)); localStorage.setItem(STORAGE_KEY, JSON.stringify(nextPreferences));
} }
function getSavedOpenState(key) {
return collapseState[key] ?? true;
}
function readCollapseState() {
try {
return JSON.parse(localStorage.getItem(COLLAPSE_STORAGE_KEY)) || {};
} catch {
return {};
}
}
function saveCollapseState() {
localStorage.setItem(COLLAPSE_STORAGE_KEY, JSON.stringify(collapseState));
}
function closeSettingsPanel() { function closeSettingsPanel() {
settingsPanel.classList.remove("is-open"); settingsPanel.classList.remove("is-open");
settingsOverlay.classList.remove("is-open"); settingsOverlay.classList.remove("is-open");