diff --git a/dashboard/src/components/improvement-modal.js b/dashboard/src/components/improvement-modal.js index 9cbe0b6..e37ad50 100644 --- a/dashboard/src/components/improvement-modal.js +++ b/dashboard/src/components/improvement-modal.js @@ -144,14 +144,17 @@ function _ensureStyles() { .impr-mode-shift, .impr-mode-shift * { cursor: copy !important; } -/* Highlight "widget" elements so the user sees what can be annotated */ +/* Highlight annotatable elements in main content, left nav, and right TOC */ .impr-mode-shift #observablehq-main figure, .impr-mode-shift #observablehq-main h2, .impr-mode-shift #observablehq-main h3, .impr-mode-shift #observablehq-main h4, .impr-mode-shift #observablehq-main [data-widget-name], .impr-mode-shift #observablehq-sidebar a, -.impr-mode-shift #observablehq-sidebar summary { +.impr-mode-shift #observablehq-sidebar summary, +.impr-mode-shift #observablehq-toc a, +.impr-mode-shift #observablehq-toc .kpi-infobox, +.impr-mode-shift #observablehq-toc [id] { outline: 1px dashed rgba(99, 102, 241, 0.45); background: rgba(99, 102, 241, 0.055) !important; border-radius: 4px; @@ -163,7 +166,19 @@ function _ensureStyles() { /* ── Widget name inference ─────────────────────────────────────────────── */ function _inferWidgetName(target) { - // 0. Sidebar navigation: nav link text or section heading + // 0a. Right-margin TOC: link text, KPI box title, or nearest labelled container + if (target.closest("#observablehq-toc")) { + const link = target.closest("a"); + if (link) return (link.textContent.trim() || "TOC link") + " (TOC)"; + const kpiTitle = target.closest(".kpi-infobox") + ?.querySelector(".kpi-infobox-title"); + if (kpiTitle) return kpiTitle.textContent.trim() + " (sidebar widget)"; + const labelled = target.closest("[id]"); + if (labelled) return labelled.id.replace(/-/g, " ") + " (TOC)"; + return "Right margin"; + } + + // 0b. Left sidebar navigation: nav link text or section heading if (target.closest("#observablehq-sidebar")) { const link = target.closest("a"); if (link) return link.textContent.trim() || "Nav link"; @@ -232,12 +247,20 @@ export function initImprovementModal({ apiBase = "http://127.0.0.1:8000", domain _initialized = true; _ensureStyles(); - // Track modifier state via keydown, keyup, AND mousemove so mode - // stays in sync even when focus changes between elements. + // Track modifier state. Highlighting is delayed 1 s so normal Shift+typing + // doesn't trigger the visual mode change; releasing Shift cancels immediately. + let _shiftTimer = null; function _updateMode(e) { if (e.shiftKey) { - document.body.classList.add("impr-mode-shift"); + if (!_shiftTimer && !document.body.classList.contains("impr-mode-shift")) { + _shiftTimer = setTimeout(() => { + document.body.classList.add("impr-mode-shift"); + _shiftTimer = null; + }, 1000); + } } else { + clearTimeout(_shiftTimer); + _shiftTimer = null; document.body.classList.remove("impr-mode-shift"); } } @@ -245,23 +268,27 @@ export function initImprovementModal({ apiBase = "http://127.0.0.1:8000", domain window.addEventListener("keyup", _updateMode); window.addEventListener("mousemove", _updateMode); // Clear on blur in case Shift is held when the window loses focus - window.addEventListener("blur", () => document.body.classList.remove("impr-mode-shift")); + window.addEventListener("blur", () => { + clearTimeout(_shiftTimer); + _shiftTimer = null; + document.body.classList.remove("impr-mode-shift"); + }); document.addEventListener("click", (e) => { if (!e.shiftKey) return; const inSidebar = !!e.target.closest("#observablehq-sidebar"); - // Block shift-clicks on form controls; allow sidebar links (preventDefault stops navigation) - if (!inSidebar && e.target.matches("input, textarea, select, a, button")) return; - if (inSidebar && e.target.matches("input, textarea, select")) return; + const inToc = !!e.target.closest("#observablehq-toc"); + // Block shift-clicks on form controls; allow nav/toc links (preventDefault stops navigation) + if (!inSidebar && !inToc && e.target.matches("input, textarea, select, a, button")) return; + if ((inSidebar || inToc) && e.target.matches("input, textarea, select")) return; e.preventDefault(); const widgetName = _inferWidgetName(e.target); - const pageName = inSidebar - ? "Navigation" - : (document.title - ? document.title.replace(" – Custodian State Hub", "").trim() - : (location.pathname.replace(/^\//, "") || "Overview")); + const currentPage = document.title + ? document.title.replace(" – Custodian State Hub", "").trim() + : (location.pathname.replace(/^\//, "") || "Overview"); + const pageName = inSidebar ? "Navigation" : currentPage; // Remove any open modal document.getElementById("_impr-root")?.remove();