@takazudo/zudo-doc 5.5.2 → 5.6.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 (70) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +39 -0
  3. package/dist/chrome/derive.d.ts +46 -0
  4. package/dist/chrome/derive.js +6 -2
  5. package/dist/config-assertions/index.d.ts +31 -0
  6. package/dist/config-assertions/index.js +24 -0
  7. package/dist/config.d.ts +23 -2
  8. package/dist/config.js +3 -0
  9. package/dist/current-path/index.d.ts +27 -0
  10. package/dist/current-path/index.js +11 -0
  11. package/dist/design-token-panel-bootstrap.d.ts +89 -23
  12. package/dist/design-token-panel-bootstrap.js +19 -6
  13. package/dist/doc-page-props/index.d.ts +5 -67
  14. package/dist/doc-route-entries/index.d.ts +10 -95
  15. package/dist/doc-route-entries/index.js +1 -78
  16. package/dist/doc-route-paths/index.d.ts +1 -1
  17. package/dist/head-with-defaults/index.d.ts +3 -1
  18. package/dist/head-with-defaults/index.js +76 -4
  19. package/dist/header/nav-active.d.ts +28 -0
  20. package/dist/header/nav-active.js +2 -1
  21. package/dist/header/nav-overflow-script.js +30 -17
  22. package/dist/header-with-defaults/index.js +11 -2
  23. package/dist/i18n-version/language-switcher.d.ts +6 -0
  24. package/dist/i18n-version/language-switcher.js +3 -1
  25. package/dist/i18n-version/version-switcher.d.ts +6 -0
  26. package/dist/i18n-version/version-switcher.js +3 -1
  27. package/dist/nav-source-docs/index.d.ts +7 -11
  28. package/dist/plugins/route-pages-candidates.d.ts +19 -0
  29. package/dist/plugins/route-pages-candidates.js +17 -0
  30. package/dist/plugins/routes.d.ts +46 -0
  31. package/dist/plugins/routes.js +72 -18
  32. package/dist/preset.d.ts +12 -1
  33. package/dist/preset.js +2 -0
  34. package/dist/route-context/index.js +2 -2
  35. package/dist/routes/_chrome.d.ts +1 -1
  36. package/dist/routes/_chrome.js +4 -0
  37. package/dist/routes/_context.d.ts +3 -3
  38. package/dist/routes/_design-token-panel-bootstrap.d.ts +18 -0
  39. package/dist/routes/_design-token-panel-bootstrap.js +11 -0
  40. package/dist/routes/_docs-helpers.d.ts +1 -36
  41. package/dist/routes/_docs-helpers.js +0 -138
  42. package/dist/safelist.css +1 -1
  43. package/dist/search-widget-script/generated-script.d.ts +8 -0
  44. package/dist/search-widget-script/generated-script.js +465 -0
  45. package/dist/search-widget-script/index.d.ts +1 -18
  46. package/dist/search-widget-script/index.js +1 -443
  47. package/dist/settings.d.ts +82 -1
  48. package/dist/sidebar-tree/category-meta.d.ts +9 -0
  49. package/dist/sidebar-tree/category-meta.js +21 -12
  50. package/dist/sidebar-tree-island/index.d.ts +8 -1
  51. package/dist/sidebar-tree-island/index.js +16 -14
  52. package/dist/site-schema/doc-route-entries.d.ts +89 -0
  53. package/dist/site-schema/doc-route-entries.js +83 -0
  54. package/dist/site-schema/index.d.ts +17 -0
  55. package/dist/site-schema/index.js +46 -0
  56. package/dist/site-schema/nav-tree.d.ts +28 -0
  57. package/dist/site-schema/nav-tree.js +138 -0
  58. package/dist/site-schema/types.d.ts +97 -0
  59. package/dist/site-schema/types.js +0 -0
  60. package/dist/theme/theme-pack-provider.d.ts +34 -3
  61. package/dist/theme/theme-pack-provider.js +30 -2
  62. package/eject/header/nav-active.ts +13 -1
  63. package/eject/header/nav-overflow-script.ts +30 -17
  64. package/eject/sidebar-tree-island/index.tsx +44 -20
  65. package/package.json +22 -12
  66. package/routes-src/_chrome.tsx +21 -9
  67. package/routes-src/_design-token-panel-bootstrap.tsx +63 -0
  68. package/routes-src/_docs-helpers.ts +18 -225
  69. package/routes-src/_virtual.d.ts +5 -2
  70. package/virtual-modules.d.ts +5 -2
@@ -9,6 +9,7 @@ import { smartBreakToHtml } from "../smart-break/index.js";
9
9
  import { AFTER_NAVIGATE_EVENT, BEFORE_NAVIGATE_EVENT } from "../transitions/index.js";
