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