fix(theme): 0.3.1 — interactive-render nav backstop (issue #6)

Under an interactive Blazor render mode the runtime replaces the prerendered
<details> after DOMContentLoaded, so nav-state.js (wired on load, re-run only on
'enhancedload') never wires the live rail — no aria sync, no persistence, no
active-reveal — and native <details> content-hiding is unreliable, leaving a
collapsed section's items visible. 0.3.1:
- nav-state.js: add a MutationObserver backstop that re-runs apply() when
  details.rail-section nodes are (re)inserted; idempotent via the per-element
  init guard, loop-safe (childList-only + active-reveal's !open guard).
- layout.css: explicit .rail-section:not([open]) > .rail-section-body{display:none}
  so visual collapse works across all render modes.
- themeissues.md: document issue #6; Directory.Build.props 0.3.0 -> 0.3.1.
48 bUnit tests green.
This commit is contained in:
Joseph Doherty
2026-06-05 07:18:30 -04:00
parent 0e41e7c2e4
commit f1efe6e081
4 changed files with 122 additions and 1 deletions
@@ -210,6 +210,13 @@
.rail-section > summary::-webkit-details-marker { display: none; }
.rail-section > summary::before { content: '\25B6'; font-size: 0.55rem; color: var(--ink-faint); margin-right: 0.4rem; }
.rail-section[open] > summary::before { content: '\25BC'; }
/* Hide a collapsed section's items explicitly. The browser's built-in
<details> content-hiding (::details-content content-visibility:hidden) is
unreliable once an interactive framework (e.g. Blazor InteractiveServer)
owns/re-renders the native <details> — a closed section can otherwise keep
showing its items under a "collapsed" chevron. An explicit display:none makes
the visual collapse work across all render modes (kit issue #6). */
.rail-section:not([open]) > .rail-section-body { display: none; }
/* StatusPill: info variant (on-palette, reuses the info blue wash) */
.chip-info { color: var(--accent-deep); background: var(--info-bg); border-color: var(--info-border); }
@@ -65,4 +65,31 @@
if (window.Blazor && typeof window.Blazor.addEventListener === "function") {
window.Blazor.addEventListener("enhancedload", apply);
}
// Re-run whenever rail sections are (re)inserted into the DOM. Under an
// interactive render mode (Blazor InteractiveServer/WebAssembly/Auto) the
// prerendered <details> wired on DOMContentLoaded are replaced when the
// runtime adopts the page, and `enhancedload` does NOT fire — so without this
// the live sections are never wired (no persistence, no aria sync, no
// active-reveal). A MutationObserver is the render-mode-agnostic backstop;
// the per-element INIT_ATTR guard keeps re-applies idempotent, and the
// childList-only filter (plus the active-reveal's `if (!sec.open)` guard)
// avoids any observe→mutate→observe loop (issue #6).
if (typeof MutationObserver === "function") {
var observer = new MutationObserver(function (mutations) {
for (var i = 0; i < mutations.length; i++) {
var added = mutations[i].addedNodes;
for (var j = 0; j < added.length; j++) {
var node = added[j];
if (node.nodeType !== 1) continue;
if ((node.matches && node.matches("details.rail-section")) ||
(node.querySelector && node.querySelector("details.rail-section"))) {
apply();
return;
}
}
}
});
observer.observe(document.documentElement, { childList: true, subtree: true });
}
})();