10
10
  import { filterTree } from "../sidebar-filter/index.js";
11
11
  import { findActiveSlug, normalizePath } from "../sidebar-active-slug/index.js";
12
+ import { CURRENT_PATH_DATASET_KEY, readCurrentPath } from "../current-path/index.js";
12
13
  import { ensureSidebarScrollPreserve } from "./sidebar-scroll-preserve.js";
13
14
  ensureSidebarScrollPreserve();
14
15
  function ToggleChevron({ isExpanded, className }) {
@@ -40,24 +41,25 @@ function saveOpenSet(set) {
40
41
  } catch {
41
42
  }
42
43
  }
43
- function deriveActiveSlugFromUrl(nodes) {
44
- if (typeof window === "undefined") return void 0;
45
- const pathname = normalizePath(window.location.pathname);
46
- return findActiveSlug(nodes, pathname);
44
+ function deriveActiveSlug(nodes, pathname) {
45
+ const resolved = readCurrentPath(CURRENT_PATH_DATASET_KEY, pathname);
46
+ if (!resolved) return void 0;
47
+ return findActiveSlug(nodes, normalizePath(resolved));
47
48
  }
48
- function useActiveSlug(nodes, initial) {
49
+ function useActiveSlug(nodes, initial, currentPath) {
49
50
  const [slug, setSlug] = useState(
50
- () => initial !== void 0 ? initial : deriveActiveSlugFromUrl(nodes)
51
+ () => initial !== void 0 ? initial : deriveActiveSlug(nodes, currentPath)
51
52
  );
52
53
  useEffect(() => {
53
- const update = () => {
54
- const found = deriveActiveSlugFromUrl(nodes);
54
+ const update = (pathname) => {
55
+ const found = deriveActiveSlug(nodes, pathname);
55
56
  if (found !== void 0) setSlug(found);
56
57
  };
57
- update();
58
- document.addEventListener(AFTER_NAVIGATE_EVENT, update);
59
- return () => document.removeEventListener(AFTER_NAVIGATE_EVENT, update);
60
- }, [nodes]);
58
+ const onNavigate = () => update();
59
+ update(currentPath);
60
+ document.addEventListener(AFTER_NAVIGATE_EVENT, onNavigate);
61
+ return () => document.removeEventListener(AFTER_NAVIGATE_EVENT, onNavigate);
62
+ }, [nodes, currentPath]);
61
63
  return slug;
62
64
  }
63
65
  function RootMenuItemEntry({ item }) {
@@ -112,8 +114,8 @@ function SidebarFooter({ links, themeDefaultMode }) {
112
114
  ] })
113
115
  );
114
116
  }
