bugfix and phone view optimizations

This commit is contained in:
2026-03-12 02:35:02 -07:00
parent d3d96912c1
commit c6b21095d9
5 changed files with 561 additions and 21 deletions

View File

@@ -5,6 +5,40 @@
const DETAIL_COLLAPSE_STORAGE_PREFIX = "tarot-detail-collapsed:";
const DEFAULT_DATASET_ENTRY_COLLAPSED = true;
const DEFAULT_DATASET_DETAIL_COLLAPSED = false;
const MOBILE_TOPBAR_MEDIA_QUERY = "(max-width: 900px)";
const sidebarControllers = new WeakMap();
const detailControllers = new WeakMap();
const AUTO_COLLAPSE_ENTRY_SELECTOR = [
".planet-list-item",
".tarot-list-item",
"[role='option']",
".kab-node[data-sephira]",
".kab-path-hit[data-path]",
".kab-path-tarot[data-path]",
".kab-rose-petal[data-path]",
".cube-face[role='button']",
".cube-edge-line[role='button']",
".cube-direction[role='button']",
".cube-connector[role='button']",
".cube-center[role='button']",
".kab-chip[data-path]"
].join(", ");
const AUTO_COLLAPSE_IGNORE_SELECTOR = [
".sidebar-toggle-inline",
".sidebar-popout-open",
".detail-toggle-inline",
".detail-popout-open",
"input",
"select",
"textarea",
"label",
"form",
".dataset-search-wrap",
".alpha-text-search-controls",
".cube-rotation-controls",
".cube-rotation-btn",
".tarot-house-action-btn"
].join(", ");
function loadSidebarCollapsedState(storageKey) {
try {
@@ -29,6 +63,128 @@
}
}
function resolveLayoutTarget(target) {
if (target instanceof HTMLElement) {
if (target.matches(".planet-layout, .tarot-layout, .kab-layout")) {
return target;
}
return target.closest(".planet-layout, .tarot-layout, .kab-layout");
}
if (typeof target === "string" && target) {
const element = document.getElementById(target);
if (element instanceof HTMLElement) {
if (element.matches(".planet-layout, .tarot-layout, .kab-layout")) {
return element;
}
return element.querySelector(".planet-layout, .tarot-layout, .kab-layout")
|| element.closest(".planet-layout, .tarot-layout, .kab-layout");
}
}
return null;
}
function setSidebarCollapsed(target, collapsed, persist = true) {
const layout = resolveLayoutTarget(target);
const controller = layout ? sidebarControllers.get(layout) : null;
if (!controller) {
return false;
}
controller.applyCollapsedState(Boolean(collapsed), persist);
return true;
}
function setDetailCollapsed(target, collapsed, persist = true) {
const layout = resolveLayoutTarget(target);
const controller = layout ? detailControllers.get(layout) : null;
if (!controller) {
return false;
}
controller.applyCollapsedState(Boolean(collapsed), persist);
return true;
}
function showDetailOnly(target, persist = true) {
const layout = resolveLayoutTarget(target);
if (!layout) {
return false;
}
const detailChanged = setDetailCollapsed(layout, false, persist);
const sidebarChanged = setSidebarCollapsed(layout, true, persist);
return detailChanged || sidebarChanged;
}
function shouldAutoCollapseFromEvent(panel, target) {
if (!(panel instanceof HTMLElement) || !(target instanceof Element) || !panel.contains(target)) {
return false;
}
if (target.closest(AUTO_COLLAPSE_IGNORE_SELECTOR)) {
return false;
}
return Boolean(target.closest(AUTO_COLLAPSE_ENTRY_SELECTOR));
}
function scheduleAutoCollapse(layout) {
if (!(layout instanceof HTMLElement)) {
return;
}
window.requestAnimationFrame(() => {
showDetailOnly(layout);
});
}
function initializeSidebarAutoCollapse() {
const layouts = document.querySelectorAll(".planet-layout, .tarot-layout, .kab-layout");
layouts.forEach((layout) => {
if (!(layout instanceof HTMLElement)) {
return;
}
const panel = Array.from(layout.children).find((child) => (
child instanceof HTMLElement
&& child.matches("aside.planet-list-panel, aside.tarot-list-panel, aside.kab-tree-panel")
));
if (!(panel instanceof HTMLElement) || panel.dataset.sidebarAutoCollapseReady === "1") {
return;
}
panel.dataset.sidebarAutoCollapseReady = "1";
panel.addEventListener("click", (event) => {
const target = event.target instanceof Element ? event.target : null;
if (!shouldAutoCollapseFromEvent(panel, target)) {
return;
}
scheduleAutoCollapse(layout);
});
panel.addEventListener("keydown", (event) => {
if (event.key !== "Enter" && event.key !== " ") {
return;
}
const target = event.target instanceof Element ? event.target : null;
if (!shouldAutoCollapseFromEvent(panel, target)) {
return;
}
scheduleAutoCollapse(layout);
});
});
}
function initializeSidebarPopouts() {
const layouts = document.querySelectorAll(".planet-layout, .tarot-layout, .kab-layout");
@@ -87,6 +243,14 @@
}
};
sidebarControllers.set(layout, {
applyCollapsedState,
panel,
collapseBtn,
openBtn,
storageKey
});
collapseBtn.addEventListener("click", () => {
applyCollapsedState(true);
});
@@ -173,6 +337,14 @@
}
};
detailControllers.set(layout, {
applyCollapsedState,
detailPanel,
collapseBtn,
openBtn,
detailStorageKey
});
collapseBtn.addEventListener("click", () => {
applyCollapsedState(true);
});
@@ -202,6 +374,103 @@
}
}
function getTopbarElements() {
const topbarEl = document.querySelector(".topbar");
const actionsEl = document.getElementById("topbar-actions");
const menuToggleEl = document.getElementById("topbar-menu-toggle");
return {
topbarEl: topbarEl instanceof HTMLElement ? topbarEl : null,
actionsEl: actionsEl instanceof HTMLElement ? actionsEl : null,
menuToggleEl: menuToggleEl instanceof HTMLButtonElement ? menuToggleEl : null
};
}
function isMobileTopbarViewport() {
return window.matchMedia(MOBILE_TOPBAR_MEDIA_QUERY).matches;
}
function setTopbarMenuOpen(isOpen) {
const { topbarEl, menuToggleEl } = getTopbarElements();
if (!(topbarEl instanceof HTMLElement) || !(menuToggleEl instanceof HTMLButtonElement)) {
return;
}
const nextOpen = Boolean(isOpen);
topbarEl.classList.toggle("is-menu-open", nextOpen);
menuToggleEl.setAttribute("aria-expanded", nextOpen ? "true" : "false");
menuToggleEl.textContent = nextOpen ? "Close" : "Menu";
menuToggleEl.setAttribute("aria-label", nextOpen ? "Close navigation menu" : "Open navigation menu");
if (!nextOpen) {
closeTopbarDropdowns();
}
}
function bindTopbarMobileMenu() {
const { topbarEl, actionsEl, menuToggleEl } = getTopbarElements();
if (!(topbarEl instanceof HTMLElement) || !(actionsEl instanceof HTMLElement) || !(menuToggleEl instanceof HTMLButtonElement)) {
return;
}
if (menuToggleEl.dataset.mobileMenuReady === "1") {
return;
}
menuToggleEl.dataset.mobileMenuReady = "1";
setTopbarMenuOpen(false);
menuToggleEl.addEventListener("click", (event) => {
event.stopPropagation();
const nextOpen = !topbarEl.classList.contains("is-menu-open");
setTopbarMenuOpen(nextOpen);
});
actionsEl.addEventListener("click", (event) => {
const button = event.target instanceof Element
? event.target.closest("button")
: null;
if (!(button instanceof HTMLButtonElement)) {
return;
}
const isDropdownTrigger = button.getAttribute("aria-haspopup") === "menu";
const isMenuItem = button.getAttribute("role") === "menuitem";
if (!isDropdownTrigger || isMenuItem) {
window.requestAnimationFrame(() => {
if (isMobileTopbarViewport()) {
setTopbarMenuOpen(false);
}
});
}
});
document.addEventListener("click", (event) => {
const clickTarget = event.target;
if (!isMobileTopbarViewport()) {
return;
}
if (clickTarget instanceof Node && topbarEl.contains(clickTarget)) {
return;
}
setTopbarMenuOpen(false);
});
window.addEventListener("resize", () => {
if (!isMobileTopbarViewport()) {
setTopbarMenuOpen(false);
}
});
document.addEventListener("keydown", (event) => {
if (event.key === "Escape") {
setTopbarMenuOpen(false);
}
});
}
function closeTopbarDropdowns(exceptEl = null) {
const topbarDropdownEls = Array.from(document.querySelectorAll(".topbar-dropdown"));
topbarDropdownEls.forEach((dropdownEl) => {
@@ -227,10 +496,16 @@
setTopbarDropdownOpen(dropdownEl, false);
dropdownEl.addEventListener("mouseenter", () => {
if (isMobileTopbarViewport()) {
return;
}
setTopbarDropdownOpen(dropdownEl, true);
});
dropdownEl.addEventListener("mouseleave", () => {
if (isMobileTopbarViewport()) {
return;
}
setTopbarDropdownOpen(dropdownEl, false);
});
@@ -275,6 +550,8 @@
function init() {
initializeSidebarPopouts();
initializeDetailPopouts();
initializeSidebarAutoCollapse();
bindTopbarMobileMenu();
bindTopbarDropdownInteractions();
}
@@ -283,6 +560,12 @@
init,
initializeSidebarPopouts,
initializeDetailPopouts,
initializeSidebarAutoCollapse,
bindTopbarMobileMenu,
setSidebarCollapsed,
setDetailCollapsed,
showDetailOnly,
setTopbarMenuOpen,
setTopbarDropdownOpen,
closeTopbarDropdowns,
bindTopbarDropdownInteractions