[object Object]

← back to Socrata Cre Viewer

nav-agent: sync v1.3 (host left-sidebar collapse on load)

bbdc14f0a6b3b7fa75ca5475fc67bf16bff8c779 · 2026-08-20 08:14:35 -0700 · Steve Abrams

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit bbdc14f0a6b3b7fa75ca5475fc67bf16bff8c779
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Aug 20 08:14:35 2026 -0700

    nav-agent: sync v1.3 (host left-sidebar collapse on load)
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 public/nav-agent/nav-agent.css | 11 ++++++
 public/nav-agent/nav-agent.js  | 79 ++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 88 insertions(+), 2 deletions(-)

diff --git a/public/nav-agent/nav-agent.css b/public/nav-agent/nav-agent.css
index c80b88a..d43a0d1 100644
--- a/public/nav-agent/nav-agent.css
+++ b/public/nav-agent/nav-agent.css
@@ -119,4 +119,15 @@ table .na-group-h td{padding:8px 6px;color:var(--na-acc);font-weight:600;border-
 .na-lmk{color:var(--na-mut);font-size:10px;text-transform:uppercase;letter-spacing:.5px;margin-right:5px}
 .na-lempty{padding:40px;text-align:center;color:var(--na-mut)}
 
+/* host left-sidebar collapse (v1.3) — always collapsed on load, ☰ reveals */
+.na-side{transition:transform .18s ease}
+.na-side.na-side-collapsed{transform:translateX(-100%)!important;pointer-events:none}
+.na-side-main{transition:margin-left .18s ease}
+.na-side-main.na-side-shift{margin-left:0!important}
+.na-side-toggle{position:fixed;left:10px;top:10px;z-index:2147483002;width:38px;height:34px;
+  display:flex;align-items:center;justify-content:center;border-radius:8px;
+  border:1px solid var(--na-line);background:var(--na-bg2);color:var(--na-fg);
+  font-size:16px;line-height:1;cursor:pointer;box-shadow:0 2px 8px rgba(0,0,0,.28)}
+.na-side-toggle:hover,.na-side-toggle.on{border-color:var(--na-acc);color:var(--na-acc)}
+
 @media (max-width:640px){.na-panel{width:86vw}.na-lmain{min-width:100%}}
diff --git a/public/nav-agent/nav-agent.js b/public/nav-agent/nav-agent.js
index 4c16ce8..f955268 100644
--- a/public/nav-agent/nav-agent.js
+++ b/public/nav-agent/nav-agent.js
@@ -78,10 +78,13 @@
     var cards = [].slice.call(target.children).filter(function (c) { return c.nodeType === 1 && !c.classList.contains('na-group-h') && !c.classList.contains('na-group-body'); });
     var sample = cards.slice(0, 24), seen = {}, order = [];
     sample.forEach(function (card) {
-      [].slice.call(card.querySelectorAll('[data-field]')).forEach(function (n) {
+      var dfCells = [].slice.call(card.querySelectorAll('[data-field]'));
+      dfCells.forEach(function (n) {
         var k = n.getAttribute('data-field'); if (!seen[k]) { seen[k] = { key: slug(k), label: k, cls: '[data-field="' + k + '"]', df: k, hits: 0 }; order.push(seen[k]); } seen[k].hits++;
       });
-      // also collect direct-child element classes as coarse fields when no data-field present
+      // coarse child-class fields ONLY when this card tags no [data-field] cells —
+      // otherwise a cell with both a class and a data-field yields a duplicate field.
+      if (dfCells.length) return;
       [].slice.call(card.children).forEach(function (n) {
         var cls = (n.className && typeof n.className === 'string') ? n.className.split(/\s+/)[0] : '';
         if (!cls || cls.indexOf('na-') === 0) return;
@@ -508,13 +511,85 @@
   };
   window.NavAgent = API;
 
+  /* -- host left-sidebar collapse (standard feature, v1.3, 2026-08-20) ----
+   * The Nav Agent collapses the host page's primary LEFT sidebar on load and
+   * adds a ☰ toggle to reveal/hide it. ALWAYS collapsed on load — the reveal is
+   * an in-session choice (deliberately NOT persisted), so every page load starts
+   * clean and collapsed. Runs even on pages with no data grid.
+   *   Opt out:  <body data-nav-sidebar="off">
+   *   Force:    <body data-nav-sidebar="#mySelector">  or  <aside data-nav-sidebar>
+   *   Content:  the shifted main column auto-detects; force with [data-nav-main]. */
+  function naIsSidebar(e) {
+    if (!e || e === document.body) return false;
+    if (e.classList && (e.classList.contains('na-panel') || e.classList.contains('na-list') ||
+        e.classList.contains('na-launch') || e.classList.contains('na-side-toggle'))) return false;
+    var cs; try { cs = getComputedStyle(e); } catch (x) { return false; }
+    if (['fixed', 'sticky', 'absolute'].indexOf(cs.position) < 0) return false;
+    var r = e.getBoundingClientRect();
+    var atLeft = r.left <= 6;
+    var tall = r.height >= Math.min(window.innerHeight * 0.55, 380);
+    var narrow = r.width > 20 && r.width <= 360;
+    var hasNav = e.querySelectorAll('a,button,nav,.nav').length >= 1;
+    return atLeft && tall && narrow && hasNav;
+  }
+  function naFindSidebar() {
+    var body = document.body, a = body.getAttribute('data-nav-sidebar');
+    if (a === 'off') return null;
+    if (a && a !== 'on' && a !== '') { var s = document.querySelector(a); if (s) return s; }
+    var marked = document.querySelector('[data-nav-sidebar]');
+    if (marked && marked !== body) return marked;
+    var sel = ['#side', '#sidebar', '.sidebar', '#side-nav', '.side-nav', '#leftnav', '.leftnav', 'aside'];
+    for (var i = 0; i < sel.length; i++) { var e = document.querySelector(sel[i]); if (naIsSidebar(e)) return e; }
+    var kids = [].slice.call(body.children);
+    for (var j = 0; j < kids.length; j++) { if (naIsSidebar(kids[j])) return kids[j]; }
+    return null;
+  }
+  function naFindMain(side) {
+    var m = document.querySelector('[data-nav-main]'); if (m) return m;
+    var w = side.getBoundingClientRect().width;
+    var pool = [].slice.call(document.body.children).filter(function (c) {
+      return c.nodeType === 1 && c !== side &&
+        !(c.classList && (c.classList.contains('na-panel') || c.classList.contains('na-launch') ||
+          c.classList.contains('na-side-toggle') || c.classList.contains('na-list')));
+    });
+    for (var i = 0; i < pool.length; i++) {
+      var ml = parseFloat(getComputedStyle(pool[i]).marginLeft) || 0;
+      if (ml > 40 && Math.abs(ml - w) <= 28) return pool[i];
+    }
+    return side.nextElementSibling || null;
+  }
+  function naMountSidebar() {
+    try {
+      if (document.body.getAttribute('data-nav-agent') === 'off') return;
+      if (document.querySelector('.na-side-toggle')) return;   // once per page
+      var side = naFindSidebar(); if (!side) return;
+      side.classList.add('na-side');
+      var main = naFindMain(side); if (main) main.classList.add('na-side-main');
+      var open = false;                                        // ALWAYS collapsed on load
+      var tog = el('button', 'na-side-toggle', '☰'); tog.type = 'button';
+      function apply() {
+        side.classList.toggle('na-side-collapsed', !open);
+        if (main) main.classList.toggle('na-side-shift', !open);
+        tog.classList.toggle('on', open);
+        tog.title = open ? 'Collapse sidebar' : 'Expand sidebar';
+        tog.setAttribute('aria-expanded', open ? 'true' : 'false');
+      }
+      tog.onclick = function () { open = !open; apply(); };
+      document.body.appendChild(tog);
+      apply();
+    } catch (e) { if (window.console) console.warn('[nav-agent] sidebar collapse skipped:', e); }
+  }
+  API.collapseSidebar = naMountSidebar;
+
   function boot() {
     try {
       var body = document.body;
       if (!body || body.getAttribute('data-nav-agent') === 'off') return;
+      naMountSidebar(); // collapse the host left sidebar on load (grid-independent)
       // wait a tick for API-driven grids to render their first batch
       var tries = 0;
       (function attempt() {
+        naMountSidebar(); // idempotent — catches a late-rendered sidebar too
         var t = findTarget();
         var populated = false;
         if (t) {

← 4606272 nav-agent: List data full-width tight columns (grid) — layou  ·  back to Socrata Cre Viewer  ·  creds-safe fetch guard: resolve relative fetch vs credential 0299543 →