115
- function SidebarTree({ nodes, currentSlug, rootMenuItems, backToMenuLabel, localeLinks, themeDefaultMode }) {
116
- const activeSlug = useActiveSlug(nodes, currentSlug);
117
+ function SidebarTree({ nodes, currentSlug, currentPath, rootMenuItems, backToMenuLabel, localeLinks, themeDefaultMode }) {
118
+ const activeSlug = useActiveSlug(nodes, currentSlug, currentPath);
117
119
  const [query, setQuery] = useState("");
118
120
  const [showingRootMenu, setShowingRootMenu] = useState(false);
119
121
  const filterRef = useRef(null);
@@ -0,0 +1,89 @@
1
+ import type { AutoIndexNode, CategoryMeta, DocEntryLike, DocNavNode, DocPageBaseProps, HeadingItem, NavSourceDocs } from "./types.js";
2
+ /** One enumerated doc route: a content entry or an auto-generated category
3
+ * index, with all per-page derived data pre-computed. */
4
+ export interface DocRouteEntry<E extends DocEntryLike = DocEntryLike> {
5
+ /** Canonical route slug ("" for the docs root index — #1891). */
6
+ slug: string;
7
+ /** Optional-catchall params array — `toSlugParams(slug)` ([] for the root). */
8
+ slugParams: string[];
9
+ /**
10
+ * True when the entry came from the base collection rather than the locale
11
+ * collection (`!localeSlugSet.has(slug)`). Only meaningful on routes whose
12
+ * nav source performs a locale merge — routes without one (default-locale /
13
+ * versioned-EN, where `localeSlugSet` is empty) must ignore this field.
14
+ * Always false for autoIndex items.
15
+ */
16
+ isFallback: boolean;
17
+ /** Shared page props (kind/entry/autoIndex/breadcrumbs/prev/next/headings). */
18
+ props: DocPageBaseProps<E>;
19
+ }
20
+ export interface BuildDocRouteEntriesArgs<E extends DocEntryLike = DocEntryLike> {
21
+ /** Identity-stable nav source for this route's (locale, version) context. */
22
+ source: NavSourceDocs<E>;
23
+ /** Active locale for nav-tree labels and breadcrumbs. */
24
+ locale: string;
25
+ /**
26
+ * Unique memo signature for this route context. Each route file passes its
27
+ * own prefix plus the loop variables (version slug / locale), e.g.
28
+ * "docs;en", "locale-docs;ja", "v-docs;1.0", "v-locale-docs;1.0;ja" —
29
+ * call sites that share a docs array identity must never collide on a key.
30
+ */
31
+ routeSig: string;
32
+ /** Versioned URL closure bound to the route's version (+ locale). Presence
33
+ * switches the versioned behaviors (breadcrumbs, prev/next, child hrefs). */
34
+ urlFor?: (slug: string) => string;
35
+ }
36
+ /**
37
+ * Context slice `createDocRouteEntries` derives its bag from (epic Collapse
38
+ * Wiring Shells #2420, FACTORIES #2424 — breaking signature). STRUCTURAL SUBSET
39
+ * of the unified `RouteContext`/`ChromeContext` — the nav-tree builder is
40
+ * reconstructed by binding the context's 4-arg `buildNavTree` to `docsUrl`, and
41
+ * the breadcrumb trail by binding the package `buildBreadcrumbs` to the
42
+ * locale-prefixed `withBase` base, exactly as the pre-collapse wiring did.
43
+ */
44
+ export interface DocRouteEntriesContext<E extends DocEntryLike = DocEntryLike> {
45
+ /** Default locale code (drives the breadcrumb base-prefix selection). */
46
+ defaultLocale: string;
47
+ /** Build the nav tree for a locale (4-arg form with an explicit href builder). */
48
+ buildNavTree: (docs: E[], locale: string, categoryMeta: Map<string, CategoryMeta> | undefined, buildHref: (slug: string, locale: string) => string) => DocNavNode[];
49
+ /** Build a docs URL for the given slug and locale. */
50
+ docsUrl: (slug: string, locale?: string) => string;
51
+ /** Prefix a path with the configured base directory. */
52
+ withBase: (path: string) => string;
53
+ /** Collect all auto-generated index nodes (categories without index.mdx). */
54
+ collectAutoIndexNodes: (tree: DocNavNode[]) => AutoIndexNode[];
55
+ /** Determine the nav section (categoryMatch) for a slug. */
56
+ getNavSectionForSlug: (slug: string) => string | undefined;
57
+ /** Filter top-level nav nodes by a categoryMatch value. */
58
+ getNavSubtree: (tree: DocNavNode[], categoryMatch?: string) => DocNavNode[];
59
+ /** Convert a content entry slug to a canonical route slug. */
60
+ toRouteSlug: (entrySlug: string) => string;
61
+ /** Convert a canonical route slug to an optional-catchall params array. */
62
+ toSlugParams: (slug: string) => string[];
63
+ /** Extract headings from a raw MDX body (bound to settings depth/strategy). */
64
+ extractHeadings: (body: string) => HeadingItem[];
65
+ }
66
+ /**
67
+ * Result of `createDocRouteEntries`. Contains the `buildDocRouteEntries`
68
+ * function bound to the injected context.
69
+ */
70
+ export interface DocRouteEntriesAPI<E extends DocEntryLike = DocEntryLike> {
71
+ /**
72
+ * Enumerate all doc routes for one (locale, version) context, with per-entry
73
+ * derived data pre-computed. Memoized per build on the identity-stable
74
+ * `source.docs` array.
75
+ */
76
+ buildDocRouteEntries: (args: BuildDocRouteEntriesArgs<E>) => DocRouteEntry<E>[];
77
+ }
78
+ /**
79
+ * Create the `buildDocRouteEntries` function bound to the host's injected
80
+ * context.
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * import { createDocRouteEntries } from "@takazudo/zudo-doc/site-schema";
85
+ *
86
+ * export const { buildDocRouteEntries } = createDocRouteEntries(routeContext);
87
+ * ```
88
+ */
89
+ export declare function createDocRouteEntries<E extends DocEntryLike = DocEntryLike>(ctx: DocRouteEntriesContext<E>): DocRouteEntriesAPI<E>;
@@ -0,0 +1,83 @@
1
+ import { memoizeDerived } from "../nav-source-cache/index.js";
2
+ import {
3
+ resolveDocPrevNext,
4
+ flattenSubtree,
5
+ rewriteNavHref,
6
+ remapNavChildHrefs
7
+ } from "../doc-route-paths/index.js";
8
+ import { buildBreadcrumbs as buildBreadcrumbsImpl } from "./nav-tree.js";
9
+ let nextFactoryId = 1;
10
+ function createDocRouteEntries(ctx) {
11
+ const factoryId = nextFactoryId++;
12
+ const defaultLocale = ctx.defaultLocale;
13
+ const buildNavTree = (docs, locale, categoryMeta) => ctx.buildNavTree(docs, locale, categoryMeta, (slug, loc) => ctx.docsUrl(slug, loc));
14
+ const buildBreadcrumbs = (tree, slug, locale, urlFor) => buildBreadcrumbsImpl(
15
+ tree,
16
+ slug,
17
+ locale === defaultLocale ? ctx.withBase("/") : ctx.withBase(`/${locale}/`),
18
+ urlFor
19
+ );
20
+ const collectAutoIndexNodes = ctx.collectAutoIndexNodes;
21
+ const getNavSectionForSlug = ctx.getNavSectionForSlug;
22
+ const getNavSubtree = ctx.getNavSubtree;
23
+ const toRouteSlug = ctx.toRouteSlug;
24
+ const toSlugParams = ctx.toSlugParams;
25
+ const extractHeadings = ctx.extractHeadings;
26
+ function buildDocRouteEntries(args) {
27
+ const { source, locale, routeSig, urlFor } = args;
28
+ return memoizeDerived([source.docs], `docRouteEntries;${factoryId};${routeSig}`, () => {
29
+ const { docs, navDocs, categoryMeta, localeSlugSet } = source;
30
+ const tree = buildNavTree(navDocs, locale, categoryMeta);
31
+ const breadcrumbTree = urlFor ? tree : buildNavTree(docs, locale, categoryMeta);
32
+ const result = [];
33
+ for (const entry of docs) {
34
+ if (entry.data.category_no_page === true) continue;
35
+ const slug = entry.data.slug ?? toRouteSlug(entry.slug);
36
+ const navSection = getNavSectionForSlug(slug);
37
+ const subtree = getNavSubtree(tree, navSection);
38
+ const { prev: prevNode, next: nextNode } = resolveDocPrevNext(
39
+ tree,
40
+ flattenSubtree(subtree),
41
+ slug,
42
+ entry.data
43
+ );
44
+ result.push({
45
+ slug,
46
+ slugParams: toSlugParams(slug),
47
+ isFallback: !localeSlugSet.has(slug),
48
+ props: {
49
+ kind: "entry",
50
+ entry,
51
+ breadcrumbs: buildBreadcrumbs(breadcrumbTree, slug, locale, urlFor),
52
+ prev: rewriteNavHref(prevNode, urlFor),
53
+ next: rewriteNavHref(nextNode, urlFor),
54
+ headings: extractHeadings(entry.body ?? "")
55
+ }
56
+ });
57
+ }
58
+ for (const node of collectAutoIndexNodes(tree)) {
59
+ result.push({
60
+ slug: node.slug,
61
+ slugParams: toSlugParams(node.slug),
62
+ isFallback: false,
63
+ props: {
64
+ kind: "autoIndex",
65
+ autoIndex: urlFor ? {
66
+ ...node,
67
+ children: remapNavChildHrefs(node.children, urlFor)
68
+ } : node,
69
+ breadcrumbs: buildBreadcrumbs(breadcrumbTree, node.slug, locale, urlFor),
70
+ prev: null,
71
+ next: null,
72
+ headings: []
73
+ }
74
+ });
75
+ }
76
+ return result;
77
+ });
78
+ }
79
+ return { buildDocRouteEntries };
80
+ }
81
+ export {
82
+ createDocRouteEntries
83
+ };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Contract version of this module's shape. Bump on any breaking change to the
3
+ * exported types or functions so a consumer can fail closed rather than
4
+ * silently mis-read a newer schema (`@takazudo/zudo-doc/catalog` precedent).
5
+ */
6
+ export declare const schemaVersion = 1;
7
+ export type { AutoIndexNode, BreadcrumbItem, CategoryMeta, CollectionEntryLike, DocEntryLike, DocNavNode, DocPageAutoIndexProps, DocPageBaseProps, DocPageEntryProps, DocPageFrontmatter, HeadingItem, NavSourceDocs, SidebarFrontmatter, SidebarNode, } from "./types.js";
8
+ export type { BuildDocRouteEntriesArgs, DocRouteEntriesAPI, DocRouteEntriesContext, DocRouteEntry, } from "./doc-route-entries.js";
9
+ export type { BuildHref } from "./nav-tree.js";
10
+ export type { PaginationOverrides } from "../doc-route-paths/index.js";
11
+ export { buildBreadcrumbs, buildNavTree, collectAutoIndexNodes, findNode, firstRoutedHref, groupSatelliteNodes, isNavVisible, } from "./nav-tree.js";
12
+ export { flattenSubtree, flattenTree, remapNavChildHrefs, resolveDocPrevNext, rewriteNavHref, } from "../doc-route-paths/index.js";
13
+ export { getCategoryOrder, getNavSectionForSlug, getNavSubtree, } from "../nav-scope/index.js";
14
+ export type { NavScopeHeaderNavItem, NavScopeNode } from "../nav-scope/index.js";
15
+ export { buildSidebarTree } from "../sidebar-tree/build-tree.js";
16
+ export { extractHeadings } from "../extract-headings/index.js";
17
+ export { createDocRouteEntries } from "./doc-route-entries.js";
@@ -0,0 +1,46 @@
1
+ const schemaVersion = 1;
2
+ import {
3
+ buildBreadcrumbs,
4
+ buildNavTree,
5
+ collectAutoIndexNodes,
6
+ findNode,
7
+ firstRoutedHref,
8
+ groupSatelliteNodes,
9
+ isNavVisible
10
+ } from "./nav-tree.js";
11
+ import {
12
+ flattenSubtree,
13
+ flattenTree,
14
+ remapNavChildHrefs,
15
+ resolveDocPrevNext,
16
+ rewriteNavHref
17
+ } from "../doc-route-paths/index.js";
18
+ import {
19
+ getCategoryOrder,
20
+ getNavSectionForSlug,
21
+ getNavSubtree
22
+ } from "../nav-scope/index.js";
23
+ import { buildSidebarTree } from "../sidebar-tree/build-tree.js";
24
+ import { extractHeadings } from "../extract-headings/index.js";
25
+ import { createDocRouteEntries } from "./doc-route-entries.js";
26
+ export {
27
+ buildBreadcrumbs,
28
+ buildNavTree,
29
+ buildSidebarTree,
30
+ collectAutoIndexNodes,
31
+ createDocRouteEntries,
32
+ extractHeadings,
33
+ findNode,
34
+ firstRoutedHref,
35
+ flattenSubtree,
36
+ flattenTree,
37
+ getCategoryOrder,
38
+ getNavSectionForSlug,
39
+ getNavSubtree,
40
+ groupSatelliteNodes,
41
+ isNavVisible,
42
+ remapNavChildHrefs,
43
+ resolveDocPrevNext,
44
+ rewriteNavHref,
45
+ schemaVersion
46
+ };
@@ -0,0 +1,28 @@
1
+ import type { CategoryMeta } from "../sidebar-tree/types.js";
2
+ import type { BreadcrumbItem, DocEntryLike, DocNavNode } from "./types.js";
3
+ /** Filter predicate: true when a doc should appear in navigation. */
4
+ export declare function isNavVisible(doc: DocEntryLike): boolean;
5
+ /** A docs-href builder: `(slug, locale) => url`. */
6
+ export type BuildHref = (slug: string, locale: string) => string;
7
+ /**
8
+ * Build a recursive navigation tree from a flat doc collection. Delegates tree
9
+ * construction to the shared `buildSidebarTree`; keeps the host-side concerns
10
+ * (DocNavNode shape, root-index node synthesis). `buildHref` parameterizes the
11
+ * href space (default: the injected `docsUrl`). Unlike the host copy this drops
12
+ * the LRU / identity caches — the snapshot-anchored `stableDocs` already makes
13
+ * the input arrays identity-stable so the factory-level `memoizeDerived` short-
14
+ * circuits per build.
15
+ */
16
+ export declare function buildNavTree(docs: DocEntryLike[], locale: string, categoryMeta: Map<string, CategoryMeta> | undefined, buildHref: BuildHref): DocNavNode[];
17
+ /** Group "satellite" nodes (slug-prefix siblings) under their primary node. */
18
+ export declare function groupSatelliteNodes(tree: DocNavNode[], prefixes: string[]): DocNavNode[];
19
+ /** Find a node by slug anywhere in the tree. */
20
+ export declare function findNode(nodes: DocNavNode[], slug: string): DocNavNode | undefined;
21
+ /** Href of the first routed descendant, walking children depth-first. */
22
+ export declare function firstRoutedHref(node: DocNavNode): string | undefined;
23
+ /** Collect all category nodes that have children but no page (no index.mdx). */
24
+ export declare function collectAutoIndexNodes(nodes: DocNavNode[]): DocNavNode[];
25
+ /** Build breadcrumb trail by walking the nav tree. `homeHref` is the locale
26
+ * docs root (injected). `hrefFor` optionally remaps intermediate crumbs into
27
+ * a versioned URL space. */
28
+ export declare function buildBreadcrumbs(tree: DocNavNode[], slug: string, homeHref: string, hrefFor?: (slug: string) => string): BreadcrumbItem[];
@@ -0,0 +1,138 @@
1
+ import { buildSidebarTree } from "../sidebar-tree/build-tree.js";
2
+ import { toRouteSlug } from "../slug/index.js";
3
+ function isNavVisible(doc) {
4
+ return !doc.data.unlisted && !doc.data.standalone;
5
+ }
6
+ function toNavNode(node) {
7
+ return {
8
+ slug: node.id,
9
+ label: node.label,
10
+ description: node.description,
11
+ position: node.sidebar_position ?? 999,
12
+ href: node.href,
13
+ hasPage: node.hasPage,
14
+ children: node.children.map(toNavNode),
15
+ sortOrder: node.sortOrder ?? "asc"
16
+ };
17
+ }
18
+ function findRootIndexDoc(docs) {
19
+ let found;
20
+ for (const d of docs) {
21
+ const slug = d.data.slug ?? toRouteSlug(d.slug);
22
+ if (slug === "") found = d;
23
+ }
24
+ return found;
25
+ }
26
+ function toRootNavNode(doc, locale, buildHref, categoryMeta) {
27
+ const meta = categoryMeta?.get("");
28
+ const noPage = doc.data.category_no_page ?? meta?.noPage;
29
+ const sortOrder = doc.data.category_sort_order ?? meta?.sortOrder ?? "asc";
30
+ return {
31
+ slug: "",
32
+ label: doc.data.sidebar_label ?? doc.data.title ?? meta?.label ?? "",
33
+ description: doc.data.description ?? meta?.description,
34
+ position: doc.data.sidebar_position ?? meta?.position ?? 999,
35
+ href: noPage ? void 0 : buildHref("", locale),
36
+ hasPage: noPage !== true,
37
+ children: [],
38
+ sortOrder
39
+ };
40
+ }
41
+ function buildNavTree(docs, locale, categoryMeta, buildHref) {
42
+ const href = buildHref;
43
+ const sidebarTree = buildSidebarTree(
44
+ docs,
45
+ locale,
46
+ {
47
+ categoryMeta,
48
+ buildHref: (slug, loc) => href(slug, loc),
49
+ // Host call sites own visibility; disable the builder's default filter so
50
+ // breadcrumb (unfiltered) and nav (pre-filtered) paths are unchanged.
51
+ isNavVisible: () => true
52
+ }
53
+ );
54
+ const result = sidebarTree.map(toNavNode);
55
+ const rootDoc = findRootIndexDoc(docs);
56
+ if (rootDoc) {
57
+ result.push(toRootNavNode(rootDoc, locale, href, categoryMeta));
58
+ result.sort((a, b) => {
59
+ const posCompare = a.position - b.position;
60
+ if (posCompare !== 0) return posCompare;
61
+ return a.slug.localeCompare(b.slug);
62
+ });
63
+ }
64
+ return result;
65
+ }
66
+ function groupSatelliteNodes(tree, prefixes) {
67
+ const result = [...tree];
68
+ for (const prefix of prefixes) {
69
+ const primaryIdx = result.findIndex((n) => n.slug === prefix);
70
+ if (primaryIdx < 0) continue;
71
+ const primary = result[primaryIdx];
72
+ if (!primary) continue;
73
+ const satelliteIdxs = [];
74
+ for (let i = 0; i < result.length; i++) {
75
+ const node = result[i];
76
+ if (node && i !== primaryIdx && node.slug.startsWith(`${prefix}-`)) {
77
+ satelliteIdxs.push(i);
78
+ }
79
+ }
80
+ if (satelliteIdxs.length === 0) continue;
81
+ const extraChildren = [];
82
+ for (const idx of satelliteIdxs) {
83
+ const node = result[idx];
84
+ if (node) extraChildren.push(node);
85
+ }
86
+ result[primaryIdx] = { ...primary, children: [...primary.children, ...extraChildren] };
87
+ for (const idx of satelliteIdxs.reverse()) result.splice(idx, 1);
88
+ }
89
+ return result;
90
+ }
91
+ function findNode(nodes, slug) {
92
+ for (const node of nodes) {
93
+ if (node.slug === slug) return node;
94
+ const found = findNode(node.children, slug);
95
+ if (found) return found;
96
+ }
97
+ return void 0;
98
+ }
99
+ function firstRoutedHref(node) {
100
+ for (const child of node.children) {
101
+ if (child.hasPage && child.href) return child.href;
102
+ const nested = firstRoutedHref(child);
103
+ if (nested) return nested;
104
+ }
105
+ return void 0;
106
+ }
107
+ function collectAutoIndexNodes(nodes) {
108
+ const result = [];
109
+ for (const node of nodes) {
110
+ if (!node.hasPage && node.children.length > 0 && node.href) result.push(node);
111
+ result.push(...collectAutoIndexNodes(node.children));
112
+ }
113
+ return result;
114
+ }
115
+ function buildBreadcrumbs(tree, slug, homeHref, hrefFor) {
116
+ const parts = slug.split("/");
117
+ const crumbs = [{ label: "", href: homeHref }];
118
+ let nodes = tree;
119
+ for (let i = 0; i < parts.length; i++) {
120
+ const partialSlug = parts.slice(0, i + 1).join("/");
121
+ const node = nodes.find((n) => n.slug === partialSlug);
122
+ if (!node) break;
123
+ const isLast = i === parts.length - 1;
124
+ const href = isLast ? void 0 : hrefFor && node.href !== void 0 ? hrefFor(node.slug) : node.href;
125
+ crumbs.push({ label: node.label, href });
126
+ nodes = node.children;
127
+ }
128
+ return crumbs;
129
+ }
130
+ export {
131
+ buildBreadcrumbs,
132
+ buildNavTree,
133
+ collectAutoIndexNodes,
134
+ findNode,
135
+ firstRoutedHref,
136
+ groupSatelliteNodes,
137
+ isNavVisible
138
+ };
@@ -0,0 +1,97 @@
1
+ import type { CategoryMeta, CollectionEntryLike, SidebarFrontmatter, SidebarNode } from "../sidebar-tree/types.js";
2
+ import type { BreadcrumbItem } from "../breadcrumb/types.js";
3
+ import type { HeadingItem } from "../extract-headings/index.js";
4
+ export type { BreadcrumbItem, CategoryMeta, CollectionEntryLike, HeadingItem, SidebarFrontmatter, SidebarNode, };
5
+ /**
6
+ * Nav tree node shape as consumed by doc-route pages.
7
+ *
8
+ * Structurally identical to a host project's `NavNode`. Defined here so the
9
+ * package types depend on no host alias and no engine package; a host `NavNode`
10
+ * is a structural subtype and assignable wherever `DocNavNode` is expected.
11
+ */
12
+ export interface DocNavNode {
13
+ slug: string;
14
+ label: string;
15
+ description?: string;
16
+ position: number;
17
+ href?: string;
18
+ hasPage: boolean;
19
+ children: DocNavNode[];
20
+ sortOrder?: "asc" | "desc";
21
+ collapsed?: boolean;
22
+ }
23
+ /** A category node with children but no page of its own — an auto-index. */
24
+ export interface AutoIndexNode extends DocNavNode {
25
+ children: DocNavNode[];
26
+ }
27
+ /**
28
+ * Minimal docs frontmatter shape consumed by the doc-page types.
29
+ *
30
+ * A structural subset of a project's full docs schema — only the fields the
31
+ * nav/route domain actually reads. A richer `DocsData` is a structural
32
+ * supertype and is assignable here.
33
+ */
34
+ export interface DocPageFrontmatter {
35
+ title: string;
36
+ description?: string;
37
+ slug?: string;
38
+ draft?: boolean;
39
+ unlisted?: boolean;
40
+ standalone?: boolean;
41
+ sidebar_position?: number;
42
+ sidebar_label?: string;
43
+ category_no_page?: boolean;
44
+ category_sort_order?: "asc" | "desc";
45
+ pagination_prev?: string | null;
46
+ pagination_next?: string | null;
47
+ tags?: string[];
48
+ [key: string]: unknown;
49
+ }
50
+ /**
51
+ * The structural doc-collection entry this domain reads: a slug, docs
52
+ * frontmatter, and an optional raw body. zfb's `CollectionEntry<DocPageFrontmatter>`
53
+ * (exported as `DocPageEntry` from `@takazudo/zudo-doc/doc-page-props`) is
54
+ * assignable to it, as is any plain fixture object of the same shape.
55
+ */
56
+ export type DocEntryLike = CollectionEntryLike<DocPageFrontmatter>;
57
+ /** Shared fields present in every doc-page route. */
58
+ interface DocPagePropsBase {
59
+ breadcrumbs: BreadcrumbItem[];
60
+ prev: DocNavNode | null;
61
+ next: DocNavNode | null;
62
+ /** Depth-2/3/4 headings extracted from the MDX body, for SSG TOC links. */
63
+ headings: HeadingItem[];
64
+ }
65
+ /** Branch: a real content entry. `autoIndex` is absent. */
66
+ export interface DocPageEntryProps<E extends DocEntryLike = DocEntryLike> extends DocPagePropsBase {
67
+ kind: "entry";
68
+ entry: E;
69
+ autoIndex?: undefined;
70
+ }
71
+ /** Branch: an auto-generated category index. `entry` is absent. */
72
+ export interface DocPageAutoIndexProps extends DocPagePropsBase {
73
+ kind: "autoIndex";
74
+ autoIndex: AutoIndexNode;
75
+ entry?: undefined;
76
+ }
77
+ /** Discriminated union for the `kind` prop. Narrow via `props.kind === "entry"`. */
78
+ export type DocPageBaseProps<E extends DocEntryLike = DocEntryLike> = DocPageEntryProps<E> | DocPageAutoIndexProps;
79
+ /**
80
+ * The resolved, identity-stable doc set for one (locale, version) context —
81
+ * the input every route-entry enumeration starts from.
82
+ *
83
+ * The concrete resolver that PRODUCES one (`createNavSourceDocs`) reads
84
+ * `_category_.json` sidecars off disk and therefore stays in
85
+ * `@takazudo/zudo-doc/nav-source-docs`; only the shape is browser-safe.
86
+ */
87
+ export interface NavSourceDocs<E extends DocEntryLike = DocEntryLike> {
88
+ /** Full doc list (merged + draft-filtered; unlisted retained per options). */
89
+ docs: E[];
90
+ /** `docs.filter(isNavVisible)` — stable instance for buildNavTree. */
91
+ navDocs: E[];
92
+ /** Stable category-meta Map for the active (locale, version). */
93
+ categoryMeta: Map<string, CategoryMeta>;
94
+ /** Slugs that came from the locale collection (for isFallback). Empty for
95
+ * default-locale / single-collection cases. */
96
+ localeSlugSet: ReadonlySet<string>;
97
+ }
File without changes
@@ -1,5 +1,34 @@
1
1
  import type { JSX } from "preact";
2
2
  import type { ThemePackRegistry } from "../theme-packs-registry/index.js";
3
+ /**
4
+ * Transient `<html>` attribute the bootstrap sets while the pack stylesheet
5
+ * request is in flight, but ONLY while `document.readyState === "loading"`
6
+ * (#3407/#3413) — a post-paint re-evaluation (SPA embedding, #3399) inserts a
7
+ * corrective link without ever arming the latch, since the body it would hide
8
+ * is already painted. Paired with {@link THEME_PACK_LATCH_CSS}; cleared on
9
+ * the link's load/error or by the watchdog. Deliberately NOT part of
10
+ * `preserveHtmlAttrs` — it must never survive a soft navigation.
11
+ *
12
+ * Not to be confused with `THEME_PACK_LINK_LOADING_ATTR`
13
+ * (`data-zd-theme-pack-css-loading`), which marks the in-flight `<link>`
14
+ * during a RUNTIME `applyThemePack` swap. Different element, different
15
+ * mechanism — a runtime swap awaits the sheet off-screen and never hides
16
+ * the body.
17
+ */
18
+ export declare const THEME_PACK_LOADING_ATTR = "data-zd-theme-pack-loading";
19
+ /**
20
+ * Upper bound (ms) on how long the latch may hide the body. Past this the
21
+ * attribute is cleared regardless of the stylesheet's state: blank-screen
22
+ * avoidance wins over strict no-FOUC (see the latch contract in the file
23
+ * header).
24
+ */
25
+ export declare const THEME_PACK_LOAD_WATCHDOG_MS = 2000;
26
+ /**
27
+ * The latch rule, emitted as a build-static `<style>` BEFORE the bootstrap
28
+ * script so it is live by the time the body parses. Inert until the script
29
+ * sets {@link THEME_PACK_LOADING_ATTR}.
30
+ */
31
+ export declare const THEME_PACK_LATCH_CSS = "html[data-zd-theme-pack-loading] body{visibility:hidden}";
3
32
  export interface ThemePackProviderProps {
4
33
  /** The build-configured pack slug (`settings.themePack`, default `"default"`). */
5
34
  configuredSlug: string;
@@ -35,8 +64,10 @@ export declare function resolveThemePackSsrSlug(registry: ThemePackRegistry | nu
35
64
  */
36
65
  export declare function buildThemePackBootstrap(configuredSlug: string, enabled: Record<string, string>, base: string): string;
37
66
  /**
38
- * Server-rendered head fragment: the pre-paint bootstrap `<script>` plus the
39
- * `<noscript>` configured-pack fallback. No hydration — like
40
- * `ColorSchemeProvider` it just emits markup the engine streams into `<head>`.
67
+ * Server-rendered head fragment: the anti-FOUC latch `<style>`, the pre-paint
68
+ * bootstrap `<script>`, and the `<noscript>` configured-pack fallback. No
69
+ * hydration — like `ColorSchemeProvider` it just emits markup the engine
70
+ * streams into `<head>`. The latch style MUST stay first (see the contract in
71
+ * the file header).
41
72
  */
42
73
  export default function ThemePackProvider({ configuredSlug, enabled, base, }: ThemePackProviderProps): JSX.Element;