@takazudo/zudo-doc 4.4.13 → 5.0.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.
Files changed (86) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/dist/category-nav/index.d.ts +31 -0
  3. package/dist/category-nav/index.js +15 -3
  4. package/dist/category-tree-nav/index.d.ts +32 -0
  5. package/dist/category-tree-nav/index.js +16 -4
  6. package/dist/chrome/derive.d.ts +32 -3
  7. package/dist/chrome/derive.js +26 -7
  8. package/dist/code-syntax/mermaid-init-script.d.ts +6 -7
  9. package/dist/config.d.ts +22 -5
  10. package/dist/config.js +11 -0
  11. package/dist/content.css +1 -0
  12. package/dist/desktop-toc-toggle-island/index.d.ts +7 -0
  13. package/dist/desktop-toc-toggle-island/index.js +64 -0
  14. package/dist/doc-page-renderer/index.d.ts +12 -1
  15. package/dist/doc-page-renderer/index.js +10 -3
  16. package/dist/doc-page-shell/index.d.ts +13 -0
  17. package/dist/doc-page-shell/index.js +37 -3
  18. package/dist/doclayout/doc-layout.d.ts +14 -0
  19. package/dist/doclayout/doc-layout.js +4 -2
  20. package/dist/features.css +82 -10
  21. package/dist/head-with-defaults/index.js +1 -0
  22. package/dist/header/header.d.ts +1 -1
  23. package/dist/header/header.js +2 -2
  24. package/dist/header/types.d.ts +7 -0
  25. package/dist/header-with-defaults/index.d.ts +2 -0
  26. package/dist/header-with-defaults/index.js +9 -2
  27. package/dist/i18n-version/version-switcher.d.ts +36 -0
  28. package/dist/i18n-version/version-switcher.js +43 -3
  29. package/dist/inline-version-switcher/index.d.ts +18 -0
  30. package/dist/inline-version-switcher/index.js +3 -1
  31. package/dist/mdx-components/index.d.ts +11 -0
  32. package/dist/mdx-components/index.js +4 -4
  33. package/dist/nav-data-prep/index.d.ts +18 -3
  34. package/dist/nav-data-prep/index.js +3 -3
  35. package/dist/plugins/internal/claude-resources/generate.d.ts +11 -0
  36. package/dist/plugins/internal/claude-resources/generate.js +16 -13
  37. package/dist/plugins/internal/claude-resources/index.js +6 -1
  38. package/dist/preset.d.ts +16 -7
  39. package/dist/preset.js +8 -6
  40. package/dist/routes/_context.d.ts +1 -1
  41. package/dist/safelist.css +1 -1
  42. package/dist/settings.d.ts +17 -2
  43. package/dist/site-tree-nav/index.d.ts +21 -0
  44. package/dist/site-tree-nav/index.js +26 -7
  45. package/dist/theme-packs/academia/meta.json +1 -1
  46. package/dist/theme-packs/academia/pack.css +13 -3
  47. package/dist/theme-packs/blueprint/meta.json +1 -1
  48. package/dist/theme-packs/blueprint/pack.css +9 -0
  49. package/dist/theme-packs/broadsheet/meta.json +1 -1
  50. package/dist/theme-packs/broadsheet/pack.css +9 -0
  51. package/dist/theme-packs/drift/meta.json +1 -1
  52. package/dist/theme-packs/drift/pack.css +9 -0
  53. package/dist/theme-packs/fjord/meta.json +1 -1
  54. package/dist/theme-packs/fjord/pack.css +9 -0
  55. package/dist/theme-packs/hearth/meta.json +1 -1
  56. package/dist/theme-packs/hearth/pack.css +9 -0
  57. package/dist/theme-packs/hollow/meta.json +1 -1
  58. package/dist/theme-packs/hollow/pack.css +9 -0
  59. package/dist/theme-packs/matcha/meta.json +1 -1
  60. package/dist/theme-packs/matcha/pack.css +9 -0
  61. package/dist/theme-packs/nocturne/meta.json +1 -1
  62. package/dist/theme-packs/nocturne/pack.css +9 -0
  63. package/dist/theme-packs/observatory/meta.json +1 -1
  64. package/dist/theme-packs/observatory/pack.css +18 -0
  65. package/dist/theme-packs/onyx/meta.json +1 -1
  66. package/dist/theme-packs/onyx/pack.css +9 -0
  67. package/dist/theme-packs/sakura/meta.json +1 -1
  68. package/dist/theme-packs/sakura/pack.css +9 -0
  69. package/dist/theme-packs/sumi/meta.json +1 -1
  70. package/dist/theme-packs/sumi/pack.css +9 -0
  71. package/dist/theme-packs/timberline/meta.json +1 -1
  72. package/dist/theme-packs/timberline/pack.css +9 -0
  73. package/dist/theme-packs-registry/validator.js +38 -6
  74. package/dist/toc-prepaint/index.d.ts +60 -0
  75. package/dist/toc-prepaint/index.js +44 -0
  76. package/dist/url-helpers/index.d.ts +1 -1
  77. package/dist/url-helpers/index.js +2 -2
  78. package/dist/version-availability/index.d.ts +75 -0
  79. package/dist/version-availability/index.js +56 -0
  80. package/dist/versions-page/index.d.ts +9 -1
  81. package/dist/versions-page/index.js +6 -2
  82. package/eject/header/header.tsx +3 -2
  83. package/eject/header/types.ts +7 -0
  84. package/package.json +17 -10
  85. package/tsconfig.base.json +2 -1
  86. package/zfb-config-shim.d.ts +22 -204
