@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
|
@@ -29,307 +29,29 @@
|
|
|
29
29
|
// Kept as a separate module (rather than inlined in `header.tsx`) so
|
|
30
30
|
// the JSX file stays focused on markup and so future edits to the
|
|
31
31
|
// script can be reviewed in isolation.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
// the
|
|
54
|
-
// script
|
|
55
|
-
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
tokens.map((token) => JSON.stringify(token)).join(", ");
|
|
59
|
-
|
|
60
|
-
// -> `"bg-fg text-bg"` — a single class-string literal for `className = ...`.
|
|
61
|
-
const clsLiteral = (tokens: readonly string[]): string =>
|
|
62
|
-
JSON.stringify(tokens.join(" "));
|
|
63
|
-
|
|
64
|
-
// -> `" font-bold text-accent"` — leading-space append for `className += ...`.
|
|
65
|
-
const clsAppend = (tokens: readonly string[]): string =>
|
|
66
|
-
JSON.stringify(" " + tokens.join(" "));
|
|
67
|
-
|
|
68
|
-
export const NAV_OVERFLOW_SCRIPT = `(function () {
|
|
69
|
-
var cleanupNavOverflow = null;
|
|
70
|
-
|
|
71
|
-
function trimSlashes(p) {
|
|
72
|
-
while (p.length > 1 && p.charAt(p.length - 1) === "/") p = p.slice(0, -1);
|
|
73
|
-
return p || "/";
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function navPathname(a) {
|
|
77
|
-
try { return trimSlashes(new URL(a.href, location.href).pathname); }
|
|
78
|
-
catch (e) { return ""; }
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function isUnderPath(cur, p) {
|
|
82
|
-
if (!p) return false;
|
|
83
|
-
if (cur === p) return true;
|
|
84
|
-
return p !== "/" && cur.indexOf(p + "/") === 0;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Recompute which header nav item is "active" from the CURRENT URL and
|
|
88
|
-
// repaint the highlight. SSR sets the active item on first paint, but the
|
|
89
|
-
// header is persisted across same-locale client-router swaps
|
|
90
|
-
// (data-zfb-transition-persist), so without this the highlight would stay
|
|
91
|
-
// frozen on the page where the header was first rendered. Mirrors the
|
|
92
|
-
// sidebar island's client-side approach (match location.pathname against
|
|
93
|
-
// each entry's href) and the SSR longest-match + dropdown-parent rules.
|
|
94
|
-
// URL-based: hrefs and location.pathname both carry the base + locale
|
|
95
|
-
// prefix, so they compare directly without stripping.
|
|
96
|
-
function applyActiveNav() {
|
|
97
|
-
var nav = document.querySelector("[data-header-nav]");
|
|
98
|
-
if (!nav) return;
|
|
99
|
-
var topItems = Array.from(nav.querySelectorAll(":scope > [data-nav-item]"));
|
|
100
|
-
if (topItems.length === 0) return;
|
|
101
|
-
|
|
102
|
-
var cur = trimSlashes(location.pathname);
|
|
103
|
-
|
|
104
|
-
// Deepest (longest) nav path the current URL lives under, across both
|
|
105
|
-
// top-level and dropdown-child paths — matches computeActiveNavPath.
|
|
106
|
-
var activePath = "";
|
|
107
|
-
topItems.forEach(function (it) {
|
|
108
|
-
var isDropdown = it.hasAttribute("data-nav-item-dropdown");
|
|
109
|
-
var topA = isDropdown ? it.querySelector(":scope > a") : it;
|
|
110
|
-
if (topA) {
|
|
111
|
-
var tp = navPathname(topA);
|
|
112
|
-
if (isUnderPath(cur, tp) && tp.length > activePath.length) activePath = tp;
|
|
113
|
-
}
|
|
114
|
-
if (isDropdown) {
|
|
115
|
-
it.querySelectorAll(":scope > div a").forEach(function (c) {
|
|
116
|
-
var cp = navPathname(c);
|
|
117
|
-
if (isUnderPath(cur, cp) && cp.length > activePath.length) activePath = cp;
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
function setTopActive(a, active) {
|
|
123
|
-
if (!a) return;
|
|
124
|
-
if (active) {
|
|
125
|
-
a.classList.add(${clsArgs(NAV_TOP_ACTIVE)});
|
|
126
|
-
a.classList.remove(${clsArgs(NAV_TOP_INACTIVE)});
|
|
127
|
-
a.setAttribute("aria-current", "page");
|
|
128
|
-
} else {
|
|
129
|
-
a.classList.remove(${clsArgs(NAV_TOP_ACTIVE)});
|
|
130
|
-
a.classList.add(${clsArgs(NAV_TOP_INACTIVE)});
|
|
131
|
-
a.removeAttribute("aria-current");
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
topItems.forEach(function (it) {
|
|
136
|
-
var isDropdown = it.hasAttribute("data-nav-item-dropdown");
|
|
137
|
-
var topA = isDropdown ? it.querySelector(":scope > a") : it;
|
|
138
|
-
var topActive = false;
|
|
139
|
-
|
|
140
|
-
if (isDropdown) {
|
|
141
|
-
var parentMatch = !!topA && navPathname(topA) === activePath && activePath !== "";
|
|
142
|
-
var anyChild = false;
|
|
143
|
-
it.querySelectorAll(":scope > div a").forEach(function (c) {
|
|
144
|
-
var childActive = navPathname(c) === activePath && activePath !== "";
|
|
145
|
-
if (childActive) {
|
|
146
|
-
anyChild = true;
|
|
147
|
-
c.setAttribute("data-active", "");
|
|
148
|
-
c.classList.add(${clsArgs(NAV_CHILD_ACTIVE)});
|
|
149
|
-
c.classList.remove(${clsArgs(NAV_CHILD_INACTIVE)});
|
|
150
|
-
} else {
|
|
151
|
-
c.removeAttribute("data-active");
|
|
152
|
-
c.classList.remove(${clsArgs(NAV_CHILD_ACTIVE)});
|
|
153
|
-
c.classList.add(${clsArgs(NAV_CHILD_INACTIVE)});
|
|
154
|
-
}
|
|
155
|
-
});
|
|
156
|
-
topActive = parentMatch || anyChild;
|
|
157
|
-
var svg = topA ? topA.querySelector("svg") : null;
|
|
158
|
-
if (svg) {
|
|
159
|
-
if (topActive) { svg.classList.add(${clsArgs(NAV_CHEVRON_ACTIVE)}); svg.classList.remove(${clsArgs(NAV_CHEVRON_INACTIVE)}); }
|
|
160
|
-
else { svg.classList.add(${clsArgs(NAV_CHEVRON_INACTIVE)}); svg.classList.remove(${clsArgs(NAV_CHEVRON_ACTIVE)}); }
|
|
161
|
-
}
|
|
162
|
-
} else {
|
|
163
|
-
topActive = activePath !== "" && navPathname(topA) === activePath;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
setTopActive(topA, topActive);
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function initNavOverflow() {
|
|
171
|
-
if (cleanupNavOverflow) cleanupNavOverflow();
|
|
172
|
-
|
|
173
|
-
// Repaint the active highlight for the current URL before measuring /
|
|
174
|
-
// cloning, so the overflow "···" menu mirrors the correct active state.
|
|
175
|
-
applyActiveNav();
|
|
176
|
-
|
|
177
|
-
var nav = document.querySelector("[data-header-nav]");
|
|
178
|
-
var moreContainer = document.querySelector("[data-nav-more]");
|
|
179
|
-
var moreMenu = document.querySelector("[data-nav-more-menu]");
|
|
180
|
-
var moreToggle = document.querySelector("[data-nav-more-toggle]");
|
|
181
|
-
if (!nav || !moreContainer || !moreMenu || !moreToggle) return;
|
|
182
|
-
|
|
183
|
-
var items = Array.from(nav.querySelectorAll(":scope > [data-nav-item]"));
|
|
184
|
-
if (items.length === 0) return;
|
|
185
|
-
|
|
186
|
-
var controller = new AbortController();
|
|
187
|
-
|
|
188
|
-
function update() {
|
|
189
|
-
items.forEach(function (el) { el.style.display = ""; });
|
|
190
|
-
moreContainer.style.display = "";
|
|
191
|
-
moreMenu.innerHTML = "";
|
|
192
|
-
moreMenu.classList.add("hidden");
|
|
193
|
-
moreToggle.setAttribute("aria-expanded", "false");
|
|
194
|
-
|
|
195
|
-
var itemWidths = items.map(function (el) { return el.offsetWidth; });
|
|
196
|
-
var moreWidth = moreContainer.offsetWidth;
|
|
197
|
-
var navGap = parseFloat(getComputedStyle(nav).columnGap) || 0;
|
|
198
|
-
var available = nav.clientWidth;
|
|
199
|
-
|
|
200
|
-
if (available <= 0) {
|
|
201
|
-
moreContainer.style.display = "none";
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
var total = 0;
|
|
206
|
-
for (var i = 0; i < itemWidths.length; i++) {
|
|
207
|
-
total += itemWidths[i] + (i > 0 ? navGap : 0);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
if (total <= available) {
|
|
211
|
-
moreContainer.style.display = "none";
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
var used = 0;
|
|
216
|
-
var cutoffIndex = 0;
|
|
217
|
-
|
|
218
|
-
for (var i2 = 0; i2 < items.length; i2++) {
|
|
219
|
-
var w = itemWidths[i2] + (i2 > 0 ? navGap : 0);
|
|
220
|
-
if (used + w > available - moreWidth - navGap) break;
|
|
221
|
-
used += w;
|
|
222
|
-
cutoffIndex = i2 + 1;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
for (var i3 = cutoffIndex; i3 < items.length; i3++) {
|
|
226
|
-
items[i3].style.display = "none";
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
for (var i4 = cutoffIndex; i4 < items.length; i4++) {
|
|
230
|
-
var el = items[i4];
|
|
231
|
-
var isDropdown = el.hasAttribute("data-nav-item-dropdown");
|
|
232
|
-
|
|
233
|
-
if (isDropdown) {
|
|
234
|
-
var parentLink = el.querySelector(":scope > a");
|
|
235
|
-
var childLinks = el.querySelectorAll(":scope > div a");
|
|
236
|
-
if (parentLink) {
|
|
237
|
-
var li = document.createElement("li");
|
|
238
|
-
var a = document.createElement("a");
|
|
239
|
-
a.href = parentLink.href;
|
|
240
|
-
var parentText = parentLink.textContent ? parentLink.textContent.trim().replace(/\\s+/g, " ") : "";
|
|
241
|
-
a.textContent = parentText;
|
|
242
|
-
a.className = ${clsLiteral(NAV_MENU_PARENT)};
|
|
243
|
-
if (parentLink.getAttribute("aria-current") === "page") {
|
|
244
|
-
a.className += ${clsAppend(NAV_MENU_PARENT_ACTIVE_SUFFIX)};
|
|
245
|
-
}
|
|
246
|
-
li.appendChild(a);
|
|
247
|
-
moreMenu.appendChild(li);
|
|
248
|
-
}
|
|
249
|
-
childLinks.forEach(function (child) {
|
|
250
|
-
var li = document.createElement("li");
|
|
251
|
-
var a = document.createElement("a");
|
|
252
|
-
a.href = child.href;
|
|
253
|
-
a.textContent = child.textContent ? child.textContent.trim() : "";
|
|
254
|
-
var isChildActive = child.hasAttribute("data-active");
|
|
255
|
-
a.className = isChildActive
|
|
256
|
-
? ${clsLiteral(NAV_MENU_CHILD_ACTIVE)}
|
|
257
|
-
: ${clsLiteral(NAV_MENU_CHILD_INACTIVE)};
|
|
258
|
-
li.appendChild(a);
|
|
259
|
-
moreMenu.appendChild(li);
|
|
260
|
-
});
|
|
261
|
-
} else {
|
|
262
|
-
var anchor = el;
|
|
263
|
-
var li2 = document.createElement("li");
|
|
264
|
-
var a2 = document.createElement("a");
|
|
265
|
-
a2.href = anchor.href;
|
|
266
|
-
a2.textContent = anchor.textContent ? anchor.textContent.trim() : "";
|
|
267
|
-
a2.className = ${clsLiteral(NAV_MENU_PLAIN)};
|
|
268
|
-
if (anchor.getAttribute("aria-current") === "page") {
|
|
269
|
-
a2.className += ${clsAppend(NAV_MENU_PLAIN_ACTIVE_SUFFIX)};
|
|
270
|
-
}
|
|
271
|
-
li2.appendChild(a2);
|
|
272
|
-
moreMenu.appendChild(li2);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
moreToggle.addEventListener("click", function () {
|
|
278
|
-
var isOpen = !moreMenu.classList.contains("hidden");
|
|
279
|
-
moreMenu.classList.toggle("hidden", isOpen);
|
|
280
|
-
moreToggle.setAttribute("aria-expanded", String(!isOpen));
|
|
281
|
-
}, { signal: controller.signal });
|
|
282
|
-
|
|
283
|
-
document.addEventListener("click", function (e) {
|
|
284
|
-
if (!moreContainer.contains(e.target)) {
|
|
285
|
-
moreMenu.classList.add("hidden");
|
|
286
|
-
moreToggle.setAttribute("aria-expanded", "false");
|
|
287
|
-
}
|
|
288
|
-
}, { signal: controller.signal });
|
|
289
|
-
|
|
290
|
-
document.addEventListener("keydown", function (e) {
|
|
291
|
-
if (e.key !== "Escape") return;
|
|
292
|
-
if (!moreMenu.classList.contains("hidden")) {
|
|
293
|
-
moreMenu.classList.add("hidden");
|
|
294
|
-
moreToggle.setAttribute("aria-expanded", "false");
|
|
295
|
-
moreToggle.focus();
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
var active = document.activeElement;
|
|
299
|
-
var dropdown = active && active.closest ? active.closest("[data-nav-item-dropdown]") : null;
|
|
300
|
-
if (dropdown && active && active.blur) {
|
|
301
|
-
active.blur();
|
|
302
|
-
}
|
|
303
|
-
}, { signal: controller.signal });
|
|
304
|
-
|
|
305
|
-
var dropdowns = nav.querySelectorAll("[data-nav-item-dropdown]");
|
|
306
|
-
dropdowns.forEach(function (dd) {
|
|
307
|
-
var trigger = dd.querySelector(":scope > a");
|
|
308
|
-
if (!trigger) return;
|
|
309
|
-
function setExpanded(v) {
|
|
310
|
-
trigger.setAttribute("aria-expanded", String(v));
|
|
311
|
-
}
|
|
312
|
-
dd.addEventListener("mouseenter", function () { setExpanded(true); }, { signal: controller.signal });
|
|
313
|
-
dd.addEventListener("mouseleave", function () { setExpanded(false); }, { signal: controller.signal });
|
|
314
|
-
dd.addEventListener("focusin", function () { setExpanded(true); }, { signal: controller.signal });
|
|
315
|
-
dd.addEventListener("focusout", function (e) {
|
|
316
|
-
if (!dd.contains(e.relatedTarget)) {
|
|
317
|
-
setExpanded(false);
|
|
318
|
-
}
|
|
319
|
-
}, { signal: controller.signal });
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
var ro = new ResizeObserver(update);
|
|
323
|
-
ro.observe(nav);
|
|
324
|
-
controller.signal.addEventListener("abort", function () { ro.disconnect(); });
|
|
325
|
-
|
|
326
|
-
document.fonts.ready.then(update);
|
|
327
|
-
|
|
328
|
-
update();
|
|
329
|
-
|
|
330
|
-
cleanupNavOverflow = function () { controller.abort(); };
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
initNavOverflow();
|
|
334
|
-
document.addEventListener(${JSON.stringify(AFTER_NAVIGATE_EVENT)}, initNavOverflow);
|
|
335
|
-
})();`;
|
|
32
|
+
//
|
|
33
|
+
// FROZEN (zudolab/zudo-doc#3534, epic #3533): the script body used to be
|
|
34
|
+
// assembled here, at module-eval time, from three `Function.prototype.toString()`
|
|
35
|
+
// embeddings (`CURRENT_PATH_SCRIPT_PRELUDE`, `pathMatchesNavPath`,
|
|
36
|
+
// `computeActiveNavPath`) plus splice formatters over the twelve
|
|
37
|
+
// `nav-class-tokens.ts` arrays. That made the emitted bytes depend on the
|
|
38
|
+
// CONSUMING bundler (zudolab/zudo-doc#3502), so no CSP hash could be pinned
|
|
39
|
+
// against a stable value. The assembly logic now lives in
|
|
40
|
+
// `scripts/gen-nav-overflow-script.mjs`, which freezes it ONCE at package
|
|
41
|
+
// build time into the committed `./nav-overflow-generated-script.ts` literal
|
|
42
|
+
// re-exported below. Regenerate via
|
|
43
|
+
// `pnpm --filter @takazudo/zudo-doc gen:nav-overflow-script` after editing
|
|
44
|
+
// any of the four source files it reads from (see the generator's header
|
|
45
|
+
// comment for the list); the vitest guard at
|
|
46
|
+
// `src/header/__tests__/nav-overflow-script.test.ts` proves the committed
|
|
47
|
+
// literal still matches a fresh regeneration.
|
|
48
|
+
//
|
|
49
|
+
// EJECTED COPIES (`zudo-doc eject header`): the generator is NOT shipped, so
|
|
50
|
+
// in an ejected tree the re-exported literal is permanently frozen — editing
|
|
51
|
+
// the ejected `./nav-class-tokens.ts` or `./nav-active.ts` changes the SSR
|
|
52
|
+
// markup (header.tsx imports them live) but NOT this client script, breaking
|
|
53
|
+
// the SSR ↔ runtime class lockstep those files exist to guarantee. To change
|
|
54
|
+
// the client script in an ejected copy, edit the literal in
|
|
55
|
+
// `./nav-overflow-generated-script.ts` directly (it is plain JS in a string)
|
|
56
|
+
// and keep it in step with your token edits by hand.
|
|
57
|
+
export { NAV_OVERFLOW_SCRIPT } from "./nav-overflow-generated-script.js";
|
|
@@ -7,10 +7,22 @@
|
|
|
7
7
|
import { useState, useEffect } from "preact/hooks";
|
|
8
8
|
// After zudolab/zudo-doc#1335 the host components pull lifecycle event names
|
|
9
9
|
// from the v2 transitions module rather than hard-coding `astro:*` literals.
|
|
10
|
-
|
|
10
|
+
// `ensureNestedIslandPropsRefresh` is imported through the barrel (not the
|
|
11
|
+
// deep `./nested-island-props-refresh.js` path) on purpose: eject rewrites
|
|
12
|
+
// EVERY `../transitions/<anything>.js` import to the single specifier
|
|
13
|
+
// `@takazudo/zudo-doc/transitions`, so two distinct relative imports would
|
|
14
|
+
// collapse into duplicate import statements in an ejected copy.
|
|
15
|
+
import { AFTER_NAVIGATE_EVENT, ensureNestedIslandPropsRefresh } from "../transitions/index.js";
|
|
11
16
|
import { SidebarTree } from "../sidebar-tree-island/index.js";
|
|
12
17
|
import type { SidebarNavNode, SidebarRootMenuItem, SidebarLocaleLink } from "../sidebar/types.js";
|
|
13
18
|
|
|
19
|
+
// This island lives INSIDE the persisted `<header>`, so a same-locale swap
|
|
20
|
+
// lifts it verbatim and would re-mount it from the previous page's serialized
|
|
21
|
+
// props (zudolab/zudo-doc#3525). The refresh has to outlive the island's own
|
|
22
|
+
// mount/unmount cycle across a swap, so it is installed at document lifetime
|
|
23
|
+
// here rather than from an effect. SSR evaluation is a safe no-op.
|
|
24
|
+
ensureNestedIslandPropsRefresh();
|
|
25
|
+
|
|
14
26
|
const cx = (...classes: Array<string | false | null | undefined>) =>
|
|
15
27
|
classes.filter(Boolean).join(" ");
|
|
16
28
|
|
|
@@ -18,6 +18,7 @@ import { smartBreakToHtml } from "../smart-break/index.js";
|
|
|
18
18
|
import { AFTER_NAVIGATE_EVENT, BEFORE_NAVIGATE_EVENT } from "../transitions/index.js";
|
|
19
19
|
import { filterTree } from "../sidebar-filter/index.js";
|
|
20
20
|
import { findActiveSlug, normalizePath } from "../sidebar-active-slug/index.js";
|
|
21
|
+
import { CURRENT_PATH_DATASET_KEY, readCurrentPath } from "../current-path/index.js";
|
|
21
22
|
import { ensureSidebarScrollPreserve } from "./sidebar-scroll-preserve.js";
|
|
22
23
|
|
|
23
24
|
// The persisted aside can transiently tear down and re-mount its SidebarTree
|
|
@@ -61,38 +62,54 @@ function saveOpenSet(set: Set<string>) {
|
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
/**
|
|
64
|
-
* Derive the active slug from
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
65
|
+
* Derive the active slug from an explicit current-route input. Resolution
|
|
66
|
+
* order is owned by {@link readCurrentPath}: the `pathname` argument, then the
|
|
67
|
+
* `data-zd-current-path` override, then `window.location.pathname`. Used as a
|
|
68
|
+
* hydration-time fallback when the parent island does not forward
|
|
69
|
+
* `currentSlug` through its prop boundary, and at every View Transition to
|
|
70
|
+
* keep the highlight in sync.
|
|
71
|
+
*
|
|
72
|
+
* Returns `undefined` both when no pathname is resolvable AND when the
|
|
73
|
+
* resolved pathname matches no route (e.g. the literal "srcdoc") — callers
|
|
74
|
+
* must treat `undefined` as "no update," preserving whatever active slug is
|
|
75
|
+
* already set rather than clearing it.
|
|
68
76
|
*/
|
|
69
|
-
function
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
77
|
+
function deriveActiveSlug(nodes: SidebarNavNode[], pathname?: string): string | undefined {
|
|
78
|
+
// An empty-string `pathname` must fall through to the live sources instead
|
|
79
|
+
// of silently disabling derivation — SidebarWithDefaults defaults its own
|
|
80
|
+
// currentPath to "", so "" is the natural absent shape.
|
|
81
|
+
const resolved = readCurrentPath(CURRENT_PATH_DATASET_KEY, pathname);
|
|
82
|
+
if (!resolved) return undefined;
|
|
83
|
+
return findActiveSlug(nodes, normalizePath(resolved));
|
|
73
84
|
}
|
|
74
85
|
|
|
75
86
|
/**
|
|
76
87
|
* Track the current active slug, updating on View Transition navigations.
|
|
77
88
|
*
|
|
78
89
|
* The initial-state initialiser prefers the SSR-supplied `initial` prop, but
|
|
79
|
-
* falls back to
|
|
80
|
-
* prop is missing.
|
|
90
|
+
* falls back to `deriveActiveSlug` (explicit `currentPath` input, then the
|
|
91
|
+
* override/location fallbacks) when the prop is missing.
|
|
81
92
|
*/
|
|
82
|
-
function useActiveSlug(nodes: SidebarNavNode[], initial?: string): string | undefined {
|
|
93
|
+
function useActiveSlug(nodes: SidebarNavNode[], initial?: string, currentPath?: string): string | undefined {
|
|
83
94
|
const [slug, setSlug] = useState<string | undefined>(() =>
|
|
84
|
-
initial !== undefined ? initial :
|
|
95
|
+
initial !== undefined ? initial : deriveActiveSlug(nodes, currentPath),
|
|
85
96
|
);
|
|
86
97
|
|
|
87
98
|
useEffect(() => {
|
|
88
|
-
const update = () => {
|
|
89
|
-
const found =
|
|
99
|
+
const update = (pathname?: string) => {
|
|
100
|
+
const found = deriveActiveSlug(nodes, pathname);
|
|
90
101
|
if (found !== undefined) setSlug(found);
|
|
91
102
|
};
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
103
|
+
// The static `currentPath` prop is an SSR-serialized value for the page the
|
|
104
|
+
// island first hydrated on — valid at hydration time only. Post-navigation
|
|
105
|
+
// updates must re-read the LIVE sources (dataset override, then
|
|
106
|
+
// location.pathname); recomputing from the frozen prop would pin the
|
|
107
|
+
// highlight to the first-loaded page across client-router navigations.
|
|
108
|
+
const onNavigate = () => update();
|
|
109
|
+
update(currentPath);
|
|
110
|
+
document.addEventListener(AFTER_NAVIGATE_EVENT, onNavigate);
|
|
111
|
+
return () => document.removeEventListener(AFTER_NAVIGATE_EVENT, onNavigate);
|
|
112
|
+
}, [nodes, currentPath]);
|
|
96
113
|
|
|
97
114
|
return slug;
|
|
98
115
|
}
|
|
@@ -143,6 +160,13 @@ function RootMenuItemEntry({ item }: { item: SidebarRootMenuItem }) {
|
|
|
143
160
|
export interface SidebarTreeProps {
|
|
144
161
|
nodes: SidebarNavNode[];
|
|
145
162
|
currentSlug?: string;
|
|
163
|
+
/**
|
|
164
|
+
* Explicit current-route override, checked before the
|
|
165
|
+
* `data-zd-current-path` dataset override and `window.location.pathname`
|
|
166
|
+
* when deriving the active slug on hydration and at every View Transition.
|
|
167
|
+
* See `deriveActiveSlug` (zudolab/zudo-doc#3398).
|
|
168
|
+
*/
|
|
169
|
+
currentPath?: string;
|
|
146
170
|
rootMenuItems?: SidebarRootMenuItem[];
|
|
147
171
|
backToMenuLabel?: string;
|
|
148
172
|
localeLinks?: SidebarLocaleLink[];
|
|
@@ -171,8 +195,8 @@ function SidebarFooter({ links, themeDefaultMode }: { links?: SidebarLocaleLink[
|
|
|
171
195
|
);
|
|
172
196
|
}
|
|
173
197
|
|
|
174
|
-
export function SidebarTree({ nodes, currentSlug, rootMenuItems, backToMenuLabel, localeLinks, themeDefaultMode }: SidebarTreeProps) {
|
|
175
|
-
const activeSlug = useActiveSlug(nodes, currentSlug);
|
|
198
|
+
export function SidebarTree({ nodes, currentSlug, currentPath, rootMenuItems, backToMenuLabel, localeLinks, themeDefaultMode }: SidebarTreeProps) {
|
|
199
|
+
const activeSlug = useActiveSlug(nodes, currentSlug, currentPath);
|
|
176
200
|
const [query, setQuery] = useState("");
|
|
177
201
|
const [showingRootMenu, setShowingRootMenu] = useState(false);
|
|
178
202
|
const filterRef = useRef<HTMLInputElement>(null);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@takazudo/zudo-doc",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "zudo-doc framework primitives layer that sits on top of zfb's engine — sidebar, theme, TOC, breadcrumb, layouts, head injection, View Transitions, SSR-skip wrappers (per ADR-003).",
|
|
6
6
|
"license": "MIT",
|
|
@@ -239,6 +239,10 @@
|
|
|
239
239
|
"types": "./dist/catalog.d.ts",
|
|
240
240
|
"default": "./dist/catalog.js"
|
|
241
241
|
},
|
|
242
|
+
"./site-schema": {
|
|
243
|
+
"types": "./dist/site-schema/index.d.ts",
|
|
244
|
+
"default": "./dist/site-schema/index.js"
|
|
245
|
+
},
|
|
242
246
|
"./theme.css": "./dist/theme.css",
|
|
243
247
|
"./safelist.css": "./dist/safelist.css",
|
|
244
248
|
"./content.css": "./dist/content.css",
|
|
@@ -553,6 +557,10 @@
|
|
|
553
557
|
"types": "./dist/sidebar-active-slug/index.d.ts",
|
|
554
558
|
"default": "./dist/sidebar-active-slug/index.js"
|
|
555
559
|
},
|
|
560
|
+
"./current-path": {
|
|
561
|
+
"types": "./dist/current-path/index.d.ts",
|
|
562
|
+
"default": "./dist/current-path/index.js"
|
|
563
|
+
},
|
|
556
564
|
"./i18n-defaults": {
|
|
557
565
|
"types": "./dist/i18n-defaults/index.d.ts",
|
|
558
566
|
"default": "./dist/i18n-defaults/index.js"
|
|
@@ -609,11 +617,11 @@
|
|
|
609
617
|
"CHANGELOG.md"
|
|
610
618
|
],
|
|
611
619
|
"peerDependencies": {
|
|
612
|
-
"@takazudo/zdtp": "^0.4.
|
|
613
|
-
"@takazudo/zfb": "^2.
|
|
614
|
-
"@takazudo/zfb-md-wasm": "^2.
|
|
615
|
-
"@takazudo/zfb-runtime": "^2.
|
|
616
|
-
"@takazudo/zudo-doc-history-server": "^5.5.
|
|
620
|
+
"@takazudo/zdtp": "^0.4.11",
|
|
621
|
+
"@takazudo/zfb": "^2.7.1",
|
|
622
|
+
"@takazudo/zfb-md-wasm": "^2.7.1",
|
|
623
|
+
"@takazudo/zfb-runtime": "^2.7.1",
|
|
624
|
+
"@takazudo/zudo-doc-history-server": "^5.5.3",
|
|
617
625
|
"diff": "^8.0.0",
|
|
618
626
|
"katex": "^0.16.0",
|
|
619
627
|
"preact": "^10.29.1",
|
|
@@ -647,14 +655,15 @@
|
|
|
647
655
|
"tsx": "^4.21.0"
|
|
648
656
|
},
|
|
649
657
|
"devDependencies": {
|
|
650
|
-
"@takazudo/zfb": "2.
|
|
651
|
-
"@takazudo/zfb-md-wasm": "2.
|
|
652
|
-
"@takazudo/zfb-runtime": "2.
|
|
658
|
+
"@takazudo/zfb": "2.7.1",
|
|
659
|
+
"@takazudo/zfb-md-wasm": "2.7.1",
|
|
660
|
+
"@takazudo/zfb-runtime": "2.7.1",
|
|
653
661
|
"@types/fs-extra": "^11.0.4",
|
|
654
662
|
"@types/minimist": "^1.2.5",
|
|
655
663
|
"@types/node": "^25.3.5",
|
|
656
664
|
"@types/pluralize": "^0.0.33",
|
|
657
665
|
"@types/string-similarity": "^4.0.2",
|
|
666
|
+
"esbuild": ">=0.28.1",
|
|
658
667
|
"happy-dom": "^20.10.6",
|
|
659
668
|
"npm-run-all2": "^7.0.2",
|
|
660
669
|
"preact": "^10.29.1",
|
|
@@ -663,15 +672,19 @@
|
|
|
663
672
|
"typescript": "^5.0.0",
|
|
664
673
|
"vitest": "^4.1.0",
|
|
665
674
|
"zod": "^4.3.6",
|
|
666
|
-
"@takazudo/zudo-doc-history-server": "5.
|
|
675
|
+
"@takazudo/zudo-doc-history-server": "5.7.0"
|
|
667
676
|
},
|
|
668
677
|
"scripts": {
|
|
669
|
-
"
|
|
670
|
-
"
|
|
678
|
+
"gen:search-widget-script": "node scripts/gen-search-widget-script.mjs",
|
|
679
|
+
"gen:nav-overflow-script": "node scripts/gen-nav-overflow-script.mjs",
|
|
680
|
+
"build": "node scripts/gen-search-widget-script.mjs && node scripts/gen-nav-overflow-script.mjs && tsup && tsc -p tsconfig.build.json",
|
|
681
|
+
"predev": "node ../../scripts/ensure-workspace-build.mjs && node scripts/gen-search-widget-script.mjs && node scripts/gen-nav-overflow-script.mjs",
|
|
671
682
|
"// dev": "Do NOT add `--continue-on-error` here. It stops a fatal watcher exit from cascading, but the dead watcher then fails silently — a dead dev:dts leaves dist/*.d.ts frozen while the JS keeps updating, which typechecks cleanly against stale types instead of failing loudly like #3113's absent declarations did. See the #3129 section in CLAUDE.md.",
|
|
672
683
|
"dev": "run-p dev:js dev:dts",
|
|
673
684
|
"dev:js": "tsup --watch",
|
|
674
685
|
"dev:dts": "tsc -p tsconfig.build.json --watch --preserveWatchOutput",
|
|
686
|
+
"// check:prepack-contract": "check:search-widget-drift and check:nav-overflow-drift run here (not just b4push/CI) because `prepare` regenerates BOTH frozen literals unconditionally — without a prepack gate, packing from a tree where a source changed but a literal was never re-committed would silently publish new script bytes and break every consumer's pinned CSP hash (#3534/#3535 for nav-overflow; #3540 extended the same gate to search-widget, the exact failure class the freezes exist to prevent).",
|
|
687
|
+
"check:prepack-contract": "pnpm --dir ../.. check:search-widget-drift && pnpm --dir ../.. check:nav-overflow-drift && pnpm --dir ../.. gen:changelog && node scripts/check-theme-css.mjs && node scripts/check-safelist.mjs && node scripts/check-content-css.mjs && node scripts/check-page-loading-css.mjs && node scripts/check-features-css.mjs && node scripts/check-theme-packs.mjs && node scripts/check-catalog.mjs && node scripts/check-site-schema.mjs && node scripts/check-plugins.mjs && node scripts/check-plugin-resolution.mjs && node scripts/check-eject-sources.mjs && node scripts/check-routes-src.mjs && node scripts/check-shim-artifacts.mjs && node scripts/check-virtual-modules.mjs && node bin/gen-component-tokens.mjs --check",
|
|
675
688
|
"test:plugin-resolution": "node scripts/check-plugin-resolution.mjs",
|
|
676
689
|
"test": "vitest run --config vitest.config.ts",
|
|
677
690
|
"test:slow": "vitest run --config vitest.slow.config.ts",
|
package/routes-src/_chrome.tsx
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import { routeCtx } from "./_context.js";
|
|
14
14
|
import { createChrome } from "@takazudo/zudo-doc/chrome";
|
|
15
15
|
import { DocHistory } from "@takazudo/zudo-doc/doc-history";
|
|
16
|
-
import type { DocNavNode } from "
|
|
16
|
+
import type { DocNavNode } from "@takazudo/zudo-doc/site-schema";
|
|
17
17
|
// Imported via the BARE published subpath rather than a relative
|
|
18
18
|
// `../chrome-bindings.js`. This file is copied into consumer projects by the
|
|
19
19
|
// routes-src mechanism (`scripts/copy-routes-src.mjs`), which rewrites relative
|
|
@@ -27,6 +27,10 @@ import { defineChromeBindings } from "@takazudo/zudo-doc/chrome-bindings";
|
|
|
27
27
|
// ("Host-callables channel — chromeBindingsModule"). Not present on disk; the
|
|
28
28
|
// package ships ambient typings for it (`routes/_virtual.d.ts`).
|
|
29
29
|
import { chromeBindings } from "virtual:zudo-doc-chrome-bindings";
|
|
30
|
+
// Routes-only configured design-token-panel island (#3396) — the ONLY importer
|
|
31
|
+
// of `virtual:zudo-doc-design-token-panel-config` in the package. Static, for
|
|
32
|
+
// the same island-scanner reason as `DocHistory` above.
|
|
33
|
+
import { ConfiguredDesignTokenPanelBootstrap } from "./_design-token-panel-bootstrap.js";
|
|
30
34
|
|
|
31
35
|
// Island-scanner contract (load-bearing): the injected doc routes reach the real
|
|
32
36
|
// DocHistory client island ONLY through this static import → `createChrome`
|
|
@@ -57,15 +61,23 @@ import { chromeBindings } from "virtual:zudo-doc-chrome-bindings";
|
|
|
57
61
|
// the way the static `DocHistory` import above is (the virtual re-export sits
|
|
58
62
|
// outside zfb's static-import scanner reachability graph) — see the ADR.
|
|
59
63
|
//
|
|
60
|
-
// `DesignTokenPanelBootstrap`
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
// `
|
|
64
|
+
// `DesignTokenPanelBootstrap` IS threaded here since #3396, but only to swap
|
|
65
|
+
// the BUILDER — the mount, the settings gate, and the slot default all still
|
|
66
|
+
// belong to `chrome/derive.tsx`'s `deriveBodyEndIslands` (gate-2 fix from the
|
|
67
|
+
// Wave-5 confirm #2659), so the locked-manifest self-contained doc stub (#2653)
|
|
68
|
+
// and every other bare `createChrome` caller keep getting the package-default
|
|
69
|
+
// island with no explicit wiring. On THESE injected routes the configured
|
|
70
|
+
// wrapper wins, which is what carries a host's
|
|
71
|
+
// `settings.designTokenPanelConfigModule` through. Scanner reachability holds
|
|
72
|
+
// through the static chain route → this shim → `_design-token-panel-bootstrap`.
|
|
73
|
+
//
|
|
74
|
+
// It is spread BEFORE `...chromeBindings` (unlike `DocHistory`, which is spread
|
|
75
|
+
// after): a host's own `DesignTokenPanelBootstrap` slot value must still win,
|
|
76
|
+
// exactly as it did when the derive-level default was the only package wiring.
|
|
68
77
|
const chrome = createChrome(routeCtx, {
|
|
78
|
+
...defineChromeBindings({
|
|
79
|
+
DesignTokenPanelBootstrap: ConfiguredDesignTokenPanelBootstrap,
|
|
80
|
+
}),
|
|
69
81
|
...chromeBindings,
|
|
70
82
|
...defineChromeBindings({ DocHistory }),
|
|
71
83
|
});
|