bugfix and phone view optimizations
This commit is contained in:
283
app/ui-chrome.js
283
app/ui-chrome.js
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user