@@ -4,7 +4,7 @@
4
4
  "name": "Broadsheet",
5
5
  "description": "Newspaper editorial — Playfair masthead, Oxford ink rules, a red drop cap, and a night-edition dark mode.",
6
6
  "mode": "light",
7
- "version": "1.0.1",
7
+ "version": "1.0.2",
8
8
  "fonts": {
9
9
  "sans": "Source Serif 4",
10
10
  "mono": "Courier Prime",
@@ -187,6 +187,15 @@ html[data-theme-pack="broadsheet"] body {
187
187
  background-attachment: fixed;
188
188
  }
189
189
 
190
+ /* Touch devices: background-attachment:fixed is unsupported (iOS Safari) or
191
+ repaint-heavy on scroll — fall back to scroll attachment. Catalog-wide
192
+ convention from issue #3070. */
193
+ @media (pointer: coarse) {
194
+ html[data-theme-pack="broadsheet"] body {
195
+ background-attachment: scroll;
196
+ }
197
+ }
198
+
190
199
  /* masthead: Playfair black wordmark over a thick-thin Oxford rule */
191
200
  html[data-theme-pack="broadsheet"] header[data-header] {
192
201
  border-bottom: 3px solid var(--zd-fg);
@@ -4,7 +4,7 @@
4
4
  "name": "Drift",
5
5
  "description": "Floaty slate-blue comfort dark for long reading — soft glows in place of borders, gentle elevation, relaxed Plex type.",
6
6
  "mode": "dark",
7
- "version": "1.0.1",
7
+ "version": "1.0.2",
8
8
  "fonts": {
9
9
  "sans": "IBM Plex Sans",
10
10
  "mono": "IBM Plex Mono",
@@ -210,6 +210,15 @@ html[data-theme-pack="drift"] body {
210
210
  );
211
211
  background-attachment: fixed;
212
212
  }
213
+
214
+ /* Touch devices: background-attachment:fixed is unsupported (iOS Safari) or
215
+ repaint-heavy on scroll — fall back to scroll attachment. Catalog-wide
216
+ convention from issue #3070. */
217
+ @media (pointer: coarse) {
218
+ html[data-theme-pack="drift"] body {
219
+ background-attachment: scroll;
220
+ }
221
+ }
213
222
  /* the base stylesheet sets `.zd-content { line-height: var(--leading-relaxed) }`
214
223
  (1.625), which beats the inherited body value — restate the relaxed
215
224
  1.75 rhythm on the prose container itself */
@@ -4,7 +4,7 @@
4
4
  "name": "Fjord",
5
5
  "description": "Polar-night blue under a faint aurora — frost-cyan accents, icy borders, snow-white text. Cold nordic calm.",
6
6
  "mode": "dark",
7
- "version": "1.0.1",
7
+ "version": "1.0.2",
8
8
  "fonts": {
9
9
  "sans": "Inter",
10
10
  "mono": "JetBrains Mono",
@@ -170,6 +170,15 @@ html[data-theme-pack="fjord"] body {
170
170
  background-attachment: fixed;
171
171
  }
172
172
 
173
+ /* Touch devices: background-attachment:fixed is unsupported (iOS Safari) or
174
+ repaint-heavy on scroll — fall back to scroll attachment. Catalog-wide
175
+ convention from issue #3070. */
176
+ @media (pointer: coarse) {
177
+ html[data-theme-pack="fjord"] body {
178
+ background-attachment: scroll;
179
+ }
180
+ }
181
+
173
182
  html[data-theme-pack="fjord"] {
174
183
  scrollbar-color: color-mix(in oklch, var(--zd-muted) 45%, transparent) transparent;
175
184
  }
@@ -4,7 +4,7 @@
4
4
  "name": "Hearth",
5
5
  "description": "Warm cream & brick-red fireside docs — Fraunces headings, amber code blocks, ember-glow dark mode.",
6
6
  "mode": "light",
7
- "version": "1.0.2",
7
+ "version": "1.0.3",
8
8
  "fonts": {
9
9
  "sans": "Nunito",
10
10
  "mono": "Spline Sans Mono",
@@ -172,6 +172,15 @@ html[data-theme-pack="hearth"] body {
172
172
  background-attachment: fixed;
173
173
  }
174
174
 
175
+ /* Touch devices: background-attachment:fixed is unsupported (iOS Safari) or
176
+ repaint-heavy on scroll — fall back to scroll attachment. Catalog-wide
177
+ convention from issue #3070. */
178
+ @media (pointer: coarse) {
179
+ html[data-theme-pack="hearth"] body {
180
+ background-attachment: scroll;
181
+ }
182
+ }
183
+
175
184
  /* header: warm mantelpiece — soft gradient, warm hairline, brown
176
185
  shadow */
177
186
  html[data-theme-pack="hearth"] header[data-header] {
@@ -4,7 +4,7 @@
4
4
  "name": "Hollow",
5
5
  "description": "Dark violet space — neon pink headings, violet links, a quiet starfield, and razor-square midnight chrome.",
6
6
  "mode": "dark",
7
- "version": "1.0.1",
7
+ "version": "1.0.2",
8
8
  "fonts": {
9
9
  "sans": "Space Grotesk",
10
10
  "mono": "Space Mono",
@@ -209,6 +209,15 @@ html[data-theme-pack="hollow"] body {
209
209
  background-attachment: fixed;
210
210
  }
211
211
 
212
+ /* Touch devices: background-attachment:fixed is unsupported (iOS Safari) or
213
+ repaint-heavy on scroll — fall back to scroll attachment. Catalog-wide
214
+ convention from issue #3070. */
215
+ @media (pointer: coarse) {
216
+ html[data-theme-pack="hollow"] body {
217
+ background-attachment: scroll;
218
+ }
219
+ }
220
+
212
221
  /* sidebar floats transparently over the starfield */
213
222
  html[data-theme-pack="hollow"] #desktop-sidebar {
214
223
  background-color: transparent;
@@ -4,7 +4,7 @@
4
4
  "name": "Matcha",
5
5
  "description": "Green tea ceremony — deep matcha on warm cream, mincho headings, zen whitespace, tea-foam surfaces in both modes.",
6
6
  "mode": "light",
7
- "version": "1.0.1",
7
+ "version": "1.0.2",
8
8
  "fonts": {
9
9
  "sans": "Zen Kaku Gothic New",
10
10
  "mono": "M PLUS 1 Code",
@@ -183,6 +183,15 @@ html[data-theme-pack="matcha"] body {
183
183
  background-attachment: fixed;
184
184
  }
185
185
 
186
+ /* Touch devices: background-attachment:fixed is unsupported (iOS Safari) or
187
+ repaint-heavy on scroll — fall back to scroll attachment. Catalog-wide
188
+ convention from issue #3070. */
189
+ @media (pointer: coarse) {
190
+ html[data-theme-pack="matcha"] body {
191
+ background-attachment: scroll;
192
+ }
193
+ }
194
+
186
195
  /* zen prose: let the reading column breathe. The package flow-space
187
196
  rhythm (vsp-2xl h2 / vsp-xl h3) already sits within 0.1rem of the
188
197
  prototype's spacing, so only the line-height lever is ported. */
@@ -4,7 +4,7 @@
4
4
  "name": "Nocturne",
5
5
  "description": "Purple midnight — velvet aubergine depths, lavender links, and muted gold hairlines. Elegant, never neon.",
6
6
  "mode": "dark",
7
- "version": "1.0.1",
7
+ "version": "1.0.2",
8
8
  "fonts": {
9
9
  "sans": "Manrope",
10
10
  "mono": "IBM Plex Mono",
@@ -216,6 +216,15 @@ html[data-theme-pack="nocturne"] body {
216
216
  background-attachment: fixed;
217
217
  }
218
218
 
219
+ /* Touch devices: background-attachment:fixed is unsupported (iOS Safari) or
220
+ repaint-heavy on scroll — fall back to scroll attachment. Catalog-wide
221
+ convention from issue #3070. */
222
+ @media (pointer: coarse) {
223
+ html[data-theme-pack="nocturne"] body {
224
+ background-attachment: scroll;
225
+ }
226
+ }
227
+
219
228
  /* header: velvet band under a gold hairline */
220
229
  html[data-theme-pack="nocturne"] header[data-header] {
221
230
  border-bottom-color: light-dark(oklch(0.53 0.105 85 / 0.45), oklch(0.78 0.105 87 / 0.45));
@@ -4,7 +4,7 @@
4
4
  "name": "Observatory",
5
5
  "description": "Night-sky atlas: star-field depth, constellation-line rules, nebula violet and comet gold over astronomer's navy.",
6
6
  "mode": "dark",
7
- "version": "1.0.2",
7
+ "version": "1.0.3",
8
8
  "fonts": {
9
9
  "sans": "Jost",
10
10
  "mono": "Space Mono",
@@ -258,6 +258,15 @@ html[data-theme-pack="observatory"] body {
258
258
  letter-spacing: 0.005em;
259
259
  }
260
260
 
261
+ /* Touch devices: background-attachment:fixed is unsupported (iOS Safari) or
262
+ repaint-heavy on scroll — fall back to scroll attachment. Catalog-wide
263
+ convention from issue #3070. */
264
+ @media (pointer: coarse) {
265
+ html[data-theme-pack="observatory"] body {
266
+ background-attachment: scroll;
267
+ }
268
+ }
269
+
261
270
  /* header — observatory signage over a gold→violet ecliptic rule */
262
271
  html[data-theme-pack="observatory"] header[data-header] {
263
272
  border-image: linear-gradient(
@@ -319,6 +328,15 @@ html[data-theme-pack="observatory"] #desktop-sidebar {
319
328
  320px 320px;
320
329
  background-attachment: fixed;
321
330
  }
331
+
332
+ /* Touch devices: background-attachment:fixed is unsupported (iOS Safari) or
333
+ repaint-heavy on scroll — fall back to scroll attachment. Catalog-wide
334
+ convention from issue #3070. */
335
+ @media (pointer: coarse) {
336
+ html[data-theme-pack="observatory"] #desktop-sidebar {
337
+ background-attachment: scroll;
338
+ }
339
+ }
322
340
  html[data-theme-pack="observatory"] #desktop-sidebar a {
323
341
  border-radius: 0;
324
342
  }
@@ -4,7 +4,7 @@
4
4
  "name": "Onyx",
5
5
  "description": "Luxury noir — jet black, champagne serif headings, and a single gold hairline accent. Watch-brand restraint.",
6
6
  "mode": "dark",
7
- "version": "1.0.2",
7
+ "version": "1.0.3",
8
8
  "fonts": {
9
9
  "sans": "Jost",
10
10
  "mono": "IBM Plex Mono",
@@ -215,6 +215,15 @@ html[data-theme-pack="onyx"] body {
215
215
  letter-spacing: 0.005em;
216
216
  }
217
217
 
218
+ /* Touch devices: background-attachment:fixed is unsupported (iOS Safari) or
219
+ repaint-heavy on scroll — fall back to scroll attachment. Catalog-wide
220
+ convention from issue #3070. */
221
+ @media (pointer: coarse) {
222
+ html[data-theme-pack="onyx"] body {
223
+ background-attachment: scroll;
224
+ }
225
+ }
226
+
218
227
  /* header: engraved nameplate over a gold hairline */
219
228
  html[data-theme-pack="onyx"] header[data-header] {
220
229
  border-bottom: 1px solid color-mix(in oklch, var(--zd-accent) 38%, transparent);
@@ -4,7 +4,7 @@
4
4
  "name": "Sakura",
5
5
  "description": "Cherry-blossom pastels — blush-white paper, plum ink, and rose accents on petal-soft corners; dark mode is yozakura, night blossoms under lantern-pink light.",
6
6
  "mode": "light",
7
- "version": "1.0.0",
7
+ "version": "1.0.1",
8
8
  "fonts": {
9
9
  "sans": "Nunito",
10
10
  "mono": "M PLUS 1 Code",
@@ -194,6 +194,15 @@ html[data-theme-pack="sakura"] body {
194
194
  background-attachment: fixed;
195
195
  }
196
196
 
197
+ /* Touch devices: background-attachment:fixed is unsupported (iOS Safari) or
198
+ repaint-heavy on scroll — fall back to scroll attachment. Catalog-wide
199
+ convention from issue #3070. */
200
+ @media (pointer: coarse) {
201
+ html[data-theme-pack="sakura"] body {
202
+ background-attachment: scroll;
203
+ }
204
+ }
205
+
197
206
  /* header — a blush veil fading toward the right, petal hairline */
198
207
  html[data-theme-pack="sakura"] header[data-header] {
199
208
  background-image: linear-gradient(
@@ -4,7 +4,7 @@
4
4
  "name": "Sumi",
5
5
  "description": "Sumi-e ink on washi — bold mincho brush headings, deep black strokes, one vermillion hanko seal accent.",
6
6
  "mode": "light",
7
- "version": "1.0.2",
7
+ "version": "1.0.3",
8
8
  "fonts": {
9
9
  "sans": "Zen Kaku Gothic New",
10
10
  "mono": "IBM Plex Mono",
@@ -239,6 +239,15 @@ html[data-theme-pack="sumi"] body {
239
239
  background-attachment: fixed;
240
240
  }
241
241
 
242
+ /* Touch devices: background-attachment:fixed is unsupported (iOS Safari) or
243
+ repaint-heavy on scroll — fall back to scroll attachment. Catalog-wide
244
+ convention from issue #3070. */
245
+ @media (pointer: coarse) {
246
+ html[data-theme-pack="sumi"] body {
247
+ background-attachment: scroll;
248
+ }
249
+ }
250
+
242
251
  /* header: one decisive ink rule; mincho logo signed with a seal */
243
252
  html[data-theme-pack="sumi"] header[data-header] {
244
253
  border-bottom: 3px solid var(--zd-fg);
@@ -4,7 +4,7 @@
4
4
  "name": "Timberline",
5
5
  "description": "Forest cabin after dark — pine & umber planks, cream prose, and matte lantern-amber accents; light mode is the same lodge at morning.",
6
6
  "mode": "dark",
7
- "version": "1.0.0",
7
+ "version": "1.0.1",
8
8
  "fonts": {
9
9
  "sans": "Source Sans 3",
10
10
  "mono": "JetBrains Mono",
@@ -191,6 +191,15 @@ html[data-theme-pack="timberline"] body {
191
191
  background-attachment: fixed;
192
192
  }
193
193
 
194
+ /* Touch devices: background-attachment:fixed is unsupported (iOS Safari) or
195
+ repaint-heavy on scroll — fall back to scroll attachment. Catalog-wide
196
+ convention from issue #3070. */
197
+ @media (pointer: coarse) {
198
+ html[data-theme-pack="timberline"] body {
199
+ background-attachment: scroll;
200
+ }
201
+ }
202
+
194
203
  /* header: the lintel beam — umber board over a plank seam (dark
195
204
  groove hairline + chamfer highlight catching on the board below) */
196
205
  html[data-theme-pack="timberline"] header[data-header] {
@@ -11,15 +11,28 @@ import {
11
11
  function stripComments(css) {
12
12
  return css.replace(/\/\*[\s\S]*?\*\//g, "");
13
13
  }
14
- function parseTopLevelRules(css) {
14
+ function parseTopLevelRules(css, onBareStatement) {
15
15
  const text = stripComments(css);
16
16
  const rules = [];
17
17
  const n = text.length;
18
18
  let i = 0;
19
+ const reportBareStatements = (segment) => {
20
+ if (!onBareStatement) return;
21
+ const re = /@[a-zA-Z-]+[^{;]*;/g;
22
+ let m;
23
+ while ((m = re.exec(segment)) !== null) {
24
+ onBareStatement(m[0].trim());
25
+ }
26
+ };
19
27
  while (i < n) {
20
28
  const braceIdx = text.indexOf("{", i);
21
- if (braceIdx === -1) break;
22
- const preludeRaw = text.slice(i, braceIdx).replace(/@[a-zA-Z-]+[^{;]*;/g, "");
29
+ if (braceIdx === -1) {
30
+ reportBareStatements(text.slice(i));
31
+ break;
32
+ }
33
+ const preludeSegment = text.slice(i, braceIdx);
34
+ reportBareStatements(preludeSegment);
35
+ const preludeRaw = preludeSegment.replace(/@[a-zA-Z-]+[^{;]*;/g, "");
23
36
  const prelude = preludeRaw.trim();
24
37
  let depth = 1;
25
38
  let j = braceIdx + 1;
@@ -38,6 +51,9 @@ function parseTopLevelRules(css) {
38
51
  function isFontFaceRule(rule) {
39
52
  return /^@font-face\b/i.test(rule.prelude);
40
53
  }
54
+ function isMediaRule(rule) {
55
+ return /^@media\b/i.test(rule.prelude);
56
+ }
41
57
  function extractFontFaceFamilies(cssContent) {
42
58
  const families = [];
43
59
  for (const rule of parseTopLevelRules(cssContent)) {
@@ -86,9 +102,8 @@ function checkCssPresence(dirName, cssContent, issues) {
86
102
  }
87
103
  function checkScoping(slug, cssContent, issues) {
88
104
  const prefix = `html[data-theme-pack="${slug}"]`;
89
- for (const rule of parseTopLevelRules(cssContent)) {
90
- if (isFontFaceRule(rule)) continue;
91
- for (const rawBranch of rule.prelude.split(",")) {
105
+ const checkPrelude = (prelude) => {
106
+ for (const rawBranch of prelude.split(",")) {
92
107
  const branch = rawBranch.trim();
93
108
  if (branch.length === 0) continue;
94
109
  if (!branch.startsWith(prefix)) {
@@ -99,6 +114,23 @@ function checkScoping(slug, cssContent, issues) {
99
114
  });
100
115
  }
101
116
  }
117
+ };
118
+ for (const rule of parseTopLevelRules(cssContent)) {
119
+ if (isFontFaceRule(rule)) continue;
120
+ if (isMediaRule(rule)) {
121
+ const innerRules = parseTopLevelRules(rule.body, (statement) => {
122
+ issues.push({
123
+ rule: "scoping",
124
+ severity: "error",
125
+ message: `at-rule statement "${statement}" inside @media is not a pack-scoped rule and is not allowed`
126
+ });
127
+ });
128
+ for (const innerRule of innerRules) {
129
+ checkPrelude(innerRule.prelude);
130
+ }
131
+ continue;
132
+ }
133
+ checkPrelude(rule.prelude);
102
134
  }
103
135
  }
104
136
  function checkKnownTokenNames(cssContent, issues) {
@@ -0,0 +1,60 @@
1
+ /** @jsxRuntime automatic */
2
+ /** @jsxImportSource preact */
3
+ import type { JSX } from "preact";
4
+ export interface TocPrepaintProps {
5
+ /**
6
+ * Whether the page actually renders the package's own default TOC (i.e.
7
+ * `shouldRenderToc && !customTocIsPresent`, computed by the shell). When
8
+ * false there is no default TOC for the toggle to collapse — the shell
9
+ * either has no TOC at all or renders a custom `hostBindings.Toc` override.
10
+ */
11
+ shouldRenderDefaultToc?: boolean;
12
+ }
13
+ /** Settings subset read by the toc-prepaint factories. */
14
+ export interface TocPrepaintSettings {
15
+ tocToggle?: boolean;
16
+ }
17
+ /**
18
+ * Shared gate for BOTH toc-prepaint factories: the head pre-paint `<script>`
19
+ * and the `afterSidebar` toggle Island must appear on exactly the same pages,
20
+ * so they read the same predicate here rather than each inlining
21
+ * `settings.tocToggle && shouldRenderDefaultToc` (a one-sided edit could
22
+ * otherwise let the script and the button desync — the #2571 pattern).
23
+ *
24
+ * The predicate cannot know whether a custom Toc exists on its own — the
25
+ * shell computes `shouldRenderDefaultToc` and passes it in.
26
+ */
27
+ export declare function tocPrepaintActive(settings: TocPrepaintSettings, shouldRenderDefaultToc?: boolean): boolean;
28
+ /**
29
+ * Pre-paint inline script body: restore persisted TOC visibility to
30
+ * `<html data-toc-hidden>` before first paint to avoid a hard-reload flash.
31
+ *
32
+ * A tiny synchronous IIFE that reads `localStorage[TOC_STORAGE_KEY]` and
33
+ * sets `data-toc-hidden` only when the stored value is exactly "false"
34
+ * (the collapsed preference) — the default (visible) needs no attribute and
35
+ * causes no layout shift. Silently no-ops in privacy / disabled-storage modes.
36
+ *
37
+ * The storage key is interpolated from `TOC_STORAGE_KEY` so it stays in
38
+ * sync with the island's reader; the `data-toc-hidden` attribute name is a
39
+ * literal here (the island's `setDataAttribute` hardcodes the same literal —
40
+ * keep the two in sync). Intended for `<head>` placement so it executes before
41
+ * the `.zd-toc-col` TOC column is painted.
42
+ */
43
+ export declare const TOC_VISIBILITY_PREPAINT_SCRIPT: string;
44
+ /**
45
+ * Create a `TocVisibilityPrepaint` component bound to the host's settings.
46
+ *
47
+ * Returns the pre-paint `<script>` (for the page `<head>`) when
48
+ * `settings.tocToggle` is enabled AND the page actually renders the default
49
+ * TOC; returns `undefined` otherwise — the SAME gating as
50
+ * {@link createTocPrepaint}, so the head script and the afterSidebar toggle
51
+ * button always appear together.
52
+ */
53
+ export declare function createTocVisibilityPrepaint(settings: TocPrepaintSettings): (props: TocPrepaintProps) => JSX.Element | undefined;
54
+ /**
55
+ * Create a `TocPrepaint` component bound to the host's settings.
56
+ *
57
+ * The host stub calls this once with `{ tocToggle: settings.tocToggle }`
58
+ * and re-exports the result.
59
+ */
60
+ export declare function createTocPrepaint(settings: TocPrepaintSettings): (props: TocPrepaintProps) => JSX.Element | undefined;
@@ -0,0 +1,44 @@
1
+ import { Fragment, jsx } from "preact/jsx-runtime";
2
+ import { Island } from "@takazudo/zfb";
3
+ import {
4
+ DesktopTocToggle,
5
+ TOC_STORAGE_KEY
6
+ } from "../desktop-toc-toggle-island/index.js";
7
+ function tocPrepaintActive(settings, shouldRenderDefaultToc) {
8
+ return Boolean(settings.tocToggle) && Boolean(shouldRenderDefaultToc);
9
+ }
10
+ const TOC_VISIBILITY_PREPAINT_SCRIPT = `(function(){try{if(localStorage.getItem(${JSON.stringify(
11
+ TOC_STORAGE_KEY
12
+ )})==='false'){document.documentElement.setAttribute('data-toc-hidden','');}}catch(e){}})();`;
13
+ function createTocVisibilityPrepaint(settings) {
14
+ function TocVisibilityPrepaint({
15
+ shouldRenderDefaultToc
16
+ }) {
17
+ if (!tocPrepaintActive(settings, shouldRenderDefaultToc)) return void 0;
18
+ return /* @__PURE__ */ jsx(
19
+ "script",
20
+ {
21
+ dangerouslySetInnerHTML: { __html: TOC_VISIBILITY_PREPAINT_SCRIPT }
22
+ }
23
+ );
24
+ }
25
+ return TocVisibilityPrepaint;
26
+ }
27
+ function createTocPrepaint(settings) {
28
+ function TocPrepaint({
29
+ shouldRenderDefaultToc
30
+ }) {
31
+ if (!tocPrepaintActive(settings, shouldRenderDefaultToc)) return void 0;
32
+ return /* @__PURE__ */ jsx(Fragment, { children: Island({
33
+ when: "load",
34
+ children: /* @__PURE__ */ jsx(DesktopTocToggle, {})
35
+ }) });
36
+ }
37
+ return TocPrepaint;
38
+ }
39
+ export {
40
+ TOC_VISIBILITY_PREPAINT_SCRIPT,
41
+ createTocPrepaint,
42
+ createTocVisibilityPrepaint,
43
+ tocPrepaintActive
44
+ };
@@ -20,7 +20,7 @@ export interface UrlHelpers {
20
20
  docsUrl(slug: string, lang?: string): string;
21
21
  isExternal(href: string): boolean;
22
22
  resolveHref(href: string): string;
23
- navHref(path: string, lang: string | undefined, currentVersion: string | undefined): string;
23
+ navHref(path: string, lang: string | undefined, currentVersion: string | undefined, versioned?: boolean): string;
24
24
  getPathForLocale(path: string, currentLang: string, targetLang: string): string;
25
25
  buildLocaleLinks(currentPath: string, currentLang: string): LocaleLink[];
26
26
  isDefaultLocaleOnlyPath(path: string): boolean;
@@ -35,9 +35,9 @@ function makeUrlHelpers(settings, i18n) {
35
35
  function resolveHref(href) {
36
36
  return isExternal(href) ? href : withBase(href);
37
37
  }
38
- function navHref(path, lang, currentVersion) {
38
+ function navHref(path, lang, currentVersion, versioned = true) {
39
39
  const isNonDefaultLocale = lang != null && lang !== defaultLocale && !isDefaultLocaleOnlyPath(path);
40
- const versionPrefix = currentVersion ? `/v/${currentVersion}` : "";
40
+ const versionPrefix = versioned && currentVersion ? `/v/${currentVersion}` : "";
41
41
  return withBase(
42
42
  isNonDefaultLocale ? `${versionPrefix}/${lang}${path}` : `${versionPrefix}${path}`
43
43
  );
@@ -0,0 +1,75 @@
1
+ import type { DocPageEntry, DocNavNode } from "../doc-page-props/index.js";
2
+ import type { CategoryMeta } from "../sidebar-tree/index.js";
3
+ /** Structural subset of `NavSourceDocs` this module reads. */
4
+ export interface VersionAvailabilityNavSource {
5
+ docs: DocPageEntry[];
6
+ navDocs: DocPageEntry[];
7
+ categoryMeta: Map<string, CategoryMeta>;
8
+ }
9
+ /** Injected dependencies for {@link createGetUnavailableVersions}. */
10
+ export interface VersionAvailabilityDeps {
11
+ /** Configured versions to check availability for (`false` / empty ⇒ no-op). */
12
+ versions: Array<{
13
+ slug: string;
14
+ }> | false;
15
+ /**
16
+ * Identity-stable nav-source resolver (`resolveNavSource` from
17
+ * `nav-source-docs`) — already memoized per (locale, version, options), so
18
+ * a cache miss in {@link createGetUnavailableVersions} is cheap.
19
+ */
20
+ resolveNavSource: (locale: string, versionSlug: string, options?: {
21
+ applyDefaultLocaleOnlyFilter?: boolean;
22
+ keepUnlisted?: boolean;
23
+ }) => VersionAvailabilityNavSource;
24
+ /** Canonical route slug for a zfb content slug — injected, not ambient. */
25
+ toRouteSlug: (entrySlug: string) => string;
26
+ /**
27
+ * Nav-tree builder, already bound to a href builder by the caller (hrefs
28
+ * are irrelevant here — the tree is only walked to discover auto-index
29
+ * nodes via {@link VersionAvailabilityDeps.collectAutoIndexNodes}).
30
+ */
31
+ buildNavTree: (docs: DocPageEntry[], locale: string, categoryMeta: Map<string, CategoryMeta>) => DocNavNode[];
32
+ /** Category nodes with children but no page of their own — real routes
33
+ * with no corresponding docs entry (see module header). */
34
+ collectAutoIndexNodes: (tree: DocNavNode[]) => DocNavNode[];
35
+ }
36
+ /**
37
+ * Build `getUnavailableVersions(slug, locale)`, bound to the injected deps.
38
+ *
39
+ * Returns `undefined` when there is no current slug to test (e.g. a
40
+ * no-current-slug header render on the home page) or when versioning is not
41
+ * configured — NEVER mark every archive unavailable just because there is no
42
+ * page to test against.
43
+ *
44
+ * Each version's available-slug Set is cached here keyed by BOTH locale and
45
+ * version (`${locale}\n${versionSlug}`) — a version-slug-only key would be
46
+ * wrong, because a versioned locale merge differs per language.
47
+ */
48
+ export declare function createGetUnavailableVersions(deps: VersionAvailabilityDeps): (slug: string | undefined, locale: string) => ReadonlySet<string> | undefined;
49
+ /**
50
+ * `data-*` attribute name the client payload rides on — see the module
51
+ * header's "Client payload contract" section for the full spelling/format
52
+ * contract. Exported so a future consumer (#3244) reads the same literal
53
+ * instead of hand-copying the string.
54
+ */
55
+ export declare const UNAVAILABLE_VERSIONS_ATTR = "data-doc-unavailable-versions";
56
+ /**
57
+ * Serialize a `getUnavailableVersions()` result into the `data-*` attribute
58
+ * bag `<DocPageShell>` spreads onto `<article>`. Returns `{}` (no key at
59
+ * all) for `undefined` input so the attribute is genuinely ABSENT from the
60
+ * rendered element, preserving the three-state contract documented above —
61
+ * an empty object here must not be confused with `{ [ATTR]: "" }`.
62
+ */
63
+ export declare function serializeUnavailableVersions(unavailableVersions: ReadonlySet<string> | undefined): Record<string, string>;
64
+ /**
65
+ * Enforce the comma-free version-slug constraint the module header's
66
+ * "Client payload contract" section documents. Shared by BOTH `zudoDoc()`
67
+ * (`../config.ts`) and `zudoDocPreset()` (`../preset.ts`) — the preset is
68
+ * itself part of the frozen exported API and documented as directly
69
+ * spreadable into `defineConfig`, so a consumer calling it straight (bypassing
70
+ * `zudoDoc()`) must hit the same guard (#3244 codex review finding 2 follow-up).
71
+ * Throws a `TypeError` naming the offending slug; no-op for `false`/`undefined`.
72
+ */
73
+ export declare function assertNoCommaInVersionSlugs(versions: ReadonlyArray<{
74
+ slug: string;
75
+ }> | false | undefined): void;