// Mobile off-canvas sidebar (issue #35); on desktop the `drawer-open` class is // inert (CSS gates the drawer styling). // // Anything that puts a new view in the content area must close the drawer, or // it renders behind the sidebar and the tap reads as a dead button. "Spawn // similar" sits inside a session row but stops propagation, so it never reaches // the row's own close — both spawn paths arrive here via document events. import { OPEN_SPAWN_EVENT, SPAWN_SIMILAR_EVENT } from "./catalog.js"; /** Toggling the drawer from outside the shell (the pane header's ☰ on mobile), * without threading the controller through every view. */ export const TOGGLE_DRAWER_EVENT = "maestro-toggle-drawer"; export function requestToggleDrawer(): void { document.dispatchEvent(new CustomEvent(TOGGLE_DRAWER_EVENT)); } export interface Drawer { open(): void; close(): void; toggle(): void; isOpen(): boolean; } export function installDrawer( shellEl: HTMLElement, sidebarEl: HTMLElement, backdropEl: HTMLElement, ): Drawer { const drawer: Drawer = { open: () => shellEl.classList.add("drawer-open"), close: () => shellEl.classList.remove("drawer-open"), toggle: () => shellEl.classList.toggle("drawer-open"), isOpen: () => shellEl.classList.contains("drawer-open"), }; backdropEl.addEventListener("click", () => drawer.close()); sidebarEl.addEventListener("click", (e) => { if ((e.target as HTMLElement).closest(".tree-leaf")) drawer.close(); }); document.addEventListener(TOGGLE_DRAWER_EVENT, () => drawer.toggle()); document.addEventListener(OPEN_SPAWN_EVENT, () => drawer.close()); document.addEventListener(SPAWN_SIMILAR_EVENT, () => drawer.close()); return drawer; } /** Horizontal swipes drive the drawer (right opens, left closes) rather than * the browser's back/forward navigation. Only a clearly horizontal gesture past * a threshold counts, so vertical terminal scrolling (which owns its own touch * handler) is untouched. */ export function installDrawerSwipe(drawer: Drawer): void { const mobileMq = window.matchMedia("(max-width: 859px)"); let sx = 0; let sy = 0; let tracking = false; document.addEventListener( "touchstart", (e) => { tracking = mobileMq.matches && e.touches.length === 1; if (tracking) { sx = e.touches[0].clientX; sy = e.touches[0].clientY; } }, { passive: true }, ); document.addEventListener( "touchend", (e) => { if (!tracking) return; tracking = false; const t = e.changedTouches[0]; const dx = t.clientX - sx; const dy = t.clientY - sy; if (Math.abs(dx) < 60 || Math.abs(dx) < Math.abs(dy) * 1.5) return; if (dx > 0 && !drawer.isOpen()) drawer.open(); else if (dx < 0 && drawer.isOpen()) drawer.close(); }, { passive: true }, ); }