@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.
- package/CHANGELOG.md +65 -0
- package/dist/category-nav/index.d.ts +31 -0
- package/dist/category-nav/index.js +15 -3
- package/dist/category-tree-nav/index.d.ts +32 -0
- package/dist/category-tree-nav/index.js +16 -4
- package/dist/chrome/derive.d.ts +32 -3
- package/dist/chrome/derive.js +26 -7
- package/dist/code-syntax/mermaid-init-script.d.ts +6 -7
- package/dist/config.d.ts +22 -5
- package/dist/config.js +11 -0
- package/dist/content.css +1 -0
- package/dist/desktop-toc-toggle-island/index.d.ts +7 -0
- package/dist/desktop-toc-toggle-island/index.js +64 -0
- package/dist/doc-page-renderer/index.d.ts +12 -1
- package/dist/doc-page-renderer/index.js +10 -3
- package/dist/doc-page-shell/index.d.ts +13 -0
- package/dist/doc-page-shell/index.js +37 -3
- package/dist/doclayout/doc-layout.d.ts +14 -0
- package/dist/doclayout/doc-layout.js +4 -2
- package/dist/features.css +82 -10
- package/dist/head-with-defaults/index.js +1 -0
- package/dist/header/header.d.ts +1 -1
- package/dist/header/header.js +2 -2
- package/dist/header/types.d.ts +7 -0
- package/dist/header-with-defaults/index.d.ts +2 -0
- package/dist/header-with-defaults/index.js +9 -2
- package/dist/i18n-version/version-switcher.d.ts +36 -0
- package/dist/i18n-version/version-switcher.js +43 -3
- package/dist/inline-version-switcher/index.d.ts +18 -0
- package/dist/inline-version-switcher/index.js +3 -1
- package/dist/mdx-components/index.d.ts +11 -0
- package/dist/mdx-components/index.js +4 -4
- package/dist/nav-data-prep/index.d.ts +18 -3
- package/dist/nav-data-prep/index.js +3 -3
- package/dist/plugins/internal/claude-resources/generate.d.ts +11 -0
- package/dist/plugins/internal/claude-resources/generate.js +16 -13
- package/dist/plugins/internal/claude-resources/index.js +6 -1
- package/dist/preset.d.ts +16 -7
- package/dist/preset.js +8 -6
- package/dist/routes/_context.d.ts +1 -1
- package/dist/safelist.css +1 -1
- package/dist/settings.d.ts +17 -2
- package/dist/site-tree-nav/index.d.ts +21 -0
- package/dist/site-tree-nav/index.js +26 -7
- package/dist/theme-packs/academia/meta.json +1 -1
- package/dist/theme-packs/academia/pack.css +13 -3
- package/dist/theme-packs/blueprint/meta.json +1 -1
- package/dist/theme-packs/blueprint/pack.css +9 -0
- package/dist/theme-packs/broadsheet/meta.json +1 -1
- package/dist/theme-packs/broadsheet/pack.css +9 -0
- package/dist/theme-packs/drift/meta.json +1 -1
- package/dist/theme-packs/drift/pack.css +9 -0
- package/dist/theme-packs/fjord/meta.json +1 -1
- package/dist/theme-packs/fjord/pack.css +9 -0
- package/dist/theme-packs/hearth/meta.json +1 -1
- package/dist/theme-packs/hearth/pack.css +9 -0
- package/dist/theme-packs/hollow/meta.json +1 -1
- package/dist/theme-packs/hollow/pack.css +9 -0
- package/dist/theme-packs/matcha/meta.json +1 -1
- package/dist/theme-packs/matcha/pack.css +9 -0
- package/dist/theme-packs/nocturne/meta.json +1 -1
- package/dist/theme-packs/nocturne/pack.css +9 -0
- package/dist/theme-packs/observatory/meta.json +1 -1
- package/dist/theme-packs/observatory/pack.css +18 -0
- package/dist/theme-packs/onyx/meta.json +1 -1
- package/dist/theme-packs/onyx/pack.css +9 -0
- package/dist/theme-packs/sakura/meta.json +1 -1
- package/dist/theme-packs/sakura/pack.css +9 -0
- package/dist/theme-packs/sumi/meta.json +1 -1
- package/dist/theme-packs/sumi/pack.css +9 -0
- package/dist/theme-packs/timberline/meta.json +1 -1
- package/dist/theme-packs/timberline/pack.css +9 -0
- package/dist/theme-packs-registry/validator.js +38 -6
- package/dist/toc-prepaint/index.d.ts +60 -0
- package/dist/toc-prepaint/index.js +44 -0
- package/dist/url-helpers/index.d.ts +1 -1
- package/dist/url-helpers/index.js +2 -2
- package/dist/version-availability/index.d.ts +75 -0
- package/dist/version-availability/index.js +56 -0
- package/dist/versions-page/index.d.ts +9 -1
- package/dist/versions-page/index.js +6 -2
- package/eject/header/header.tsx +3 -2
- package/eject/header/types.ts +7 -0
- package/package.json +17 -10
- package/tsconfig.base.json +2 -1
- package/zfb-config-shim.d.ts +22 -204
|
@@ -3,7 +3,11 @@ import { createDocPageShell } from "../doc-page-shell/index.js";
|
|
|
3
3
|
import { createDocContentHeader } from "../doc-content-header/index.js";
|
|
4
4
|
import { createDocMetainfoArea } from "../doc-metainfo-area/index.js";
|
|
5
5
|
import { createDocHistoryArea } from "../doc-history-area/index.js";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
deriveMdxComponents,
|
|
8
|
+
deriveInlineVersionSwitcher,
|
|
9
|
+
deriveGetUnavailableVersions
|
|
10
|
+
} from "../chrome/derive.js";
|
|
7
11
|
import { assertChromeContext } from "../chrome/assert-chrome-context.js";
|
|
8
12
|
function createRenderDocPage(ctx) {
|
|
9
13
|
assertChromeContext(ctx, "createRenderDocPage");
|
|
@@ -14,8 +18,10 @@ function createRenderDocPage(ctx) {
|
|
|
14
18
|
const toRouteSlug = ctx.toRouteSlug;
|
|
15
19
|
const createMdxComponents = deriveMdxComponents(ctx).createMdxComponentsBound;
|
|
16
20
|
const t = ctx.t;
|
|
21
|
+
const getUnavailableVersions = deriveGetUnavailableVersions(ctx);
|
|
17
22
|
const buildInlineVersionSwitcher = deriveInlineVersionSwitcher(
|
|
18
|
-
ctx
|
|
23
|
+
ctx,
|
|
24
|
+
getUnavailableVersions
|
|
19
25
|
);
|
|
20
26
|
const DocPageShell = createDocPageShell(ctx);
|
|
21
27
|
const DocContentHeader = createDocContentHeader(
|
|
@@ -29,7 +35,7 @@ function createRenderDocPage(ctx) {
|
|
|
29
35
|
const slug = props.kind === "autoIndex" ? props.autoIndex.slug : props.entry.data.slug ?? toRouteSlug(props.entry.slug);
|
|
30
36
|
const title = props.kind === "autoIndex" ? props.autoIndex.label : props.entry.data.title;
|
|
31
37
|
const description = props.kind === "autoIndex" ? props.autoIndex.description : props.entry.data.description;
|
|
32
|
-
const components = createMdxComponents(locale);
|
|
38
|
+
const components = createMdxComponents(locale, version?.slug);
|
|
33
39
|
const autoIndexChildren = props.kind === "autoIndex" ? version ? props.autoIndex.children.filter((c) => c.hasPage || c.children.length > 0) : props.autoIndex.children.filter((c) => c.hasPage || c.children.length > 0).map((c) => ({
|
|
34
40
|
...c,
|
|
35
41
|
href: c.href ?? docsUrl(c.slug, locale)
|
|
@@ -71,6 +77,7 @@ function createRenderDocPage(ctx) {
|
|
|
71
77
|
currentPath,
|
|
72
78
|
currentVersion: version?.slug,
|
|
73
79
|
versionSwitcher: buildInlineVersionSwitcher(slug, locale, version?.slug),
|
|
80
|
+
unavailableVersions: getUnavailableVersions(slug, locale),
|
|
74
81
|
versionBanner: versionBannerType,
|
|
75
82
|
versionBannerLatestUrl,
|
|
76
83
|
versionBannerLabels,
|
|
@@ -60,6 +60,19 @@ export interface DocPageShellProps {
|
|
|
60
60
|
currentVersion?: string;
|
|
61
61
|
/** Inline version switcher VNode for the breadcrumb right-slot. */
|
|
62
62
|
versionSwitcher: ComponentChildren;
|
|
63
|
+
/**
|
|
64
|
+
* This page's unavailable-version slugs, straight from
|
|
65
|
+
* `getUnavailableVersions(slug, locale)` (`../version-availability`) —
|
|
66
|
+
* NOT re-derived here. Serialized onto `<article>` via
|
|
67
|
+
* `serializeUnavailableVersions` so a same-locale SPA navigation rewire
|
|
68
|
+
* script (#3244) can read the per-page availability set after the
|
|
69
|
+
* persisted header's own SSR attributes go stale. `undefined` (no
|
|
70
|
+
* availability data — e.g. versioning not configured) renders NO
|
|
71
|
+
* attribute at all; an empty set renders the attribute with an empty
|
|
72
|
+
* value. See `../version-availability/index.ts` for the full
|
|
73
|
+
* absent/empty/populated contract.
|
|
74
|
+
*/
|
|
75
|
+
unavailableVersions?: ReadonlySet<string>;
|
|
63
76
|
/** Version banner type ("unmaintained" | "unreleased") or undefined on latest. */
|
|
64
77
|
versionBanner?: "unmaintained" | "unreleased";
|
|
65
78
|
/** URL of the latest equivalent page for the version banner link. */
|
|
@@ -7,6 +7,11 @@ import {
|
|
|
7
7
|
createSidebarPrepaint,
|
|
8
8
|
createSidebarVisibilityPrepaint
|
|
9
9
|
} from "../sidebar-prepaint/index.js";
|
|
10
|
+
import {
|
|
11
|
+
createTocPrepaint,
|
|
12
|
+
createTocVisibilityPrepaint
|
|
13
|
+
} from "../toc-prepaint/index.js";
|
|
14
|
+
import { serializeUnavailableVersions } from "../version-availability/index.js";
|
|
10
15
|
import { createHeadWithDefaults } from "../head-with-defaults/index.js";
|
|
11
16
|
import { resolveThemePackSsrSlug } from "../theme/theme-pack-provider.js";
|
|
12
17
|
import { createDocBodyEnd } from "../doc-body-end/index.js";
|
|
@@ -36,6 +41,13 @@ function createDocPageShell(ctx) {
|
|
|
36
41
|
const SidebarVisibilityPrepaint = createSidebarVisibilityPrepaint({
|
|
37
42
|
sidebarToggle: sidebarToggleEnabled
|
|
38
43
|
});
|
|
44
|
+
const tocToggleEnabled = Boolean(
|
|
45
|
+
ctx.settings.tocToggle
|
|
46
|
+
);
|
|
47
|
+
const TocPrepaint = createTocPrepaint({ tocToggle: tocToggleEnabled });
|
|
48
|
+
const TocVisibilityPrepaint = createTocVisibilityPrepaint({
|
|
49
|
+
tocToggle: tocToggleEnabled
|
|
50
|
+
});
|
|
39
51
|
const DocBodyEnd = createDocBodyEnd(ctx);
|
|
40
52
|
const dataThemePack = resolveThemePackSsrSlug(
|
|
41
53
|
ctx.themePackRegistry,
|
|
@@ -60,6 +72,7 @@ function createDocPageShell(ctx) {
|
|
|
60
72
|
currentPath,
|
|
61
73
|
currentVersion,
|
|
62
74
|
versionSwitcher,
|
|
75
|
+
unavailableVersions,
|
|
63
76
|
versionBanner,
|
|
64
77
|
versionBannerLatestUrl,
|
|
65
78
|
versionBannerLabels,
|
|
@@ -72,6 +85,7 @@ function createDocPageShell(ctx) {
|
|
|
72
85
|
} = props;
|
|
73
86
|
const tocTitle = getTocTitle(locale);
|
|
74
87
|
const shouldRenderToc = !hideToc && headings.length > 0;
|
|
88
|
+
const shouldRenderDefaultToc = shouldRenderToc && !customTocIsPresent;
|
|
75
89
|
const tocOverride = shouldRenderToc ? customTocIsPresent ? /* @__PURE__ */ jsx(Toc, { headings, title: tocTitle }) : (
|
|
76
90
|
// The zfb <Island> wrapper renders a bare <div> with no class, so
|
|
77
91
|
// below xl (where <Toc> itself is `hidden xl:flex`) it would remain
|
|
@@ -80,7 +94,22 @@ function createDocPageShell(ctx) {
|
|
|
80
94
|
// left at mobile widths. Hide the flex child itself below xl so it
|
|
81
95
|
// contributes no gap; the bare-nav override branch above is already
|
|
82
96
|
// self-hiding via <Toc>'s own `hidden xl:flex`. (#3082)
|
|
83
|
-
|
|
97
|
+
//
|
|
98
|
+
// Load-bearing for the TOC's sticky scroll-follow: this wrapper must
|
|
99
|
+
// be `xl:flex`, never `xl:block`. safelist-ok: `xl:block` names the rejected alternative in prose; only `xl:flex` below is emitted
|
|
100
|
+
// A sticky box can only travel within its parent's box, and the
|
|
101
|
+
// classless <Island> div sits between this wrapper and <Toc>'s
|
|
102
|
+
// sticky <nav>. As a block container that div is auto-height, so it
|
|
103
|
+
// collapses to exactly the nav's height and the nav has zero travel
|
|
104
|
+
// — it scrolls away with the page instead of pinning. Flex stretches
|
|
105
|
+
// the Island div to full content-band height (default
|
|
106
|
+
// `align-items: stretch`), restoring the travel range. (#3202)
|
|
107
|
+
//
|
|
108
|
+
// `zd-toc-col` is the stable hook class for the desktop TOC-toggle
|
|
109
|
+
// CSS (features.css "Desktop TOC toggle" block, #3254) — only the
|
|
110
|
+
// default-TOC wrapper carries it; the custom-Toc branch above stays
|
|
111
|
+
// bare and unaffected by the toggle feature.
|
|
112
|
+
/* @__PURE__ */ jsx("div", { class: "zd-toc-col hidden xl:flex", children: Island({
|
|
84
113
|
when: "load",
|
|
85
114
|
children: /* @__PURE__ */ jsx(Toc, { headings, title: tocTitle })
|
|
86
115
|
}) })
|
|
@@ -96,7 +125,8 @@ function createDocPageShell(ctx) {
|
|
|
96
125
|
description: settings.metaTags.description ? description : void 0,
|
|
97
126
|
head: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
98
127
|
/* @__PURE__ */ jsx(HeadWithDefaults, { title, description, canonical }),
|
|
99
|
-
/* @__PURE__ */ jsx(SidebarVisibilityPrepaint, { hideSidebar })
|
|
128
|
+
/* @__PURE__ */ jsx(SidebarVisibilityPrepaint, { hideSidebar }),
|
|
129
|
+
/* @__PURE__ */ jsx(TocVisibilityPrepaint, { shouldRenderDefaultToc })
|
|
100
130
|
] }),
|
|
101
131
|
lang: locale,
|
|
102
132
|
dataThemePack,
|
|
@@ -133,10 +163,14 @@ function createDocPageShell(ctx) {
|
|
|
133
163
|
),
|
|
134
164
|
tocOverride,
|
|
135
165
|
mobileTocOverride,
|
|
136
|
-
afterSidebar: /* @__PURE__ */
|
|
166
|
+
afterSidebar: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
167
|
+
/* @__PURE__ */ jsx(SidebarPrepaint, { hideSidebar }),
|
|
168
|
+
/* @__PURE__ */ jsx(TocPrepaint, { shouldRenderDefaultToc })
|
|
169
|
+
] }),
|
|
137
170
|
footerOverride: /* @__PURE__ */ jsx(FooterWithDefaults, { lang: locale }),
|
|
138
171
|
bodyEndComponents: /* @__PURE__ */ jsx(DocBodyEnd, {}),
|
|
139
172
|
enableClientRouter: settings.dynamicPageTransition,
|
|
173
|
+
articleAttrs: serializeUnavailableVersions(unavailableVersions),
|
|
140
174
|
children: kind === "autoIndex" ? (
|
|
141
175
|
/* Auto-index page: category without an index.mdx.
|
|
142
176
|
Fragment (not <div>) so children become direct children of
|
|
@@ -89,6 +89,20 @@ export interface DocLayoutProps extends DocLayoutHtmlAttrs {
|
|
|
89
89
|
* doc-history feature.
|
|
90
90
|
*/
|
|
91
91
|
afterContent?: ComponentChildren;
|
|
92
|
+
/**
|
|
93
|
+
* Raw `data-*` attributes spread onto the `<article>` element. This shell
|
|
94
|
+
* stays version/i18n-agnostic on purpose (see the module header), so it
|
|
95
|
+
* has no idea what any given key means — it is a generic passthrough, not
|
|
96
|
+
* a versioning-aware prop. `<DocPageShell>` (one level up) is the actual
|
|
97
|
+
* owner of the one entry currently threaded through here: the per-page
|
|
98
|
+
* version-availability payload documented in
|
|
99
|
+
* `../version-availability/index.ts` (`UNAVAILABLE_VERSIONS_ATTR`). Placed
|
|
100
|
+
* on `<article>` rather than a new wrapper element because `<article>` is
|
|
101
|
+
* always present, sits inside `<main>` (swapped content — see the SPA
|
|
102
|
+
* persist note on the sidebar `<aside>` above), and needs no DOM node of
|
|
103
|
+
* its own that could shift the `.zd-content` flow-space rhythm.
|
|
104
|
+
*/
|
|
105
|
+
articleAttrs?: Record<string, string>;
|
|
92
106
|
/** Optional desktop TOC rendered alongside `<main>` on wide screens. */
|
|
93
107
|
toc?: ComponentChildren;
|
|
94
108
|
/** Hide the TOC (both desktop and mobile) regardless of slot value. */
|
|
@@ -21,6 +21,7 @@ function DocLayout(props) {
|
|
|
21
21
|
mobileToc,
|
|
22
22
|
main,
|
|
23
23
|
afterContent,
|
|
24
|
+
articleAttrs,
|
|
24
25
|
toc,
|
|
25
26
|
hideToc = false,
|
|
26
27
|
contentWide = false,
|
|
@@ -54,7 +55,8 @@ function DocLayout(props) {
|
|
|
54
55
|
"data-sidebar-hidden",
|
|
55
56
|
"data-theme",
|
|
56
57
|
"data-theme-pack",
|
|
57
|
-
"style"
|
|
58
|
+
"style",
|
|
59
|
+
"data-toc-hidden"
|
|
58
60
|
]
|
|
59
61
|
}) : null,
|
|
60
62
|
head
|
|
@@ -88,7 +90,7 @@ function DocLayout(props) {
|
|
|
88
90
|
breadcrumb,
|
|
89
91
|
afterBreadcrumb,
|
|
90
92
|
!hideToc && mobileToc,
|
|
91
|
-
/* @__PURE__ */ jsx("article", { class: "zd-content max-w-none", children: main }),
|
|
93
|
+
/* @__PURE__ */ jsx("article", { class: "zd-content max-w-none", ...articleAttrs, children: main }),
|
|
92
94
|
afterContent
|
|
93
95
|
] }),
|
|
94
96
|
showToc && toc
|
package/dist/features.css
CHANGED
|
@@ -372,6 +372,66 @@ html[data-sidebar-hidden] .zd-doc-content-band[data-zd-wide] {
|
|
|
372
372
|
* before paint, so the sidebar never wipes-and-restores and no geometry
|
|
373
373
|
* transition fires mid-swap. Retires the #2198 workaround (zudolab/zudo-doc#2200). */
|
|
374
374
|
|
|
375
|
+
/* ========================================
|
|
376
|
+
* Desktop TOC toggle — 1:1 mirror of "Desktop sidebar toggle" above (epic
|
|
377
|
+
* #3252, #3254). Breakpoint is xl/1280px, NOT the sidebar's lg/1024px.
|
|
378
|
+
*
|
|
379
|
+
* `data-toc-hidden` persists across SPA navigation (doc-layout.tsx
|
|
380
|
+
* preserveHtmlAttrs), so EVERY hidden-state rule below is scoped to the
|
|
381
|
+
* default-TOC path (`.zd-toc-col` descendants / `:has(> .zd-toc-col)`) — an
|
|
382
|
+
* unscoped rule would collapse/squish a *custom* Toc reached after hiding the
|
|
383
|
+
* default TOC on another page.
|
|
384
|
+
* ======================================== */
|
|
385
|
+
|
|
386
|
+
@media (min-width: 1280px) {
|
|
387
|
+
.zd-toc-col {
|
|
388
|
+
width: var(--zdc-toc-width, 280px); /* explicit — auto→0 does not interpolate */
|
|
389
|
+
overflow: clip; /* NOT hidden: hidden creates a scroll container and breaks the nav's sticky */
|
|
390
|
+
transition: width var(--zd-transition-slow) ease-in-out;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
html[data-toc-hidden] .zd-toc-col {
|
|
394
|
+
width: 0;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/* :has guard keeps the gap intact on custom-Toc pages (wrapper is a direct
|
|
398
|
+
* band child). Without :has support the gap simply stays — cosmetic
|
|
399
|
+
* degradation only. Alternative if :has ever becomes a concern: a band
|
|
400
|
+
* marker attribute. */
|
|
401
|
+
html[data-toc-hidden] .zd-doc-content-band:has(> .zd-toc-col) {
|
|
402
|
+
gap: 0;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/* COMBINED list — a bare `transition: gap` here would override and drop the
|
|
406
|
+
* max-width transition declared in the sidebar's 1024px block (last wins at
|
|
407
|
+
* xl, same property on the same selector). No band max-width rule is added
|
|
408
|
+
* here (expand-main policy): `main` is already `flex-1 min-w-0` and absorbs
|
|
409
|
+
* the freed width by itself; both-hidden composes with the existing
|
|
410
|
+
* `html[data-sidebar-hidden] .zd-doc-content-band { max-width: 80rem }` cap. */
|
|
411
|
+
.zd-doc-content-band {
|
|
412
|
+
transition:
|
|
413
|
+
max-width var(--zd-transition-slow) ease-in-out,
|
|
414
|
+
gap var(--zd-transition-slow) ease-in-out;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.zd-toc-col nav[data-zd-toc] {
|
|
418
|
+
transition:
|
|
419
|
+
opacity var(--zd-transition-slow) ease-in-out,
|
|
420
|
+
transform var(--zd-transition-slow) ease-in-out,
|
|
421
|
+
visibility var(--zd-transition-slow);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
html[data-toc-hidden] .zd-toc-col nav[data-zd-toc] {
|
|
425
|
+
opacity: 0;
|
|
426
|
+
transform: translateX(1.5rem);
|
|
427
|
+
visibility: hidden; /* a11y: clipped links must not be focusable */
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
.zd-desktop-toc-toggle {
|
|
431
|
+
right: 0; /* constant — pins to the viewport edge; no tracking var needed */
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
375
435
|
/* ========================================
|
|
376
436
|
* Page transition animations (view-transition chrome)
|
|
377
437
|
* ======================================== */
|
|
@@ -403,19 +463,21 @@ html[data-sidebar-hidden] .zd-doc-content-band[data-zd-wide] {
|
|
|
403
463
|
* transitions via document.startViewTransition on each navigation.
|
|
404
464
|
*
|
|
405
465
|
* Chrome extraction via view-transition-name (Strategy B+, zudolab/zudo-doc#1558):
|
|
406
|
-
* <header>, <aside id="desktop-sidebar">, <footer>,
|
|
407
|
-
* button
|
|
408
|
-
*
|
|
466
|
+
* <header>, <aside id="desktop-sidebar">, <footer>, the desktop-sidebar-toggle
|
|
467
|
+
* button, and (since #3254) the desktop-toc-toggle button each carry a
|
|
468
|
+
* data-zfb-transition-persist attribute whose value is keyed by locale and
|
|
469
|
+
* nav-section. The five attribute selectors below assign a stable
|
|
409
470
|
* view-transition-name based on the attribute value prefix, extracting those
|
|
410
471
|
* elements from the root snapshot into their own named layers:
|
|
411
472
|
* - header-{lang} → zfb-header
|
|
412
473
|
* - sidebar-{locale}-… → zfb-sidebar
|
|
413
474
|
* - footer-{lang} → zfb-footer
|
|
414
475
|
* - desktop-sidebar-toggle → zfb-sidebar-toggle
|
|
476
|
+
* - desktop-toc-toggle → zfb-toc-toggle
|
|
415
477
|
*
|
|
416
478
|
* Once extracted, the root cross-fade animates only non-chrome content (main,
|
|
417
|
-
* article, TOC, etc.). The
|
|
418
|
-
*
|
|
479
|
+
* article, TOC, etc.). The ::view-transition-{old,new,group}(<name>) rules
|
|
480
|
+
* disable animation for all five chrome layers. The group pseudo must be
|
|
419
481
|
* neutralised too — even when old/new are static the group container can still
|
|
420
482
|
* produce a geometry-morph animation when snapshot size/position differs.
|
|
421
483
|
*
|
|
@@ -434,8 +496,11 @@ html[data-sidebar-hidden] .zd-doc-content-band[data-zd-wide] {
|
|
|
434
496
|
[data-zfb-transition-persist^="sidebar-"] { view-transition-name: zfb-sidebar; }
|
|
435
497
|
[data-zfb-transition-persist^="footer-"] { view-transition-name: zfb-footer; }
|
|
436
498
|
[data-zfb-transition-persist="desktop-sidebar-toggle"] { view-transition-name: zfb-sidebar-toggle; }
|
|
499
|
+
[data-zfb-transition-persist="desktop-toc-toggle"] { view-transition-name: zfb-toc-toggle; }
|
|
437
500
|
|
|
438
|
-
/* Disable animation for all four chrome layers (old, new, and group)
|
|
501
|
+
/* Disable animation for all four chrome layers (old, new, and group), plus
|
|
502
|
+
* the desktop-toc-toggle button (#3254 — mirrors desktop-sidebar-toggle so
|
|
503
|
+
* it doesn't animate on SPA swaps). */
|
|
439
504
|
::view-transition-old(zfb-header),
|
|
440
505
|
::view-transition-new(zfb-header),
|
|
441
506
|
::view-transition-group(zfb-header),
|
|
@@ -447,21 +512,26 @@ html[data-sidebar-hidden] .zd-doc-content-band[data-zd-wide] {
|
|
|
447
512
|
::view-transition-group(zfb-footer),
|
|
448
513
|
::view-transition-old(zfb-sidebar-toggle),
|
|
449
514
|
::view-transition-new(zfb-sidebar-toggle),
|
|
450
|
-
::view-transition-group(zfb-sidebar-toggle)
|
|
515
|
+
::view-transition-group(zfb-sidebar-toggle),
|
|
516
|
+
::view-transition-old(zfb-toc-toggle),
|
|
517
|
+
::view-transition-new(zfb-toc-toggle),
|
|
518
|
+
::view-transition-group(zfb-toc-toggle) { animation: none; }
|
|
451
519
|
|
|
452
520
|
/* Entry/exit: chrome element present on one side only (#2072).
|
|
453
521
|
* Exit — lone old snapshot fades out with the content cross-fade. */
|
|
454
522
|
::view-transition-old(zfb-header):only-child,
|
|
455
523
|
::view-transition-old(zfb-sidebar):only-child,
|
|
456
524
|
::view-transition-old(zfb-footer):only-child,
|
|
457
|
-
::view-transition-old(zfb-sidebar-toggle):only-child
|
|
525
|
+
::view-transition-old(zfb-sidebar-toggle):only-child,
|
|
526
|
+
::view-transition-old(zfb-toc-toggle):only-child {
|
|
458
527
|
animation: var(--default-transition-duration) ease-in both contentFadeOut;
|
|
459
528
|
}
|
|
460
529
|
/* Entry — lone new snapshot fades in with the content cross-fade. */
|
|
461
530
|
::view-transition-new(zfb-header):only-child,
|
|
462
531
|
::view-transition-new(zfb-sidebar):only-child,
|
|
463
532
|
::view-transition-new(zfb-footer):only-child,
|
|
464
|
-
::view-transition-new(zfb-sidebar-toggle):only-child
|
|
533
|
+
::view-transition-new(zfb-sidebar-toggle):only-child,
|
|
534
|
+
::view-transition-new(zfb-toc-toggle):only-child {
|
|
465
535
|
animation: var(--zd-transition-slower) ease-out both contentFadeIn;
|
|
466
536
|
}
|
|
467
537
|
|
|
@@ -492,10 +562,12 @@ html[data-sidebar-hidden] .zd-doc-content-band[data-zd-wide] {
|
|
|
492
562
|
::view-transition-old(zfb-sidebar):only-child,
|
|
493
563
|
::view-transition-old(zfb-footer):only-child,
|
|
494
564
|
::view-transition-old(zfb-sidebar-toggle):only-child,
|
|
565
|
+
::view-transition-old(zfb-toc-toggle):only-child,
|
|
495
566
|
::view-transition-new(zfb-header):only-child,
|
|
496
567
|
::view-transition-new(zfb-sidebar):only-child,
|
|
497
568
|
::view-transition-new(zfb-footer):only-child,
|
|
498
|
-
::view-transition-new(zfb-sidebar-toggle):only-child
|
|
569
|
+
::view-transition-new(zfb-sidebar-toggle):only-child,
|
|
570
|
+
::view-transition-new(zfb-toc-toggle):only-child {
|
|
499
571
|
animation: none;
|
|
500
572
|
}
|
|
501
573
|
}
|
|
@@ -65,6 +65,7 @@ function createHeadWithDefaults(ctx) {
|
|
|
65
65
|
}
|
|
66
66
|
),
|
|
67
67
|
settings.sidebarResizer && /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: SIDEBAR_RESIZER_RESTORE_SCRIPT } }),
|
|
68
|
+
/* @__PURE__ */ jsx("link", { rel: "icon", type: "image/svg+xml", href: withBase("/favicon.svg") }),
|
|
68
69
|
/* @__PURE__ */ jsx("link", { rel: "icon", href: withBase("/favicon.ico"), sizes: "any" }),
|
|
69
70
|
/* @__PURE__ */ jsx("link", { rel: "icon", type: "image/png", sizes: "32x32", href: withBase("/favicon-32x32.png") }),
|
|
70
71
|
/* @__PURE__ */ jsx("link", { rel: "icon", type: "image/png", sizes: "16x16", href: withBase("/favicon-16x16.png") }),
|
package/dist/header/header.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ import type { HeaderNavItem, HeaderRightComponentRegistry, HeaderRightItem, Loca
|
|
|
10
10
|
export interface HeaderUrlHelpers {
|
|
11
11
|
withBase(path: string): string;
|
|
12
12
|
stripBase(path: string): string;
|
|
13
|
-
navHref(path: string, lang?: Locale, currentVersion?: string): string;
|
|
13
|
+
navHref(path: string, lang?: Locale, currentVersion?: string, versioned?: boolean): string;
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
16
|
* Boundary helpers for locale-aware behaviour the host injects into
|
package/dist/header/header.js
CHANGED
|
@@ -166,7 +166,7 @@ function SidebarSlotFallback({
|
|
|
166
166
|
}
|
|
167
167
|
function renderNavItem(item, activeNavPath, activeCategory, lang, currentVersion, urlHelpers, i18n) {
|
|
168
168
|
const isActive = isNavItemActiveByCategory(item, activeCategory) || isNavItemActive(item, activeNavPath);
|
|
169
|
-
const href = urlHelpers.navHref(item.path, lang, currentVersion);
|
|
169
|
+
const href = urlHelpers.navHref(item.path, lang, currentVersion, item.versioned);
|
|
170
170
|
const label = item.labelKey ? i18n.t(item.labelKey, lang) : item.label;
|
|
171
171
|
if (item.children && item.children.length > 0) {
|
|
172
172
|
return /* @__PURE__ */ jsxs(
|
|
@@ -215,7 +215,7 @@ function renderNavItem(item, activeNavPath, activeCategory, lang, currentVersion
|
|
|
215
215
|
}
|
|
216
216
|
),
|
|
217
217
|
/* @__PURE__ */ jsx("div", { class: "absolute left-0 top-full z-dropdown hidden group-hover:block group-focus-within:block pt-vsp-3xs", children: /* @__PURE__ */ jsx("div", { class: "min-w-[10rem] border border-muted rounded bg-surface shadow-lg py-vsp-3xs", children: item.children.map((child) => {
|
|
218
|
-
const childHref = urlHelpers.navHref(child.path, lang, currentVersion);
|
|
218
|
+
const childHref = urlHelpers.navHref(child.path, lang, currentVersion, child.versioned);
|
|
219
219
|
const childLabel = child.labelKey ? i18n.t(child.labelKey, lang) : child.label;
|
|
220
220
|
const childActive = isNavItemActiveByCategory(child, activeCategory) || activeNavPath === child.path;
|
|
221
221
|
return /* @__PURE__ */ jsx(
|
package/dist/header/types.d.ts
CHANGED
|
@@ -11,6 +11,13 @@ export interface HeaderNavChildItem {
|
|
|
11
11
|
labelKey?: string;
|
|
12
12
|
path: string;
|
|
13
13
|
categoryMatch?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Whether links built from this item's `path` carry the active `/v/{version}`
|
|
16
|
+
* prefix. `false` targets a route that has no versioned counterpart, so its
|
|
17
|
+
* href must stay unversioned even when rendered under an active version.
|
|
18
|
+
* Default `true`.
|
|
19
|
+
*/
|
|
20
|
+
versioned?: boolean;
|
|
14
21
|
}
|
|
15
22
|
export interface HeaderNavItem extends HeaderNavChildItem {
|
|
16
23
|
children?: HeaderNavChildItem[];
|
|
@@ -32,10 +32,12 @@ export interface HeaderWithDefaultsSettings {
|
|
|
32
32
|
labelKey?: string;
|
|
33
33
|
path: string;
|
|
34
34
|
categoryMatch?: string;
|
|
35
|
+
versioned?: boolean;
|
|
35
36
|
children?: Array<{
|
|
36
37
|
label: string;
|
|
37
38
|
labelKey?: string;
|
|
38
39
|
path: string;
|
|
40
|
+
versioned?: boolean;
|
|
39
41
|
}>;
|
|
40
42
|
}>;
|
|
41
43
|
headerRightItems?: unknown[];
|
|
@@ -9,7 +9,11 @@ import { ThemeToggle } from "../theme-toggle/index.js";
|
|
|
9
9
|
import { SidebarToggle } from "../sidebar-toggle-island/index.js";
|
|
10
10
|
import { buildGitHubRepoUrl as buildGitHubRepoUrlBase } from "../github-helpers/index.js";
|
|
11
11
|
import { assertChromeContext } from "../chrome/assert-chrome-context.js";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
deriveGetUnavailableVersions,
|
|
14
|
+
deriveNavDataPrep,
|
|
15
|
+
deriveSearchWidgetSlot
|
|
16
|
+
} from "../chrome/derive.js";
|
|
13
17
|
function createHeaderWithDefaults(ctx) {
|
|
14
18
|
assertChromeContext(ctx, "createHeaderWithDefaults");
|
|
15
19
|
const settings = ctx.settings;
|
|
@@ -24,6 +28,7 @@ function createHeaderWithDefaults(ctx) {
|
|
|
24
28
|
const { buildRootMenuItems, buildLocaleLinksForNav, buildSidebarNodes, getThemeDefaultMode } = deriveNavDataPrep(ctx);
|
|
25
29
|
const buildGitHubRepoUrl = () => buildGitHubRepoUrlBase(ctx.settings.githubUrl);
|
|
26
30
|
const SearchWidget = deriveSearchWidgetSlot(ctx);
|
|
31
|
+
const getUnavailableVersions = deriveGetUnavailableVersions(ctx);
|
|
27
32
|
function HeaderWithDefaults(props) {
|
|
28
33
|
const {
|
|
29
34
|
lang: langProp = defaultLocale,
|
|
@@ -85,6 +90,7 @@ function createHeaderWithDefaults(ctx) {
|
|
|
85
90
|
unavailable: t("version.switcher.unavailable", lang),
|
|
86
91
|
allVersions: t("version.switcher.allVersions", lang)
|
|
87
92
|
};
|
|
93
|
+
const unavailableVersions = getUnavailableVersions(currentSlug, lang);
|
|
88
94
|
versionSwitcher = /* @__PURE__ */ jsx(
|
|
89
95
|
VersionSwitcher,
|
|
90
96
|
{
|
|
@@ -96,6 +102,7 @@ function createHeaderWithDefaults(ctx) {
|
|
|
96
102
|
latestUrl,
|
|
97
103
|
versionsPageUrl,
|
|
98
104
|
versionUrls,
|
|
105
|
+
unavailableVersions,
|
|
99
106
|
labels,
|
|
100
107
|
idSuffix: "header",
|
|
101
108
|
rewireConfig: {
|
|
@@ -158,7 +165,7 @@ function createHeaderWithDefaults(ctx) {
|
|
|
158
165
|
urlHelpers: {
|
|
159
166
|
withBase,
|
|
160
167
|
stripBase,
|
|
161
|
-
navHref: (path, l, v) => navHref(path, l, v)
|
|
168
|
+
navHref: (path, l, v, versioned) => navHref(path, l, v, versioned)
|
|
162
169
|
},
|
|
163
170
|
i18n: {
|
|
164
171
|
defaultLocale,
|
|
@@ -169,6 +169,42 @@ export declare const VERSION_SWITCHER_INIT_SCRIPT: string;
|
|
|
169
169
|
* {@link VersionSwitcherRewireConfig} is passed), so the inline breadcrumb
|
|
170
170
|
* switcher — re-rendered fresh on every swap — is left alone.
|
|
171
171
|
*
|
|
172
|
+
* Since #3244, `rewire()` also recomputes each version entry's
|
|
173
|
+
* enabled/disabled state from the per-page availability payload #3243 emits
|
|
174
|
+
* onto the swapped `<article>` (`UNAVAILABLE_VERSIONS_ATTR`, imported here
|
|
175
|
+
* as `ATTR` — see `version-availability/index.ts` for the three-state
|
|
176
|
+
* contract). The persisted header would otherwise keep showing whichever
|
|
177
|
+
* entries were disabled/enabled on the PREVIOUS page after a same-locale SPA
|
|
178
|
+
* navigation — the exact bug epic #3242 exists to fix.
|
|
179
|
+
*
|
|
180
|
+
* The three-state contract is preserved faithfully here (fixed after a P2
|
|
181
|
+
* codex review finding on the original #3244 landing, which collapsed ABSENT
|
|
182
|
+
* into EMPTY and re-enabled every entry — turning SSR-correct disabled links
|
|
183
|
+
* into live 404s whenever a page renders through `createDocPageShell` without
|
|
184
|
+
* an availability payload):
|
|
185
|
+
* - attribute ABSENT (`hasAvailabilityData` false) → no availability data
|
|
186
|
+
* for the destination page. `setDisabled` is never called for any entry;
|
|
187
|
+
* the SSR-rendered disabled/enabled state is left exactly as-is. Only the
|
|
188
|
+
* genuinely path-derived bits (href, active state on entries that are
|
|
189
|
+
* NOT currently disabled) are recomputed.
|
|
190
|
+
* - attribute present, value `""` → empty unavailable set, i.e. "everything
|
|
191
|
+
* available" — `setDisabled(a, false, …)` runs for every entry, matching
|
|
192
|
+
* the SSR component's own `!unavailableVersions || !unavailableVersions.has(slug)`
|
|
193
|
+
* fallback (`version-switcher.tsx`'s `isAvailable` check).
|
|
194
|
+
* - attribute present, `"a,b"` → those slugs disabled, the rest enabled.
|
|
195
|
+
*
|
|
196
|
+
* `setDisabled` and the `setActive` guard together transition ALL FIVE
|
|
197
|
+
* SSR-divergent properties in both directions (`aria-disabled`, `tabindex`,
|
|
198
|
+
* `title`, the disjoint class sets, `aria-current`) — see the case table in
|
|
199
|
+
* `__tests__/version-switcher.test.tsx` that pins this against the real SSR
|
|
200
|
+
* branches. `setActive` runs strictly AFTER `setDisabled` re-enables an
|
|
201
|
+
* entry, so a newly-available active entry gets `aria-current="page"`
|
|
202
|
+
* restored instead of silently staying without it. In the ABSENT branch,
|
|
203
|
+
* where `setDisabled` never runs, `setActive` instead reads the anchor's
|
|
204
|
+
* OWN current `aria-disabled` attribute directly (the only source of truth
|
|
205
|
+
* left, since availability isn't being recomputed) and skips already-disabled
|
|
206
|
+
* entries the same way.
|
|
207
|
+
*
|
|
172
208
|
* `window[FLAG]` makes it idempotent: the tag may re-execute on a hard reload or
|
|
173
209
|
* a cross-locale header repaint, but the listener registers exactly once per
|
|
174
210
|
* page lifetime.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx, jsxs } from "preact/jsx-runtime";
|
|
2
2
|
import { AFTER_NAVIGATE_EVENT } from "../transitions/page-events.js";
|
|
3
|
+
import { UNAVAILABLE_VERSIONS_ATTR } from "../version-availability/index.js";
|
|
3
4
|
function computeVersionSwitcherState(pathname, config, versionSlugs) {
|
|
4
5
|
const normalizedBase = config.base;
|
|
5
6
|
const defaultLocale = config.defaultLocale;
|
|
@@ -105,7 +106,8 @@ function VersionSwitcher(props) {
|
|
|
105
106
|
"data-base": rewireConfig.base,
|
|
106
107
|
"data-default-locale": rewireConfig.defaultLocale,
|
|
107
108
|
"data-trailing-slash": String(rewireConfig.trailingSlash),
|
|
108
|
-
"data-current-locale": rewireConfig.currentLocale
|
|
109
|
+
"data-current-locale": rewireConfig.currentLocale,
|
|
110
|
+
"data-unavailable-label": labels.unavailable
|
|
109
111
|
} : {};
|
|
110
112
|
return /* @__PURE__ */ jsxs("div", { class: "version-switcher relative", "data-version-switcher": true, ...rewireAttrs, children: [
|
|
111
113
|
/* @__PURE__ */ jsxs(
|
|
@@ -232,6 +234,7 @@ const VERSION_SWITCHER_REWIRE_SCRIPT = `(function(){
|
|
|
232
234
|
var FLAG="__zdVersionSwitcherRewire";
|
|
233
235
|
if(window[FLAG])return;
|
|
234
236
|
window[FLAG]=true;
|
|
237
|
+
var ATTR=${JSON.stringify(UNAVAILABLE_VERSIONS_ATTR)};
|
|
235
238
|
var computeVersionSwitcherState=${computeVersionSwitcherState.toString()};
|
|
236
239
|
function setActive(a,active){
|
|
237
240
|
a.classList.toggle("font-bold",active);
|
|
@@ -239,11 +242,34 @@ a.classList.toggle("text-accent",active);
|
|
|
239
242
|
a.classList.toggle("text-fg",!active);
|
|
240
243
|
if(active){a.setAttribute("aria-current","page");}else{a.removeAttribute("aria-current");}
|
|
241
244
|
}
|
|
245
|
+
function setDisabled(a,disabled,unavailableLabel){
|
|
246
|
+
a.classList.toggle("hover:bg-accent/10",!disabled);
|
|
247
|
+
a.classList.toggle("hover:underline",!disabled);
|
|
248
|
+
a.classList.toggle("focus-visible:underline",!disabled);
|
|
249
|
+
a.classList.toggle("text-muted/50",disabled);
|
|
250
|
+
a.classList.toggle("cursor-not-allowed",disabled);
|
|
251
|
+
a.classList.toggle("pointer-events-none",disabled);
|
|
252
|
+
if(disabled){
|
|
253
|
+
a.setAttribute("aria-disabled","true");
|
|
254
|
+
a.setAttribute("tabindex","-1");
|
|
255
|
+
a.setAttribute("title",unavailableLabel);
|
|
256
|
+
a.classList.remove("font-bold","text-accent","text-fg");
|
|
257
|
+
a.removeAttribute("aria-current");
|
|
258
|
+
}else{
|
|
259
|
+
a.removeAttribute("aria-disabled");
|
|
260
|
+
a.removeAttribute("tabindex");
|
|
261
|
+
a.removeAttribute("title");
|
|
262
|
+
}
|
|
263
|
+
}
|
|
242
264
|
function rewire(){
|
|
265
|
+
var articleEl=document.querySelector("["+ATTR+"]");
|
|
266
|
+
var hasAvailabilityData=articleEl!==null;
|
|
267
|
+
var unavailableSlugs=hasAvailabilityData?(articleEl.getAttribute(ATTR)||"").split(",").filter(Boolean):[];
|
|
243
268
|
var containers=document.querySelectorAll("[data-version-rewire]");
|
|
244
269
|
for(var i=0;i<containers.length;i++){
|
|
245
270
|
var c=containers[i];
|
|
246
271
|
var config={base:c.getAttribute("data-base")||"",defaultLocale:c.getAttribute("data-default-locale")||"",trailingSlash:c.getAttribute("data-trailing-slash")==="true",currentLocale:c.getAttribute("data-current-locale")||""};
|
|
272
|
+
var unavailableLabel=c.getAttribute("data-unavailable-label")||"";
|
|
247
273
|
var versionAnchors=c.querySelectorAll("[data-version-slug]");
|
|
248
274
|
var slugs=[];
|
|
249
275
|
for(var j=0;j<versionAnchors.length;j++){
|
|
@@ -262,7 +288,14 @@ var slug=a.getAttribute("data-version-slug");
|
|
|
262
288
|
if(!slug)continue;
|
|
263
289
|
var href=state.versionHrefs[slug];
|
|
264
290
|
if(href!=null)a.setAttribute("href",href);
|
|
265
|
-
if(
|
|
291
|
+
if(hasAvailabilityData){
|
|
292
|
+
var disabled=unavailableSlugs.indexOf(slug)!==-1;
|
|
293
|
+
setDisabled(a,disabled,unavailableLabel);
|
|
294
|
+
if(!disabled)setActive(a,state.activeVersion===slug);
|
|
295
|
+
}else{
|
|
296
|
+
var alreadyDisabled=a.getAttribute("aria-disabled")==="true";
|
|
297
|
+
if(!alreadyDisabled)setActive(a,state.activeVersion===slug);
|
|
298
|
+
}
|
|
266
299
|
}
|
|
267
300
|
var label=c.querySelector("[data-version-trigger-label]");
|
|
268
301
|
if(label){
|
|
@@ -278,7 +311,14 @@ label.textContent=activeLabel!=null?activeLabel:state.activeVersion;
|
|
|
278
311
|
}
|
|
279
312
|
}
|
|
280
313
|
}
|
|
281
|
-
|
|
314
|
+
// The initial call is deferred to DOMContentLoaded when the script (inline in
|
|
315
|
+
// <header>, which parses before <article>) would otherwise run before the
|
|
316
|
+
// article element exists. Running early would read a missing ATTR as "no
|
|
317
|
+
// unavailable slugs" and clobber the SSR-correct disabled state with
|
|
318
|
+
// everything-enabled \u2014 a first-paint variant of the bug this rewire exists
|
|
319
|
+
// to fix (see the doc comment above; SSR already rendered the right state for
|
|
320
|
+
// this page, so a brief deferral loses nothing).
|
|
321
|
+
if(document.readyState==="loading"){document.addEventListener("DOMContentLoaded",rewire);}else{rewire();}
|
|
282
322
|
document.addEventListener(${JSON.stringify(AFTER_NAVIGATE_EVENT)},rewire);
|
|
283
323
|
})();`;
|
|
284
324
|
export {
|
|
@@ -23,6 +23,24 @@ export interface InlineVersionSwitcherDeps {
|
|
|
23
23
|
versionedDocsUrl: (slug: string, versionSlug: string, lang?: string) => string;
|
|
24
24
|
/** Prefix a path with the configured base directory. */
|
|
25
25
|
withBase: (path: string) => string;
|
|
26
|
+
/**
|
|
27
|
+
* Slugs of configured versions where the given (slug, locale) is NOT
|
|
28
|
+
* available — passed straight through as `<VersionSwitcher
|
|
29
|
+
* unavailableVersions>` so absent pages render disabled instead of 404
|
|
30
|
+
* links (#3215). Returns `undefined` when there is no availability data to
|
|
31
|
+
* compute (the shared helper already handles the no-slug/no-versions
|
|
32
|
+
* gates — see `version-availability`).
|
|
33
|
+
*
|
|
34
|
+
* Optional — `./inline-version-switcher` is a documented frozen-1.0 public
|
|
35
|
+
* subpath (`packages/zudo-doc/CLAUDE.md`/`API.md`), so a pre-#3215 caller
|
|
36
|
+
* that hand-constructs `InlineVersionSwitcherDeps` without this field must
|
|
37
|
+
* keep compiling. Omitting it reports no version as unavailable — byte-for-
|
|
38
|
+
* byte the pre-#3215 rendering — so unlike the nav wrappers' optional
|
|
39
|
+
* `versionedDocsUrl` (whose absence yields hrefs that escape the version
|
|
40
|
+
* silo) there is nothing degraded to warn about, and this builder runs on
|
|
41
|
+
* every versioned doc page so a warning would be pure per-page noise.
|
|
42
|
+
*/
|
|
43
|
+
getUnavailableVersions?: (slug: string | undefined, locale: string) => ReadonlySet<string> | undefined;
|
|
26
44
|
}
|
|
27
45
|
/**
|
|
28
46
|
* Build the `buildInlineVersionSwitcher` function bound to the host's settings
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
VersionSwitcher
|
|
4
4
|
} from "../i18n-version/index.js";
|
|
5
5
|
function createInlineVersionSwitcher(deps) {
|
|
6
|
-
const { settings, defaultLocale, t, docsUrl, versionedDocsUrl, withBase } = deps;
|
|
6
|
+
const { settings, defaultLocale, t, docsUrl, versionedDocsUrl, withBase, getUnavailableVersions } = deps;
|
|
7
7
|
function buildInlineVersionSwitcher(slug, locale, currentVersion) {
|
|
8
8
|
if (!settings.versions || settings.versions.length === 0) return void 0;
|
|
9
9
|
const isNonDefaultLocale = locale !== defaultLocale;
|
|
@@ -21,6 +21,7 @@ function createInlineVersionSwitcher(deps) {
|
|
|
21
21
|
unavailable: t("version.switcher.unavailable", locale),
|
|
22
22
|
allVersions: t("version.switcher.allVersions", locale)
|
|
23
23
|
};
|
|
24
|
+
const unavailableVersions = getUnavailableVersions?.(slug, locale);
|
|
24
25
|
return /* @__PURE__ */ jsx(
|
|
25
26
|
VersionSwitcher,
|
|
26
27
|
{
|
|
@@ -32,6 +33,7 @@ function createInlineVersionSwitcher(deps) {
|
|
|
32
33
|
latestUrl,
|
|
33
34
|
versionsPageUrl,
|
|
34
35
|
versionUrls,
|
|
36
|
+
unavailableVersions,
|
|
35
37
|
labels,
|
|
36
38
|
idSuffix: "inline"
|
|
37
39
|
}
|