@takazudo/zudo-doc 5.5.3 → 5.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +51 -0
- package/README.md +39 -0
- package/dist/chrome/derive.d.ts +46 -0
- package/dist/chrome/derive.js +6 -2
- package/dist/config-assertions/index.d.ts +31 -0
- package/dist/config-assertions/index.js +24 -0
- package/dist/config.d.ts +23 -2
- package/dist/config.js +3 -0
- package/dist/current-path/index.d.ts +27 -0
- package/dist/current-path/index.js +11 -0
- package/dist/design-token-panel-bootstrap.d.ts +89 -23
- package/dist/design-token-panel-bootstrap.js +19 -6
- package/dist/doc-page-props/index.d.ts +5 -67
- package/dist/doc-route-entries/index.d.ts +10 -95
- package/dist/doc-route-entries/index.js +1 -78
- package/dist/doc-route-paths/index.d.ts +1 -1
- package/dist/head-with-defaults/index.d.ts +3 -1
- package/dist/head-with-defaults/index.js +76 -4
- package/dist/header/nav-active.d.ts +31 -0
- package/dist/header/nav-active.js +2 -1
- package/dist/header/nav-overflow-generated-script.d.ts +11 -0
- package/dist/header/nav-overflow-generated-script.js +302 -0
- package/dist/header/nav-overflow-script.d.ts +1 -1
- package/dist/header/nav-overflow-script.js +1 -286
- package/dist/header-with-defaults/index.js +11 -2
- package/dist/i18n-version/language-switcher.d.ts +6 -0
- package/dist/i18n-version/language-switcher.js +3 -1
- package/dist/i18n-version/version-switcher.d.ts +6 -0
- package/dist/i18n-version/version-switcher.js +3 -1
- package/dist/md-utils/index.js +33 -1
- package/dist/nav-source-docs/index.d.ts +7 -11
- package/dist/plugins/route-pages-candidates.d.ts +19 -0
- package/dist/plugins/route-pages-candidates.js +17 -0
- package/dist/plugins/routes.d.ts +46 -0
- package/dist/plugins/routes.js +72 -18
- package/dist/preset.d.ts +12 -1
- package/dist/preset.js +2 -0
- package/dist/route-context/index.js +2 -2
- package/dist/routes/_chrome.d.ts +1 -1
- package/dist/routes/_chrome.js +4 -0
- package/dist/routes/_context.d.ts +3 -3
- package/dist/routes/_design-token-panel-bootstrap.d.ts +18 -0
- package/dist/routes/_design-token-panel-bootstrap.js +11 -0
- package/dist/routes/_docs-helpers.d.ts +1 -36
- package/dist/routes/_docs-helpers.js +0 -138
- package/dist/safelist.css +1 -1
- package/dist/search-widget-script/generated-script.d.ts +8 -0
- package/dist/search-widget-script/generated-script.js +465 -0
- package/dist/search-widget-script/index.d.ts +1 -18
- package/dist/search-widget-script/index.js +1 -443
- package/dist/settings.d.ts +82 -1
- package/dist/sidebar-toggle-island/index.js +2 -1
- package/dist/sidebar-tree/category-meta.d.ts +9 -0
- package/dist/sidebar-tree/category-meta.js +21 -12
- package/dist/sidebar-tree-island/index.d.ts +8 -1
- package/dist/sidebar-tree-island/index.js +16 -14
- package/dist/site-schema/doc-route-entries.d.ts +89 -0
- package/dist/site-schema/doc-route-entries.js +83 -0
- package/dist/site-schema/index.d.ts +17 -0
- package/dist/site-schema/index.js +46 -0
- package/dist/site-schema/nav-tree.d.ts +28 -0
- package/dist/site-schema/nav-tree.js +138 -0
- package/dist/site-schema/types.d.ts +97 -0
- package/dist/site-schema/types.js +0 -0
- package/dist/theme/theme-pack-provider.d.ts +34 -3
- package/dist/theme/theme-pack-provider.js +30 -2
- package/dist/transitions/index.d.ts +1 -0
- package/dist/transitions/index.js +2 -0
- package/dist/transitions/nested-island-props-refresh.d.ts +19 -0
- package/dist/transitions/nested-island-props-refresh.js +104 -0
- package/eject/header/header.tsx +13 -5
- package/eject/header/nav-active.ts +16 -1
- package/eject/header/nav-class-tokens.ts +9 -5
- package/eject/header/nav-overflow-generated-script.ts +29 -0
- package/eject/header/nav-overflow-script.ts +26 -304
- package/eject/sidebar-toggle-island/index.tsx +13 -1
- package/eject/sidebar-tree-island/index.tsx +44 -20
- package/package.json +25 -12
- package/routes-src/_chrome.tsx +21 -9
- package/routes-src/_design-token-panel-bootstrap.tsx +63 -0
- package/routes-src/_docs-helpers.ts +18 -225
- package/routes-src/_virtual.d.ts +5 -2
- package/virtual-modules.d.ts +5 -2
|
@@ -11,6 +11,9 @@ import {
|
|
|
11
11
|
THEME_PACK_STORAGE_KEY,
|
|
12
12
|
buildPackCssUrl
|
|
13
13
|
} from "../theme-pack-switcher/theme-pack-sync.js";
|
|
14
|
+
const THEME_PACK_LOADING_ATTR = "data-zd-theme-pack-loading";
|
|
15
|
+
const THEME_PACK_LOAD_WATCHDOG_MS = 2e3;
|
|
16
|
+
const THEME_PACK_LATCH_CSS = `html[${THEME_PACK_LOADING_ATTR}] body{visibility:hidden}`;
|
|
14
17
|
function themePackVersionMap(registry) {
|
|
15
18
|
const map = {};
|
|
16
19
|
for (const entry of registry) {
|
|
@@ -33,6 +36,8 @@ function buildThemePackBootstrap(configuredSlug, enabled, base) {
|
|
|
33
36
|
const runtimeGlobal = JSON.stringify(THEME_PACK_RUNTIME_GLOBAL);
|
|
34
37
|
const afterNav = JSON.stringify(AFTER_NAVIGATE_EVENT);
|
|
35
38
|
const beforeSwap = JSON.stringify(BEFORE_SWAP_EVENT);
|
|
39
|
+
const loadingAttr = JSON.stringify(THEME_PACK_LOADING_ATTR);
|
|
40
|
+
const watchdog = JSON.stringify(THEME_PACK_LOAD_WATCHDOG_MS);
|
|
36
41
|
return `(function(){
|
|
37
42
|
var configured=${configured};
|
|
38
43
|
var packs=${packs};
|
|
@@ -40,12 +45,31 @@ var base=${b};
|
|
|
40
45
|
var KEY=${key};
|
|
41
46
|
var ATTR=${attr};
|
|
42
47
|
var LINK_ATTR=${linkAttr};
|
|
48
|
+
var LOADING_ATTR=${loadingAttr};
|
|
49
|
+
var WATCHDOG=${watchdog};
|
|
43
50
|
var DEFAULT_SLUG=${defaultSlug};
|
|
44
51
|
function resolveSlug(){var stored=null;try{stored=localStorage.getItem(KEY);}catch(e){}if(stored&&Object.prototype.hasOwnProperty.call(packs,stored))return stored;var live=document.documentElement.getAttribute(ATTR);if(live&&Object.prototype.hasOwnProperty.call(packs,live))return live;return configured;}
|
|
45
52
|
function packHref(slug){return base+"theme-packs/"+slug+"/pack.css?v="+packs[slug];}
|
|
53
|
+
function hasPackLink(href){var ls=document.querySelectorAll("link["+LINK_ATTR+"]");for(var i=0;i<ls.length;i++){if(ls[i].getAttribute("href")===href)return true;}return false;}
|
|
46
54
|
var slug=resolveSlug();
|
|
47
|
-
document.documentElement
|
|
48
|
-
|
|
55
|
+
var root=document.documentElement;
|
|
56
|
+
root.setAttribute(ATTR,slug);
|
|
57
|
+
if(slug!==DEFAULT_SLUG){
|
|
58
|
+
var href=packHref(slug);
|
|
59
|
+
if(!hasPackLink(href)){
|
|
60
|
+
var shouldLatch=document.readyState==="loading";
|
|
61
|
+
if(shouldLatch){root.setAttribute(LOADING_ATTR,"");}
|
|
62
|
+
var release=function(){root.removeAttribute(LOADING_ATTR);};
|
|
63
|
+
var link=document.createElement("link");
|
|
64
|
+
link.setAttribute("rel","stylesheet");
|
|
65
|
+
link.setAttribute(LINK_ATTR,"");
|
|
66
|
+
link.setAttribute("href",href);
|
|
67
|
+
link.onload=release;
|
|
68
|
+
link.onerror=release;
|
|
69
|
+
document.head.appendChild(link);
|
|
70
|
+
if(shouldLatch){setTimeout(release,WATCHDOG);}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
49
73
|
window[${runtimeGlobal}]={base:base,packs:packs,configured:configured};
|
|
50
74
|
document.addEventListener(${beforeSwap},function(e){
|
|
51
75
|
var s=resolveSlug();
|
|
@@ -81,11 +105,15 @@ function ThemePackProvider({
|
|
|
81
105
|
const configuredVersion = enabled[configuredSlug];
|
|
82
106
|
const noscriptHref = configuredSlug !== DEFAULT_THEME_PACK_SLUG && configuredVersion !== void 0 ? buildPackCssUrl(base, configuredSlug, configuredVersion) : null;
|
|
83
107
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
108
|
+
/* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: THEME_PACK_LATCH_CSS } }),
|
|
84
109
|
/* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: bootstrap } }),
|
|
85
110
|
noscriptHref !== null && /* @__PURE__ */ jsx("noscript", { children: /* @__PURE__ */ jsx("link", { rel: "stylesheet", href: noscriptHref }) })
|
|
86
111
|
] });
|
|
87
112
|
}
|
|
88
113
|
export {
|
|
114
|
+
THEME_PACK_LATCH_CSS,
|
|
115
|
+
THEME_PACK_LOADING_ATTR,
|
|
116
|
+
THEME_PACK_LOAD_WATCHDOG_MS,
|
|
89
117
|
buildThemePackBootstrap,
|
|
90
118
|
ThemePackProvider as default,
|
|
91
119
|
resolveThemePackSsrSlug,
|
|
@@ -4,9 +4,11 @@ import {
|
|
|
4
4
|
onBeforeNavigate,
|
|
5
5
|
onAfterNavigate
|
|
6
6
|
} from "./page-events.js";
|
|
7
|
+
import { ensureNestedIslandPropsRefresh } from "./nested-island-props-refresh.js";
|
|
7
8
|
export {
|
|
8
9
|
AFTER_NAVIGATE_EVENT,
|
|
9
10
|
BEFORE_NAVIGATE_EVENT,
|
|
11
|
+
ensureNestedIslandPropsRefresh,
|
|
10
12
|
onAfterNavigate,
|
|
11
13
|
onBeforeNavigate
|
|
12
14
|
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
interface NestedIslandPropsRefreshOptions {
|
|
2
|
+
document: Document;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Install the before-swap props-refresh listener on a document.
|
|
6
|
+
*
|
|
7
|
+
* This low-level entrypoint owns an explicit cleanup for focused tests and
|
|
8
|
+
* non-singleton hosts. Browser modules use the durable, duplicate-safe
|
|
9
|
+
* `ensureNestedIslandPropsRefresh` wrapper below instead.
|
|
10
|
+
*/
|
|
11
|
+
export declare function installNestedIslandPropsRefresh({ document, }: NestedIslandPropsRefreshOptions): () => void;
|
|
12
|
+
/**
|
|
13
|
+
* Install exactly one document-lifetime controller for a browser document.
|
|
14
|
+
* Repeated calls from component renders or duplicate boot paths are no-ops.
|
|
15
|
+
*/
|
|
16
|
+
export declare function ensureNestedIslandPropsRefresh(options?: NestedIslandPropsRefreshOptions): void;
|
|
17
|
+
/** Test/HMR teardown for a controller installed through the singleton wrapper. */
|
|
18
|
+
export declare function disposeNestedIslandPropsRefresh(document: Document): void;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { BEFORE_SWAP_EVENT } from "./page-events.js";
|
|
2
|
+
const PERSIST_ATTR = "data-zfb-transition-persist";
|
|
3
|
+
const ISLAND_ATTR = "data-zfb-island";
|
|
4
|
+
const PROPS_ATTR = "data-props";
|
|
5
|
+
const ISLAND_REMOUNT_ATTR = "data-zfb-island-remount";
|
|
6
|
+
const DOCUMENT_NODE = 9;
|
|
7
|
+
const installedControllers = /* @__PURE__ */ new WeakMap();
|
|
8
|
+
function installNestedIslandPropsRefresh({
|
|
9
|
+
document: document2
|
|
10
|
+
}) {
|
|
11
|
+
const onBeforeSwap = (event) => {
|
|
12
|
+
const incoming = event.newDocument;
|
|
13
|
+
if (!isDocument(incoming) || incoming === document2) return;
|
|
14
|
+
refreshNestedIslandProps(document2, incoming);
|
|
15
|
+
};
|
|
16
|
+
document2.addEventListener(BEFORE_SWAP_EVENT, onBeforeSwap);
|
|
17
|
+
return () => document2.removeEventListener(BEFORE_SWAP_EVENT, onBeforeSwap);
|
|
18
|
+
}
|
|
19
|
+
function ensureNestedIslandPropsRefresh(options) {
|
|
20
|
+
const resolved = options ?? resolveBrowserOptions();
|
|
21
|
+
if (!resolved || installedControllers.has(resolved.document)) return;
|
|
22
|
+
installedControllers.set(
|
|
23
|
+
resolved.document,
|
|
24
|
+
installNestedIslandPropsRefresh(resolved)
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
function disposeNestedIslandPropsRefresh(document2) {
|
|
28
|
+
installedControllers.get(document2)?.();
|
|
29
|
+
installedControllers.delete(document2);
|
|
30
|
+
}
|
|
31
|
+
function refreshNestedIslandProps(liveDocument, incomingDocument) {
|
|
32
|
+
const liveRoots = collectRefreshableRoots(liveDocument);
|
|
33
|
+
if (liveRoots.size === 0) return;
|
|
34
|
+
const incomingRoots = collectRefreshableRoots(incomingDocument);
|
|
35
|
+
for (const [persistKey, liveRoot] of liveRoots) {
|
|
36
|
+
const incomingRoot = incomingRoots.get(persistKey);
|
|
37
|
+
if (!incomingRoot) continue;
|
|
38
|
+
const incomingIslands = collectUniqueOwnedIslands(incomingRoot);
|
|
39
|
+
if (incomingIslands.size === 0) continue;
|
|
40
|
+
for (const [name, liveIsland] of collectUniqueOwnedIslands(liveRoot)) {
|
|
41
|
+
const incomingIsland = incomingIslands.get(name);
|
|
42
|
+
if (!incomingIsland) continue;
|
|
43
|
+
applyProps(liveIsland, incomingIsland);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function collectRefreshableRoots(doc) {
|
|
48
|
+
const roots = indexUniquely(
|
|
49
|
+
doc.querySelectorAll(`[${PERSIST_ATTR}]`),
|
|
50
|
+
PERSIST_ATTR
|
|
51
|
+
);
|
|
52
|
+
for (const [persistKey, element] of roots) {
|
|
53
|
+
if (element.parentElement?.closest(`[${PERSIST_ATTR}]`)) {
|
|
54
|
+
roots.delete(persistKey);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return roots;
|
|
58
|
+
}
|
|
59
|
+
function collectUniqueOwnedIslands(root) {
|
|
60
|
+
const owned = Array.from(root.querySelectorAll(`[${ISLAND_ATTR}]`)).filter(
|
|
61
|
+
(island) => island.closest(`[${PERSIST_ATTR}]`) === root
|
|
62
|
+
);
|
|
63
|
+
return indexUniquely(owned, ISLAND_ATTR);
|
|
64
|
+
}
|
|
65
|
+
function indexUniquely(elements, attribute) {
|
|
66
|
+
const byValue = /* @__PURE__ */ new Map();
|
|
67
|
+
const duplicated = /* @__PURE__ */ new Set();
|
|
68
|
+
for (const element of elements) {
|
|
69
|
+
const value = element.getAttribute(attribute);
|
|
70
|
+
if (!value) continue;
|
|
71
|
+
if (byValue.has(value)) {
|
|
72
|
+
duplicated.add(value);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
byValue.set(value, element);
|
|
76
|
+
}
|
|
77
|
+
for (const value of duplicated) byValue.delete(value);
|
|
78
|
+
return byValue;
|
|
79
|
+
}
|
|
80
|
+
function applyProps(liveIsland, incomingIsland) {
|
|
81
|
+
const incoming = incomingIsland.getAttribute(PROPS_ATTR);
|
|
82
|
+
const current = liveIsland.getAttribute(PROPS_ATTR);
|
|
83
|
+
if (incoming === current) return;
|
|
84
|
+
if (incoming === null) {
|
|
85
|
+
liveIsland.removeAttribute(PROPS_ATTR);
|
|
86
|
+
} else {
|
|
87
|
+
liveIsland.setAttribute(PROPS_ATTR, incoming);
|
|
88
|
+
}
|
|
89
|
+
liveIsland.setAttribute(ISLAND_REMOUNT_ATTR, "");
|
|
90
|
+
}
|
|
91
|
+
function isDocument(value) {
|
|
92
|
+
if (typeof value !== "object" || value === null) return false;
|
|
93
|
+
const candidate = value;
|
|
94
|
+
return candidate.nodeType === DOCUMENT_NODE && typeof candidate.querySelectorAll === "function";
|
|
95
|
+
}
|
|
96
|
+
function resolveBrowserOptions() {
|
|
97
|
+
if (typeof document === "undefined") return void 0;
|
|
98
|
+
return { document };
|
|
99
|
+
}
|
|
100
|
+
export {
|
|
101
|
+
disposeNestedIslandPropsRefresh,
|
|
102
|
+
ensureNestedIslandPropsRefresh,
|
|
103
|
+
installNestedIslandPropsRefresh
|
|
104
|
+
};
|
package/eject/header/header.tsx
CHANGED
|
@@ -310,12 +310,20 @@ export function Header(props: HeaderProps): JSX.Element {
|
|
|
310
310
|
// - Search: <site-search> custom element re-registers on
|
|
311
311
|
// AFTER_NAVIGATE_EVENT (_search-widget-script.ts:184, verified (a))
|
|
312
312
|
// - SidebarToggle (mobile): closes on AFTER_NAVIGATE_EVENT
|
|
313
|
-
// (sidebar-toggle.tsx
|
|
314
|
-
//
|
|
315
|
-
//
|
|
316
|
-
//
|
|
313
|
+
// (sidebar-toggle-island/index.tsx, verified (a)). Its section tree
|
|
314
|
+
// rides the Island's serialised data-props, and re-hydration alone
|
|
315
|
+
// does NOT correct it — the header is lifted verbatim, so the Island
|
|
316
|
+
// re-mounts from the OLD props (zudolab/zudo-doc#3525). What corrects
|
|
317
|
+
// it is `ensureNestedIslandPropsRefresh`
|
|
318
|
+
// (transitions/nested-island-props-refresh.ts, registered from the
|
|
319
|
+
// island module): on BEFORE_SWAP_EVENT it copies the incoming
|
|
320
|
+
// document's data-props onto the live nested islands, and
|
|
321
|
+
// mountNewIslands re-reads the attribute at mount time (#3530)
|
|
317
322
|
// - Header nav + aria-current: NAV_OVERFLOW_SCRIPT re-runs on
|
|
318
|
-
// AFTER_NAVIGATE_EVENT (
|
|
323
|
+
// AFTER_NAVIGATE_EVENT (frozen by zudolab/zudo-doc#3534 — the
|
|
324
|
+
// `addEventListener(${afterNavigateEventLiteral}, initNavOverflow)`
|
|
325
|
+
// call at the tail of the template in
|
|
326
|
+
// scripts/gen-nav-overflow-script.mjs, verified (a))
|
|
319
327
|
// - LanguageSwitcher: its target hrefs ARE per-page (the equivalent
|
|
320
328
|
// page in each other locale), so within a same-locale persist window
|
|
321
329
|
// they WOULD go stale — the locale is constant but the path is not.
|
|
@@ -53,13 +53,28 @@ export function pathForMatch(
|
|
|
53
53
|
/**
|
|
54
54
|
* Segment-aware prefix test: nav path `/docs/guides` matches `/docs/guides`
|
|
55
55
|
* and `/docs/guides/...` but NOT `/docs/guideship`.
|
|
56
|
+
*
|
|
57
|
+
* Exported (not just module-private) so `scripts/gen-nav-overflow-script.mjs`
|
|
58
|
+
* can embed it verbatim via `.toString()` alongside `computeActiveNavPath` —
|
|
59
|
+
* that function closes over this one, so a bare `computeActiveNavPath.toString()`
|
|
60
|
+
* embed would reference an undefined `pathMatchesNavPath` in the browser (see
|
|
61
|
+
* the caution note on `computeActiveNavPath` below). Kept self-contained (no
|
|
62
|
+
* outer references) for the same reason. The generator freezes both into the
|
|
63
|
+
* committed `nav-overflow-generated-script.ts` literal at package build time
|
|
64
|
+
* (zudolab/zudo-doc#3534) — this is no longer a live per-module-eval embed.
|
|
56
65
|
*/
|
|
57
|
-
function pathMatchesNavPath(currentPath: string, navPath: string): boolean {
|
|
66
|
+
export function pathMatchesNavPath(currentPath: string, navPath: string): boolean {
|
|
58
67
|
if (currentPath === navPath) return true;
|
|
59
68
|
const prefix = navPath.endsWith("/") ? navPath : `${navPath}/`;
|
|
60
69
|
return currentPath.startsWith(prefix);
|
|
61
70
|
}
|
|
62
71
|
|
|
72
|
+
/**
|
|
73
|
+
* CAUTION (zudolab/zudo-doc#3398): this closes over `pathMatchesNavPath`
|
|
74
|
+
* above. `scripts/gen-nav-overflow-script.mjs` embeds both via `.toString()`
|
|
75
|
+
* (never this one alone) so the frozen browser script (`./nav-overflow-generated-script.ts`,
|
|
76
|
+
* zudolab/zudo-doc#3534) is self-contained.
|
|
77
|
+
*/
|
|
63
78
|
export function computeActiveNavPath(
|
|
64
79
|
navItems: readonly NavItemLike[],
|
|
65
80
|
pathForMatchValue: string,
|
|
@@ -13,11 +13,15 @@
|
|
|
13
13
|
//
|
|
14
14
|
// Tokens are ARRAYS, not whole strings, because the two consumers need
|
|
15
15
|
// different shapes: `header.tsx` joins them into `class={}` attributes, while
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
16
|
+
// `scripts/gen-nav-overflow-script.mjs` (zudolab/zudo-doc#3534 — the assembly
|
|
17
|
+
// that used to live in `nav-overflow-script.ts` at module-eval time, now
|
|
18
|
+
// frozen into the committed `nav-overflow-generated-script.ts` literal at
|
|
19
|
+
// package build time) reads these arrays off this module's exports and both
|
|
20
|
+
// spreads them into `classList.add/remove(...)` calls and joins them into
|
|
21
|
+
// `className` assignments. Keeping them composable per-part also lets the
|
|
22
|
+
// overflow-menu recipes (which intentionally differ from the header-bar
|
|
23
|
+
// items in padding/weight/base) reuse the shared color tokens instead of
|
|
24
|
+
// re-typing them.
|
|
21
25
|
|
|
22
26
|
// ── Header-bar top-level item: active-state color toggle ─────────────────
|
|
23
27
|
// setTopActive() (script) ↔ the top-level <a> in renderNavItem() (SSR).
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// GENERATED FILE — do not edit by hand.
|
|
2
|
+
// Produced by scripts/gen-nav-overflow-script.mjs (zudolab/zudo-doc#3534,
|
|
3
|
+
// epic #3533) from src/current-path/index.ts (CURRENT_PATH_SCRIPT_PRELUDE),
|
|
4
|
+
// src/header/nav-active.ts (pathMatchesNavPath/computeActiveNavPath,
|
|
5
|
+
// type-stripped), src/header/nav-class-tokens.ts (the twelve class-token
|
|
6
|
+
// arrays), and src/transitions/page-events.ts (AFTER_NAVIGATE_EVENT).
|
|
7
|
+
// Re-run `pnpm --filter @takazudo/zudo-doc gen:nav-overflow-script` (or any
|
|
8
|
+
// build/dev entry point, which already runs it) to regenerate after editing
|
|
9
|
+
// any of those source files.
|
|
10
|
+
//
|
|
11
|
+
// This file is committed to git (mirrors search-widget-script/generated-script.ts,
|
|
12
|
+
// zudolab/zudo-doc#3421 / #3431) — a deliberate departure from this repo's
|
|
13
|
+
// usual gitignored-generated-file convention (routes-src/, virtual-modules.d.ts).
|
|
14
|
+
// Regenerate AND commit the result after editing any of the four source files.
|
|
15
|
+
|
|
16
|
+
/** Returns the frozen desktop-nav overflow controller IIFE script. NOTE: the
|
|
17
|
+
* vitest drift guard imports buildNavOverflowScript from
|
|
18
|
+
* scripts/gen-nav-overflow-script.mjs (a fresh re-generation) — NEVER from
|
|
19
|
+
* this module: comparing NAV_OVERFLOW_SCRIPT below against this same file's
|
|
20
|
+
* function would be a vacuous self-comparison. */
|
|
21
|
+
export function buildNavOverflowScript(): string {
|
|
22
|
+
return "(function () {\n var cleanupNavOverflow = null;\n\n function trimSlashes(p) {\n while (p.length > 1 && p.charAt(p.length - 1) === \"/\") p = p.slice(0, -1);\n return p || \"/\";\n }\n\n function navPathname(a) {\n try { return trimSlashes(new URL(a.href, location.href).pathname); }\n catch (e) { return \"\"; }\n }\n\n // Explicit current-route override, embedded from current-path/index.ts so\n // this script cannot drift from the three other read sites\n // (zudolab/zudo-doc#3398, #3408).\n var CURRENT_PATH_DATASET_KEY=\"zdCurrentPath\";var readCurrentPath=function readCurrentPath(datasetKey, explicit) {\n const override = typeof document === \"undefined\" ? void 0 : document.documentElement.dataset[datasetKey];\n return explicit || override || (typeof window === \"undefined\" ? void 0 : window.location.pathname);\n};\n\n // Shared matching core (zudolab/zudo-doc#3398): embedded verbatim from\n // nav-active.ts so this script's longest-match walk cannot drift from the\n // SSR header's own computeActiveNavPath call (header.tsx). computeActiveNavPath\n // closes over pathMatchesNavPath, so both are embedded together.\n var pathMatchesNavPath = function pathMatchesNavPath(currentPath, navPath) {\n if (currentPath === navPath) return true;\n const prefix = navPath.endsWith(\"/\") ? navPath : `${navPath}/`;\n return currentPath.startsWith(prefix);\n};\n var computeActiveNavPath = function computeActiveNavPath(navItems, pathForMatchValue) {\n const allNavPaths = navItems.flatMap((item) => {\n const paths = [item.path];\n if (item.children) {\n paths.push(...item.children.map((child) => child.path));\n }\n return paths;\n });\n return allNavPaths.filter((p) => pathMatchesNavPath(pathForMatchValue, p)).sort((a, b) => b.length - a.length)[0];\n};\n\n // Recompute which header nav item is \"active\" from the CURRENT URL and\n // repaint the highlight. SSR sets the active item on first paint, but the\n // header is persisted across same-locale client-router swaps\n // (data-zfb-transition-persist), so without this the highlight would stay\n // frozen on the page where the header was first rendered. Mirrors the\n // sidebar island's client-side approach (match the current path against\n // each entry's href) and the SSR longest-match + dropdown-parent rules.\n // URL-based: hrefs and the current path both carry the base + locale\n // prefix, so they compare directly without stripping.\n function applyActiveNav() {\n var nav = document.querySelector(\"[data-header-nav]\");\n if (!nav) return;\n var topItems = Array.from(nav.querySelectorAll(\":scope > [data-nav-item]\"));\n if (topItems.length === 0) return;\n\n var cur = trimSlashes(readCurrentPath(CURRENT_PATH_DATASET_KEY));\n\n // Build NavItemLike-shaped entries from the live DOM so the shared\n // computeActiveNavPath can do the deepest-match walk — the same call\n // shape the SSR header uses (matches computeActiveNavPath). A dropdown\n // missing its own top-level anchor is skipped entirely (path \"\" would\n // otherwise match every current path — pathMatchesNavPath treats \"\" as\n // the root \"/\"), mirroring the parentLink guard used below for the same\n // malformed-markup case.\n var navItems = [];\n topItems.forEach(function (it) {\n var isDropdown = it.hasAttribute(\"data-nav-item-dropdown\");\n var topA = isDropdown ? it.querySelector(\":scope > a\") : it;\n if (!topA) return;\n var children = [];\n if (isDropdown) {\n it.querySelectorAll(\":scope > div a\").forEach(function (c) {\n children.push({ path: navPathname(c) });\n });\n }\n navItems.push({ path: navPathname(topA), children: children });\n });\n\n var activePath = computeActiveNavPath(navItems, cur) || \"\";\n\n function setTopActive(a, active) {\n if (!a) return;\n if (active) {\n a.classList.add(\"bg-fg\", \"text-bg\");\n a.classList.remove(\"text-muted\", \"hover:text-accent\", \"hover:underline\", \"focus:underline\", \"focus:text-accent\");\n a.setAttribute(\"aria-current\", \"page\");\n } else {\n a.classList.remove(\"bg-fg\", \"text-bg\");\n a.classList.add(\"text-muted\", \"hover:text-accent\", \"hover:underline\", \"focus:underline\", \"focus:text-accent\");\n a.removeAttribute(\"aria-current\");\n }\n }\n\n topItems.forEach(function (it) {\n var isDropdown = it.hasAttribute(\"data-nav-item-dropdown\");\n var topA = isDropdown ? it.querySelector(\":scope > a\") : it;\n var topActive = false;\n\n if (isDropdown) {\n var parentMatch = !!topA && navPathname(topA) === activePath && activePath !== \"\";\n var anyChild = false;\n it.querySelectorAll(\":scope > div a\").forEach(function (c) {\n var childActive = navPathname(c) === activePath && activePath !== \"\";\n if (childActive) {\n anyChild = true;\n c.setAttribute(\"data-active\", \"\");\n c.classList.add(\"font-bold\", \"text-accent\");\n c.classList.remove(\"text-fg\", \"hover:text-accent\", \"focus-visible:text-accent\");\n } else {\n c.removeAttribute(\"data-active\");\n c.classList.remove(\"font-bold\", \"text-accent\");\n c.classList.add(\"text-fg\", \"hover:text-accent\", \"focus-visible:text-accent\");\n }\n });\n topActive = parentMatch || anyChild;\n var svg = topA ? topA.querySelector(\"svg\") : null;\n if (svg) {\n if (topActive) { svg.classList.add(\"text-bg\"); svg.classList.remove(\"text-muted\"); }\n else { svg.classList.add(\"text-muted\"); svg.classList.remove(\"text-bg\"); }\n }\n } else {\n topActive = activePath !== \"\" && navPathname(topA) === activePath;\n }\n\n setTopActive(topA, topActive);\n });\n }\n\n function initNavOverflow() {\n if (cleanupNavOverflow) cleanupNavOverflow();\n\n // Repaint the active highlight for the current URL before measuring /\n // cloning, so the overflow \"···\" menu mirrors the correct active state.\n applyActiveNav();\n\n var nav = document.querySelector(\"[data-header-nav]\");\n var moreContainer = document.querySelector(\"[data-nav-more]\");\n var moreMenu = document.querySelector(\"[data-nav-more-menu]\");\n var moreToggle = document.querySelector(\"[data-nav-more-toggle]\");\n if (!nav || !moreContainer || !moreMenu || !moreToggle) return;\n\n var items = Array.from(nav.querySelectorAll(\":scope > [data-nav-item]\"));\n if (items.length === 0) return;\n\n var controller = new AbortController();\n\n function update() {\n items.forEach(function (el) { el.style.display = \"\"; });\n moreContainer.style.display = \"\";\n moreMenu.innerHTML = \"\";\n moreMenu.classList.add(\"hidden\");\n moreToggle.setAttribute(\"aria-expanded\", \"false\");\n\n var itemWidths = items.map(function (el) { return el.offsetWidth; });\n var moreWidth = moreContainer.offsetWidth;\n var navGap = parseFloat(getComputedStyle(nav).columnGap) || 0;\n var available = nav.clientWidth;\n\n if (available <= 0) {\n moreContainer.style.display = \"none\";\n return;\n }\n\n var total = 0;\n for (var i = 0; i < itemWidths.length; i++) {\n total += itemWidths[i] + (i > 0 ? navGap : 0);\n }\n\n if (total <= available) {\n moreContainer.style.display = \"none\";\n return;\n }\n\n var used = 0;\n var cutoffIndex = 0;\n\n for (var i2 = 0; i2 < items.length; i2++) {\n var w = itemWidths[i2] + (i2 > 0 ? navGap : 0);\n if (used + w > available - moreWidth - navGap) break;\n used += w;\n cutoffIndex = i2 + 1;\n }\n\n for (var i3 = cutoffIndex; i3 < items.length; i3++) {\n items[i3].style.display = \"none\";\n }\n\n for (var i4 = cutoffIndex; i4 < items.length; i4++) {\n var el = items[i4];\n var isDropdown = el.hasAttribute(\"data-nav-item-dropdown\");\n\n if (isDropdown) {\n var parentLink = el.querySelector(\":scope > a\");\n var childLinks = el.querySelectorAll(\":scope > div a\");\n if (parentLink) {\n var li = document.createElement(\"li\");\n var a = document.createElement(\"a\");\n a.href = parentLink.href;\n var parentText = parentLink.textContent ? parentLink.textContent.trim().replace(/\\s+/g, \" \") : \"\";\n a.textContent = parentText;\n a.className = \"block px-hsp-md py-vsp-2xs text-small font-bold hover:bg-accent/10 hover:underline focus-visible:underline focus-visible:text-accent text-fg hover:text-accent\";\n if (parentLink.getAttribute(\"aria-current\") === \"page\") {\n a.className += \" text-accent\";\n }\n li.appendChild(a);\n moreMenu.appendChild(li);\n }\n childLinks.forEach(function (child) {\n var li = document.createElement(\"li\");\n var a = document.createElement(\"a\");\n a.href = child.href;\n a.textContent = child.textContent ? child.textContent.trim() : \"\";\n var isChildActive = child.hasAttribute(\"data-active\");\n a.className = isChildActive\n ? \"block pl-hsp-xl pr-hsp-md py-vsp-2xs text-small font-bold text-accent hover:bg-accent/10 hover:underline focus-visible:underline\"\n : \"block pl-hsp-xl pr-hsp-md py-vsp-2xs text-small text-fg hover:bg-accent/10 hover:text-accent hover:underline focus-visible:underline focus-visible:text-accent\";\n li.appendChild(a);\n moreMenu.appendChild(li);\n });\n } else {\n var anchor = el;\n var li2 = document.createElement(\"li\");\n var a2 = document.createElement(\"a\");\n a2.href = anchor.href;\n a2.textContent = anchor.textContent ? anchor.textContent.trim() : \"\";\n a2.className = \"block px-hsp-md py-vsp-2xs text-small hover:bg-accent/10 hover:underline focus-visible:underline focus-visible:text-accent text-fg hover:text-accent\";\n if (anchor.getAttribute(\"aria-current\") === \"page\") {\n a2.className += \" font-bold text-accent\";\n }\n li2.appendChild(a2);\n moreMenu.appendChild(li2);\n }\n }\n }\n\n moreToggle.addEventListener(\"click\", function () {\n var isOpen = !moreMenu.classList.contains(\"hidden\");\n moreMenu.classList.toggle(\"hidden\", isOpen);\n moreToggle.setAttribute(\"aria-expanded\", String(!isOpen));\n }, { signal: controller.signal });\n\n document.addEventListener(\"click\", function (e) {\n if (!moreContainer.contains(e.target)) {\n moreMenu.classList.add(\"hidden\");\n moreToggle.setAttribute(\"aria-expanded\", \"false\");\n }\n }, { signal: controller.signal });\n\n document.addEventListener(\"keydown\", function (e) {\n if (e.key !== \"Escape\") return;\n if (!moreMenu.classList.contains(\"hidden\")) {\n moreMenu.classList.add(\"hidden\");\n moreToggle.setAttribute(\"aria-expanded\", \"false\");\n moreToggle.focus();\n return;\n }\n var active = document.activeElement;\n var dropdown = active && active.closest ? active.closest(\"[data-nav-item-dropdown]\") : null;\n if (dropdown && active && active.blur) {\n active.blur();\n }\n }, { signal: controller.signal });\n\n var dropdowns = nav.querySelectorAll(\"[data-nav-item-dropdown]\");\n dropdowns.forEach(function (dd) {\n var trigger = dd.querySelector(\":scope > a\");\n if (!trigger) return;\n function setExpanded(v) {\n trigger.setAttribute(\"aria-expanded\", String(v));\n }\n dd.addEventListener(\"mouseenter\", function () { setExpanded(true); }, { signal: controller.signal });\n dd.addEventListener(\"mouseleave\", function () { setExpanded(false); }, { signal: controller.signal });\n dd.addEventListener(\"focusin\", function () { setExpanded(true); }, { signal: controller.signal });\n dd.addEventListener(\"focusout\", function (e) {\n if (!dd.contains(e.relatedTarget)) {\n setExpanded(false);\n }\n }, { signal: controller.signal });\n });\n\n var ro = new ResizeObserver(update);\n ro.observe(nav);\n controller.signal.addEventListener(\"abort\", function () { ro.disconnect(); });\n\n document.fonts.ready.then(update);\n\n update();\n\n cleanupNavOverflow = function () { controller.abort(); };\n }\n\n initNavOverflow();\n document.addEventListener(\"zfb:after-swap\", initNavOverflow);\n})();";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Client-side script string for the desktop header nav overflow controller.
|
|
26
|
+
* See the module header of this generator for the embedding contract; see
|
|
27
|
+
* current-path/index.ts / header/nav-active.ts / header/nav-class-tokens.ts /
|
|
28
|
+
* transitions/page-events.ts for the frozen sources. */
|
|
29
|
+
export const NAV_OVERFLOW_SCRIPT: string = buildNavOverflowScript();
|