Docs: Improved the navigation seperating out ToC from top nac bar

This commit is contained in:
2026-03-09 23:13:06 -04:00
parent a41fc02f11
commit 01a2a03e2e
4 changed files with 319 additions and 424 deletions
+25 -349
View File
@@ -10,6 +10,9 @@
* - When the reader scrolls past a section, any items that were only
* auto-expanded (not user-pinned) collapse back down.
* - Works with mkdocs-material instant-loading / SPA navigation.
*
* Page navigation is handled by the top navbar tabs + dropdowns.
* This script only manages the TOC (md-nav--secondary) in the left sidebar.
*/
(function () {
"use strict";
@@ -68,11 +71,9 @@
//
// Scans `containerEl` for <li> items with nested <nav> children and
// turns them into collapsible items: adds the --nested class, collapses
// them, and injects a toggle chevron. This is used both for the active
// page's built-in TOC (md-nav--secondary) and for TOC items lazily
// injected into converted single-page nav sections.
// them, and injects a toggle chevron.
function makeItemsCollapsible(containerEl, trackPinning) {
function makeItemsCollapsible(containerEl) {
containerEl.querySelectorAll("li.md-nav__item").forEach(function (li) {
var nestedNav = li.querySelector(":scope > nav.md-nav, :scope > .md-nav");
if (!nestedNav) return;
@@ -101,10 +102,10 @@
var isCollapsed = li.classList.contains("toc-collapsed");
if (isCollapsed) {
expand(li);
if (trackPinning) userPinned.add(li);
userPinned.add(li);
} else {
collapse(li);
if (trackPinning) userPinned.delete(li);
userPinned.delete(li);
}
});
}
@@ -121,25 +122,27 @@
userPinned.clear();
autoExpanded.clear();
// Force the TOC visible. Material's base CSS hides the secondary
// nav via a sibling-combinator rule keyed off the #__toc checkbox.
// We force-check it and set inline display so the TOC is guaranteed
// visible regardless of the CSS cascade.
var tocCheckbox = sidebar.querySelector("#__toc");
if (tocCheckbox) {
tocCheckbox.checked = true;
}
// Find the actual TOC (md-nav--secondary). We scope to the secondary
// nav to avoid touching the page-level nav items that wrap sections
// those use Material's native checkbox-based toggle and should not be
// overridden.
//
// NOTE: The primary nav already has .md-nav--integrated added by
// mkdocs-material when the toc.integrate feature is enabled. We do
// NOT re-add that class or extend it to descendant navs — doing so
// causes the CSS rule ".md-nav--integrated .md-nav__item--nested >
// .md-nav { display: block }" to override Material's native
// checkbox-based collapse on primary nav sections (Reference,
// Development, Architecture Decisions, etc.), creating whitespace
// below collapsed section headings.
// nav to avoid touching the page-level nav items that wrap sections.
var tocNav = sidebar.querySelector("nav.md-nav--secondary");
if (!tocNav) return;
tocNav.style.display = "block";
tocNav.style.opacity = "1";
tocNav.style.visibility = "visible";
// Make all nested TOC items collapsible, with pinning support for
// the active-link watcher.
makeItemsCollapsible(tocNav, true);
makeItemsCollapsible(tocNav);
// Kick off the active-link watcher
watchActiveLink();
@@ -229,336 +232,14 @@
});
}
// ── Convert leaf-page nav items into section-like structures ─────────
//
// Material renders leaf-page nav items (any <li> with just an <a> link
// and no nested <nav>) as flat links with no expand/collapse chevron.
// Only native sections (Reference, Development, Architecture Decisions)
// get a checkbox + label + nested <nav>.
//
// This function converts leaf-page items at two levels:
// 1. Top-level items (Specification, Work Remaining, FAQ)
// 2. Children inside native sections (Action CLI, Session Model, etc.)
//
// It explicitly avoids touching items inside md-nav--secondary (the TOC)
// or md-nav--page-toc (our own lazy-loaded TOC containers).
//
// The ACTIVE page (wherever it sits in the tree) is treated specially:
// it becomes an always-expanded, non-collapsible section with a
// <label> heading instead of an <a>, matching how Material handles
// native sections that contain the current page.
/** Counter for unique checkbox IDs across conversions. */
var pageNavCounter = 100;
/** Cache of fetched TOC HTML keyed by page URL. */
var tocCache = {};
/**
* Collect the <ul> lists whose direct <li> children should be
* converted. This includes:
* - The top-level nav list (direct child of md-nav--primary)
* - The child lists inside native sections (level-1 <nav> elements
* that are NOT md-nav--secondary and NOT md-nav--page-toc)
*/
function collectConvertibleLists(primaryNav) {
var lists = [];
// 1. Top-level list
var topList = primaryNav.querySelector(":scope > .md-nav__list");
if (topList) lists.push(topList);
// 2. Lists inside native sections (one level deep)
if (topList) {
topList.querySelectorAll(":scope > li.md-nav__item--nested").forEach(function (sectionLi) {
var sectionNav = sectionLi.querySelector(":scope > nav.md-nav:not(.md-nav--secondary):not(.md-nav--page-toc)");
if (sectionNav) {
var sectionList = sectionNav.querySelector(":scope > .md-nav__list");
if (sectionList) lists.push(sectionList);
}
});
}
return lists;
}
/**
* Convert leaf-page <li> items into section-like expandable structures.
* Only operates on items in the top-level nav and inside native sections.
*/
function convertPageItems() {
var primaryNav = document.querySelector("nav.md-nav--primary");
if (!primaryNav) return;
var lists = collectConvertibleLists(primaryNav);
lists.forEach(function (ul) {
// Only process DIRECT children of each list
var children = ul.querySelectorAll(":scope > li.md-nav__item");
children.forEach(function (li) {
// Skip items already converted or that are native sections
if (li.classList.contains("md-nav__item--nested")) return;
// Active page items need special handling — always-expanded,
// non-collapsible, matching native section behaviour.
if (li.classList.contains("md-nav__item--active")) {
convertActivePageItem(li);
return;
}
convertInactivePageItem(li);
});
});
}
/**
* Convert an INACTIVE leaf-page <li> into a section-like structure
* with a checkbox + label + nested nav that lazy-loads the page's TOC.
*/
function convertInactivePageItem(li) {
var link = li.querySelector(":scope > a.md-nav__link");
if (!link) return;
var href = link.getAttribute("href");
var text = link.textContent.trim();
var navId = "__nav_" + (pageNavCounter++);
// Build the checkbox
var checkbox = document.createElement("input");
checkbox.className = "md-nav__toggle md-toggle";
checkbox.type = "checkbox";
checkbox.id = navId;
// Build the label (with chevron icon) — identical to native sections
var label = document.createElement("label");
label.className = "md-nav__link";
label.setAttribute("for", navId);
label.tabIndex = 0;
var ellipsis = document.createElement("span");
ellipsis.className = "md-ellipsis";
ellipsis.textContent = text;
label.appendChild(ellipsis);
var icon = document.createElement("span");
icon.className = "md-nav__icon md-icon";
label.appendChild(icon);
// Build the nested nav with the original link as first child
var nav = document.createElement("nav");
nav.className = "md-nav md-nav--page-toc";
nav.setAttribute("data-md-level", "1");
nav.setAttribute("aria-expanded", "false");
var ul = document.createElement("ul");
ul.className = "md-nav__list";
// First child: a link to the page itself (like section index pages)
var indexLi = document.createElement("li");
indexLi.className = "md-nav__item";
var indexLink = document.createElement("a");
indexLink.className = "md-nav__link";
indexLink.href = href;
var indexSpan = document.createElement("span");
indexSpan.className = "md-ellipsis";
indexSpan.textContent = text;
indexLink.appendChild(indexSpan);
indexLi.appendChild(indexLink);
ul.appendChild(indexLi);
nav.appendChild(ul);
// Replace the <li> contents
li.innerHTML = "";
li.classList.add("md-nav__item--nested");
li.appendChild(checkbox);
li.appendChild(label);
li.appendChild(nav);
// Lazy-load the TOC when first expanded
checkbox.addEventListener("change", function () {
if (!checkbox.checked) return;
if (tocCache[href]) {
injectToc(ul, tocCache[href]);
return;
}
loadToc(href, ul);
});
}
/**
* Convert an ACTIVE page item to look like a native expanded section.
*
* Material emits a __toc checkbox + label (with a TOC-list icon) plus
* an <a> link and the md-nav--secondary TOC nav. We hide the __toc
* machinery and replace the <a> with a non-navigating <label> that
* has the same section-heading + chevron appearance as native sections.
*
* The TOC stays always-visible and cannot be collapsed — matching how
* Material treats native sections that contain the current page.
*/
function convertActivePageItem(li) {
// 1. Force-check and hide the __toc checkbox so Material keeps the
// TOC visible, and prevent any future toggling.
var tocCheckbox = li.querySelector(":scope > #__toc");
if (tocCheckbox) {
tocCheckbox.checked = true;
tocCheckbox.disabled = true;
tocCheckbox.style.display = "none";
}
// 2. Hide the __toc label (it has the wrong icon and toggles the
// checkbox — we don't want either).
var tocLabel = li.querySelector(':scope > label[for="__toc"]');
if (tocLabel) {
tocLabel.style.display = "none";
}
// 3. Mark the <li> as nested + our custom marker so CSS can style it.
li.classList.add("md-nav__item--nested");
li.classList.add("md-nav__item--section-page");
// 4. Replace the <a> with a non-navigating <label> that matches the
// exact structure of native section headings.
var link = li.querySelector(":scope > a.md-nav__link");
if (link) {
var text = link.textContent.trim();
var heading = document.createElement("label");
heading.className = "md-nav__link";
heading.tabIndex = 0;
var ellipsis = document.createElement("span");
ellipsis.className = "md-ellipsis";
ellipsis.textContent = text;
heading.appendChild(ellipsis);
var icon = document.createElement("span");
icon.className = "md-nav__icon md-icon";
heading.appendChild(icon);
link.parentNode.replaceChild(heading, link);
}
// 5. Force the TOC nav always visible (belt-and-suspenders alongside
// the CSS rule).
var tocNav = li.querySelector(":scope > nav.md-nav--secondary");
if (tocNav) {
tocNav.style.display = "block";
}
}
/**
* Fetch a page and extract its TOC <ul> content, then inject it.
*/
function loadToc(href, targetUl) {
// Add a placeholder
var placeholder = document.createElement("li");
placeholder.className = "md-nav__item md-nav__toc-placeholder";
placeholder.textContent = "Loading…";
targetUl.appendChild(placeholder);
fetch(href)
.then(function (res) { return res.text(); })
.then(function (html) {
// Remove placeholder
if (placeholder.parentNode) placeholder.parentNode.removeChild(placeholder);
// Parse the fetched page and extract TOC items
var parser = new DOMParser();
var doc = parser.parseFromString(html, "text/html");
var tocList = doc.querySelector("[data-md-component='toc']");
if (!tocList) return;
// Cache the TOC items (as an array of <li> outerHTML)
var items = [];
tocList.querySelectorAll(":scope > li").forEach(function (li) {
items.push(li.outerHTML);
});
tocCache[href] = items;
injectToc(targetUl, items);
})
.catch(function () {
if (placeholder.parentNode) placeholder.parentNode.removeChild(placeholder);
});
}
/**
* Inject cached TOC items into the target <ul>.
*/
function injectToc(targetUl, items) {
// Don't inject if already done
if (targetUl.dataset.tocLoaded) return;
targetUl.dataset.tocLoaded = "true";
items.forEach(function (itemHtml) {
var temp = document.createElement("template");
temp.innerHTML = itemHtml;
var li = temp.content.firstElementChild;
if (li) {
// Fix relative anchor links — they need the page href prefix
li.querySelectorAll("a[href^='#']").forEach(function (a) {
var pageLink = targetUl.querySelector("li:first-child a");
if (pageLink) {
a.href = pageLink.href + a.getAttribute("href");
}
});
targetUl.appendChild(li);
}
});
// Initialise collapse/expand behaviour on any nested items that
// were just injected (sub-sections inside the fetched TOC).
// No pinning tracking — these are non-active pages with no
// MutationObserver watching them.
makeItemsCollapsible(targetUl, false);
}
// ── Lock active ancestor sections ────────────────────────────────────
//
// When you are viewing a page inside a section (e.g. CI/CD Pipeline
// inside Development), Material marks every ancestor section <li> with
// md-nav__item--active and checks its toggle. By default the user can
// still uncheck (collapse) those sections. We prevent this so that
// every section on the path to the active page stays permanently open,
// matching the intuitive expectation that you cannot collapse a section
// you are currently inside.
function lockActiveAncestorSections() {
var primaryNav = document.querySelector("nav.md-nav--primary");
if (!primaryNav) return;
// Find native sections that are ancestors of the current page.
// These have both --active (propagated up from the active page)
// and --nested (they are real sections, not converted page items).
primaryNav.querySelectorAll(
"li.md-nav__item--active.md-nav__item--nested:not(.md-nav__item--section-page)"
).forEach(function (li) {
var toggle = li.querySelector(":scope > input.md-nav__toggle");
if (!toggle) return;
// Force checked and prevent user from unchecking
toggle.checked = true;
// Intercept click/change to keep it locked
toggle.addEventListener("click", function (e) {
e.preventDefault();
});
toggle.addEventListener("change", function () {
toggle.checked = true;
});
});
}
// ── Desktop detection ────────────────────────────────────────────────
//
// This entire script is designed for the desktop persistent sidebar
// (≥76.25em). On mobile, Material uses a slide-out drawer with its
// own drill-down navigation, scroll management, and back-button
// behaviour. Running our DOM restructuring (convertPageItems,
// initCollapsibleToc, lockActiveAncestorSections) on mobile breaks
// the drawer's scroll container and makes it impossible to scroll
// past the visible items.
// behaviour. Running our DOM restructuring on mobile breaks the
// drawer's scroll container and makes it impossible to scroll past
// the visible items.
//
// We use matchMedia to detect the breakpoint and only bootstrap on
// desktop. On resize/orientation changes that cross the breakpoint,
@@ -576,8 +257,6 @@
function bootstrap() {
if (!isDesktop()) return; // skip all DOM manipulation on mobile
initCollapsibleToc();
convertPageItems();
lockActiveAncestorSections();
}
if (document.readyState === "loading") {
@@ -589,9 +268,6 @@
// Re-init after instant navigation (mkdocs-material SPA mode)
if (typeof document$ !== "undefined") {
document$.subscribe(function () {
// Reset TOC cache and counter on navigation
tocCache = {};
pageNavCounter = 100;
bootstrap();
});
}
+74
View File
@@ -0,0 +1,74 @@
{#-
Custom tabs partial — adds dropdown menus for sections with children.
For top-level nav items that have children (Development, Reference, ADRs),
a hover-activated dropdown lists all child pages. Single-page tabs
(Specification, Timeline, FAQ) render as plain links.
Replaces the default Material tabs.html + tabs-item.html.
-#}
{#- Recursive macro: drill to the first leaf page in a section tree. -#}
{% macro first_leaf_url(nav_item) %}
{% if nav_item.children %}
{% set first = nav_item.children | first %}
{{ first_leaf_url(first) }}
{% else %}
{{ nav_item.url | url }}
{% endif %}
{% endmacro %}
{#- Recursive helper: collect all leaf pages under a section. -#}
{% macro render_dropdown_items(children) %}
{% for child in children %}
{% if child.children %}
{#- Nested section — render a group label + its children -#}
<li class="md-tabs__dropdown-group">
<span class="md-tabs__dropdown-label">{{ child.title }}</span>
<ul>
{{ render_dropdown_items(child.children) }}
</ul>
</li>
{% else %}
<li>
<a href="{{ child.url | url }}" class="md-tabs__dropdown-link{% if child == page %} md-tabs__dropdown-link--active{% endif %}">
{{ child.title }}
</a>
</li>
{% endif %}
{% endfor %}
{% endmacro %}
<nav class="md-tabs" aria-label="{{ lang.t('tabs') }}" data-md-component="tabs">
<div class="md-grid">
<ul class="md-tabs__list">
{% for nav_item in nav.items %}
{% set class = "md-tabs__item" %}
{% if nav_item.active %}
{% set class = class ~ " md-tabs__item--active" %}
{% endif %}
{% if nav_item.children %}
{#- Section with children — render tab + dropdown -#}
{% set class = class ~ " md-tabs__item--dropdown" %}
<li class="{{ class }}">
<a href="{{ first_leaf_url(nav_item) | trim }}" class="md-tabs__link">
{{ nav_item.title }}
<span class="md-tabs__dropdown-caret"></span>
</a>
<ul class="md-tabs__dropdown">
{{ render_dropdown_items(nav_item.children) }}
</ul>
</li>
{% else %}
{#- Single page — plain tab link -#}
<li class="{{ class }}">
<a href="{{ nav_item.url | url }}" class="md-tabs__link">
{{ nav_item.title }}
</a>
</li>
{% endif %}
{% endfor %}
</ul>
</div>
</nav>
+217 -75
View File
@@ -98,85 +98,227 @@
min-width: 0;
}
/* --- Primary nav section collapse fix -----------------------------------------
/* --- Navbar dropdown menus ---------------------------------------------------
*
* Material's native checkbox-based toggle for nav sections (Reference,
* Development, Architecture Decisions, etc.) must fully hide children
* when unchecked. Without this, the <nav> container retains residual
* height from padding/grid-rows even when the checkbox is unchecked,
* causing visible whitespace below collapsed section headings.
*
* DESKTOP ONLY (≥76.25em): On mobile, Material uses a slide-out drawer
* with drill-down navigation that depends on nested <nav> elements being
* in the render tree. Applying display:none on mobile breaks both the
* drill-down animation and scroll-height computation, preventing users
* from scrolling to see all nav items.
* Custom tabs.html renders top-level sections (Development, Reference, ADRs)
* as tabs with hover-activated dropdown menus listing their child pages.
* Single-page tabs (Specification, Timeline, FAQ) have no dropdown.
* ----------------------------------------------------------------------- */
/* Override Material's overflow/containment on the tabs bar and its list.
* Material sets overflow:auto and contain:content which clip absolutely-
* positioned children (our dropdown menus). We need visible overflow
* so the dropdowns can extend below the tab bar.
*
* Material's selectors:
* .md-tabs { overflow: auto } — (0,1,0)
* .md-tabs__list { contain: content; overflow: auto } — (0,1,0)
*
* Counter-selectors beat them by nesting under .md-header: */
.md-header .md-tabs {
overflow: visible;
}
.md-header .md-tabs .md-tabs__list {
overflow: visible;
contain: none;
}
/* Dropdown container — positioned below the tab item */
.md-tabs__item--dropdown {
position: relative;
}
.md-tabs__dropdown {
display: none;
position: absolute;
top: 100%;
left: 0;
min-width: 16rem;
max-height: 70vh;
overflow-y: auto;
list-style: none;
margin: 0;
padding: .4rem 0;
background: var(--md-primary-fg-color);
border-radius: 0 0 .2rem .2rem;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
z-index: 100;
}
/* Show dropdown on hover and when the tab is focused within */
.md-tabs__item--dropdown:hover > .md-tabs__dropdown,
.md-tabs__item--dropdown:focus-within > .md-tabs__dropdown {
display: block;
}
/* Dropdown link items */
.md-tabs__dropdown-link {
display: block;
padding: .4rem 1rem;
color: var(--md-primary-bg-color);
font-size: .7rem;
white-space: nowrap;
text-decoration: none;
transition: background-color .15s;
}
.md-tabs__dropdown-link:hover {
background-color: rgba(255, 255, 255, 0.15);
}
.md-tabs__dropdown-link--active {
font-weight: 700;
}
/* Nested section groups inside the dropdown */
.md-tabs__dropdown-group {
list-style: none;
}
.md-tabs__dropdown-label {
display: block;
padding: .5rem 1rem .2rem;
font-size: .6rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .05em;
opacity: .7;
color: var(--md-primary-bg-color);
}
.md-tabs__dropdown-group > ul {
list-style: none;
margin: 0;
padding: 0;
}
/* Small caret indicator on tabs that have dropdowns */
.md-tabs__dropdown-caret {
display: inline-block;
width: 0;
height: 0;
margin-left: .3rem;
vertical-align: middle;
border-left: .25rem solid transparent;
border-right: .25rem solid transparent;
border-top: .25rem solid currentcolor;
opacity: .6;
}
/* --- Left sidebar: show only the TOC (desktop) --------------------------------
*
* With navigation.tabs + toc.integrate, the left sidebar contains both the
* sub-page navigation for the active tab AND the integrated TOC for the
* current page. We hide all non-TOC elements so the left sidebar displays
* only the Table of Contents.
*
* The TOC is always rendered inside <nav class="md-nav--secondary">.
* Everything else in the primary sidebar is page navigation.
*
* DESKTOP ONLY (>= 76.25em) — on mobile, the drawer navigation is used
* and must remain fully functional.
* ----------------------------------------------------------------------- */
/* Fully hide children of unchecked primary nav sections — desktop only */
@media screen and (min-width: 76.25em) {
.md-sidebar--primary .md-nav__item--nested > .md-nav__toggle:not(:checked) ~ .md-nav {
/* Hide all top-level nav items in the sidebar — these are the page
* links within the active tab section (or the tab items themselves
* if the tab has no children). */
.md-sidebar--primary .md-nav--primary > .md-nav__list > .md-nav__item {
display: none;
}
}
/* --- Leaf-page nav items: section-like behaviour ----------------------------
*
* toc-collapse.js converts ALL leaf-page nav items in the primary sidebar
* into section-like DOM structures so every page gets an expand/collapse
* chevron. This applies to both top-level single-page items (Specification,
* Work Remaining, FAQ) and children within sections (Action CLI, Session
* Model, ADR-001, etc.).
*
* INACTIVE pages are converted into a checkbox + label + nested-nav
* structure identical to Material's sections. The page's TOC is
* lazy-loaded into the nested nav on first expand.
*
* ACTIVE pages (the page you're currently viewing) are converted into
* an always-expanded, non-collapsible section. The __toc checkbox and
* label are hidden, the <a> is replaced with a non-navigating <label>
* matching native section heading structure, and the TOC
* (md-nav--secondary) is forced visible.
* ----------------------------------------------------------------------- */
/* But show items on the ancestor path to the current page — these
* contain the nested .md-nav--secondary (TOC). */
.md-sidebar--primary .md-nav--primary > .md-nav__list > .md-nav__item--active {
display: block;
}
/* -- Active page item: always-expanded section appearance -----------------
*
* toc-collapse.js converts the active page item (at any level in the nav
* tree) into a non-collapsible section. The __toc checkbox and label are
* hidden, the <a> is replaced with a <label> that matches native section
* heading structure, and the TOC (md-nav--secondary) is forced visible.
*
* The marker class .md-nav__item--section-page is added by JS.
* ----------------------------------------------------------------------- */
/* Within active section wrappers, hide sibling page links (only the
* active child with its TOC should be visible).
*
* IMPORTANT: the :not(.md-nav--secondary) guard prevents this rule
* from also matching .md-nav--secondary (the TOC nav), whose <li>
* children are plain .md-nav__item without --active and would
* otherwise be hidden. */
.md-sidebar--primary .md-nav__item--active > .md-nav:not(.md-nav--secondary) > .md-nav__list > .md-nav__item:not(.md-nav__item--active) {
display: none;
}
/* Force the TOC always visible (JS also sets inline style as fallback) */
.md-nav__item--section-page > .md-nav--secondary {
display: block !important;
}
/* Hide the page-link labels / headings on active items so only the
* TOC tree remains visible. This targets:
* - Section headings (<label class="md-nav__link">)
* - Page links (<a class="md-nav__link">)
* - The __toc checkbox and its label
* - The section title inside nested navs
* but NOT anything inside .md-nav--secondary (the TOC itself). */
.md-sidebar--primary .md-nav--primary > .md-nav__list > .md-nav__item--active > .md-nav__toggle,
.md-sidebar--primary .md-nav--primary > .md-nav__list > .md-nav__item--active > label.md-nav__link,
.md-sidebar--primary .md-nav--primary > .md-nav__list > .md-nav__item--active > a.md-nav__link,
.md-sidebar--primary .md-nav--primary > .md-nav__list > .md-nav__item--active > .md-nav__container,
.md-sidebar--primary .md-nav--primary > .md-nav__list > .md-nav__item--active > nav > .md-nav__title {
display: none;
}
/* Rotate the chevron to point downward (expanded state), matching the
* native rotation rule for checked section toggles. */
.md-nav__item--section-page > .md-nav__link > .md-nav__icon::after {
transform: rotate(90deg);
}
/* Within nested sections (level 2+), hide the active page's own link
* and __toc machinery, leaving only the .md-nav--secondary TOC. */
.md-sidebar--primary .md-nav__item--active > #__toc,
.md-sidebar--primary .md-nav__item--active > label[for="__toc"],
.md-sidebar--primary .md-nav__item--active > a.md-nav__link,
.md-sidebar--primary .md-nav__item--active > .md-nav__container {
display: none;
}
/* -- Lazy-loaded TOC placeholder -- */
/* Force the TOC (md-nav--secondary) visible.
*
* Material's rules that hide or collapse the TOC:
*
* A) .md-nav--primary .md-nav__link[for=__toc]~.md-nav
* → display: none — (0,4,0)
*
* B) .md-nav__toggle~.md-nav (desktop media query)
* → display: grid; opacity: 0; visibility: collapse — (0,2,0)
*
* Material's integrated rule tries to re-show it:
* C) .md-nav--integrated>.md-nav__list>.md-nav__item--active .md-nav--secondary
* → display: block; opacity: 1; visibility: visible — (0,4,0)
*
* A and C tie at (0,4,0), so cascade order decides — and A often
* wins. We counter with higher-specificity selectors that
* guarantee the TOC is always visible without using !important.
*/
.md-nav__toc-placeholder {
padding: .4rem .8rem;
font-size: .64rem;
opacity: .6;
/* Counter rule A — specificity (0,6,0) beats (0,4,0).
* Directly negates the sibling-combinator hiding rule. */
.md-sidebar--primary .md-nav--primary .md-nav__link[for="__toc"] ~ nav.md-nav--secondary {
display: block;
opacity: 1;
visibility: visible;
}
/* General fallback for integrated mode — specificity (0,5,0)
* beats rule B (0,2,0) and ties/beats rule C (0,4,0) by being
* loaded later. */
.md-sidebar--primary .md-nav--primary.md-nav--integrated .md-nav__item--active .md-nav--secondary {
display: block;
opacity: 1;
visibility: visible;
}
/* Remove the top-level sidebar title ("Navigation" label) on desktop
* since we no longer show page navigation in the sidebar. */
.md-sidebar--primary .md-nav--primary > .md-nav__title {
display: none;
}
}
/* --- Collapsible TOC in integrated sidebar --- */
/* Container for the link row: link text + toggle chevron side by side.
* Scoped to .md-nav--secondary (active page TOC) and .md-nav--page-toc
* (lazy-loaded TOC in converted single-page nav items) so it does not
* affect primary nav section headings which use Material's native
* <label> + checkbox. */
.md-nav--secondary .md-nav__item--nested > .md-nav__link,
.md-nav--page-toc .md-nav__item--nested > .md-nav__link {
* Scoped to .md-nav--secondary (active page TOC) so it does not affect
* primary nav section headings which use Material's native <label> +
* checkbox. */
.md-nav--secondary .md-nav__item--nested > .md-nav__link {
display: flex;
align-items: center;
}
@@ -231,13 +373,11 @@
* Desktop only — on mobile the TOC is rendered within Material's drawer
* and must remain in the render tree for correct scroll behaviour. */
@media screen and (min-width: 76.25em) {
.md-nav--secondary .md-nav__item--nested.toc-collapsed > .md-nav,
.md-nav--page-toc .md-nav__item--nested.toc-collapsed > .md-nav {
.md-nav--secondary .md-nav__item--nested.toc-collapsed > .md-nav {
display: none;
}
.md-nav--secondary .md-nav__item--nested > .md-nav,
.md-nav--page-toc .md-nav__item--nested > .md-nav {
.md-nav--secondary .md-nav__item--nested > .md-nav {
display: block;
}
}
@@ -280,15 +420,17 @@
/* Undo any items hidden by the JS-driven toc-collapsed class that
* may have been applied before the mobile guard kicks in (race
* condition on resize or orientation change). */
.md-sidebar--primary .toc-collapsed > .md-nav {
display: block !important;
}
/* Undo the section-page forced display set by JS on active pages —
* let Material's native mobile styling handle it. */
.md-nav__item--section-page > .md-nav--secondary {
display: revert !important;
* condition on resize or orientation change).
*
* The desktop hiding rule is:
* .md-nav--secondary .md-nav__item--nested.toc-collapsed > .md-nav
* { display: none } — (0,3,0), inside a ≥76.25em media query
*
* This mobile rule applies inside a <76.1875em media query, so the
* two never conflict — no !important needed. We bump specificity
* slightly for safety against any other rules. */
.md-sidebar--primary .md-nav--secondary .toc-collapsed > .md-nav {
display: block;
}
}
+3
View File
@@ -67,7 +67,10 @@ nav:
theme:
name: material
custom_dir: docs/overrides
features:
- navigation.tabs
- navigation.tabs.sticky
- toc.integrate
- toc.follow
palette: