@takazudo/zudo-doc 0.2.21 → 1.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/bin/gen-z-index.mjs +164 -0
- package/bin/tags-audit-runner.ts +126 -0
- package/bin/tags-audit.mjs +64 -0
- package/bin/zudo-doc.mjs +83 -0
- package/dist/ai-chat-modal/index.d.ts +8 -0
- package/dist/ai-chat-modal/index.js +243 -0
- package/dist/body-foot-util/body-foot-util-area.d.ts +4 -8
- package/dist/body-foot-util/edit-link.d.ts +4 -8
- package/dist/body-foot-util/index.d.ts +4 -3
- package/dist/breadcrumb/breadcrumb.d.ts +5 -9
- package/dist/breadcrumb/find-path.d.ts +2 -5
- package/dist/breadcrumb/index.d.ts +4 -4
- package/dist/breadcrumb/types.d.ts +3 -5
- package/dist/category-nav/index.d.ts +86 -0
- package/dist/category-nav/index.js +44 -0
- package/dist/category-tree-nav/index.d.ts +65 -0
- package/dist/category-tree-nav/index.js +24 -0
- package/dist/code-group/index.d.ts +10 -0
- package/dist/code-group/index.js +37 -0
- package/dist/code-syntax/code-block-enhancer-script.d.ts +1 -3
- package/dist/code-syntax/code-block-enhancer.d.ts +3 -6
- package/dist/code-syntax/index.d.ts +9 -8
- package/dist/code-syntax/mermaid-init-script.d.ts +3 -5
- package/dist/code-syntax/mermaid-init.d.ts +4 -7
- package/dist/code-syntax/tabs-init-script.d.ts +1 -3
- package/dist/code-syntax/tabs-init.d.ts +3 -6
- package/dist/code-syntax/tabs.d.ts +4 -7
- package/dist/color-scheme-utils.d.ts +125 -0
- package/dist/color-scheme-utils.js +151 -0
- package/dist/compose-meta-title/index.d.ts +7 -0
- package/dist/compose-meta-title/index.js +10 -0
- package/dist/content/component-map.d.ts +13 -17
- package/dist/content/content-blockquote.d.ts +3 -6
- package/dist/content/content-code.d.ts +3 -6
- package/dist/content/content-code.js +1 -1
- package/dist/content/content-link.d.ts +3 -6
- package/dist/content/content-link.js +1 -1
- package/dist/content/content-ol.d.ts +3 -6
- package/dist/content/content-paragraph.d.ts +3 -6
- package/dist/content/content-strong.d.ts +3 -6
- package/dist/content/content-table.d.ts +3 -6
- package/dist/content/content-ul.d.ts +3 -6
- package/dist/content/heading-h2.d.ts +3 -6
- package/dist/content/heading-h3.d.ts +3 -6
- package/dist/content/heading-h4.d.ts +3 -6
- package/dist/content/index.d.ts +29 -13
- package/dist/content-admonition/index.d.ts +15 -0
- package/dist/content-admonition/index.js +14 -0
- package/dist/design-token-panel-bootstrap.d.ts +38 -0
- package/dist/design-token-panel-bootstrap.js +29 -0
- package/dist/desktop-sidebar-toggle-island/index.d.ts +5 -0
- package/dist/desktop-sidebar-toggle-island/index.js +62 -0
- package/dist/details/details.d.ts +3 -7
- package/dist/details/index.d.ts +2 -2
- package/dist/doc-body-end/index.d.ts +21 -0
- package/dist/doc-body-end/index.js +15 -0
- package/dist/doc-content-header/index.d.ts +69 -0
- package/dist/doc-content-header/index.js +49 -0
- package/dist/doc-history/index.d.ts +10 -0
- package/dist/doc-history/index.js +407 -0
- package/dist/doc-history-area/index.d.ts +84 -0
- package/dist/doc-history-area/index.js +76 -0
- package/dist/doc-metainfo-area/index.d.ts +46 -0
- package/dist/doc-metainfo-area/index.js +41 -0
- package/dist/doc-page-props/index.d.ts +93 -0
- package/dist/doc-page-props/index.js +0 -0
- package/dist/doc-page-renderer/index.d.ts +128 -0
- package/dist/doc-page-renderer/index.js +103 -0
- package/dist/doc-page-shell/index.d.ts +136 -0
- package/dist/doc-page-shell/index.js +127 -0
- package/dist/doc-pager/index.d.ts +25 -0
- package/dist/doc-pager/index.js +41 -0
- package/dist/doc-route-entries/index.d.ts +125 -0
- package/dist/doc-route-entries/index.js +76 -0
- package/dist/doc-route-paths/index.d.ts +54 -0
- package/dist/doc-route-paths/index.js +61 -0
- package/dist/doc-tags-area/index.d.ts +38 -0
- package/dist/doc-tags-area/index.js +30 -0
- package/dist/doclayout/anchors.d.ts +7 -9
- package/dist/doclayout/doc-layout-with-defaults.d.ts +16 -12
- package/dist/doclayout/doc-layout.d.ts +5 -9
- package/dist/doclayout/index.d.ts +6 -6
- package/dist/eject/index.d.ts +33 -0
- package/dist/eject/index.js +229 -0
- package/dist/extract-headings/index.d.ts +43 -0
- package/dist/extract-headings/index.js +111 -0
- package/dist/factory-context/index.d.ts +80 -0
- package/dist/factory-context/index.js +0 -0
- package/dist/features.css +882 -0
- package/dist/footer/footer.d.ts +4 -8
- package/dist/footer/index.d.ts +13 -3
- package/dist/footer/types.d.ts +4 -6
- package/dist/footer-with-defaults/index.d.ts +63 -0
- package/dist/footer-with-defaults/index.js +128 -0
- package/dist/frontmatter-preview-data/index.d.ts +26 -0
- package/dist/frontmatter-preview-data/index.js +23 -0
- package/dist/github-helpers/index.d.ts +17 -0
- package/dist/github-helpers/index.js +16 -0
- package/dist/head/doc-head.d.ts +3 -6
- package/dist/head/index.d.ts +9 -5
- package/dist/head/og-tags.d.ts +4 -7
- package/dist/head/twitter-card.d.ts +3 -6
- package/dist/head/types.d.ts +1 -3
- package/dist/head-with-defaults/index.d.ts +49 -0
- package/dist/head-with-defaults/index.js +62 -0
- package/dist/header/header.d.ts +6 -10
- package/dist/header/index.d.ts +16 -6
- package/dist/header/nav-active.d.ts +5 -7
- package/dist/header/nav-overflow-script.d.ts +1 -3
- package/dist/header/right-items.d.ts +3 -6
- package/dist/header/types.d.ts +10 -12
- package/dist/header-with-defaults/index.d.ts +77 -0
- package/dist/header-with-defaults/index.js +163 -0
- package/dist/html-preview-wrapper/dedent.d.ts +1 -3
- package/dist/html-preview-wrapper/highlighted-code.d.ts +3 -7
- package/dist/html-preview-wrapper/html-preview-wrapper.d.ts +6 -10
- package/dist/html-preview-wrapper/html-preview.d.ts +5 -9
- package/dist/html-preview-wrapper/index.d.ts +8 -5
- package/dist/html-preview-wrapper/preflight.d.ts +1 -3
- package/dist/html-preview-wrapper/preview-base.d.ts +4 -8
- package/dist/i18n-version/index.d.ts +7 -5
- package/dist/i18n-version/language-switcher.d.ts +5 -8
- package/dist/i18n-version/types.d.ts +3 -5
- package/dist/i18n-version/version-banner.d.ts +4 -8
- package/dist/i18n-version/version-switcher.d.ts +7 -10
- package/dist/icons/index.d.ts +9 -13
- package/dist/image-enlarge/index.d.ts +13 -0
- package/dist/image-enlarge/index.js +176 -0
- package/dist/index.d.ts +26 -2
- package/dist/inline-version-switcher/index.d.ts +31 -0
- package/dist/inline-version-switcher/index.js +44 -0
- package/dist/integrations/claude-resources/escape-for-mdx.d.ts +1 -3
- package/dist/integrations/claude-resources/generate.d.ts +2 -4
- package/dist/integrations/claude-resources/index.d.ts +7 -10
- package/dist/integrations/doc-history/index.d.ts +21 -24
- package/dist/integrations/doc-history/pre-build.d.ts +7 -9
- package/dist/integrations/llms-txt/dev-middleware.d.ts +7 -11
- package/dist/integrations/llms-txt/emit.d.ts +2 -7
- package/dist/integrations/llms-txt/generate.d.ts +3 -8
- package/dist/integrations/llms-txt/index.d.ts +33 -8
- package/dist/integrations/llms-txt/load.d.ts +3 -7
- package/dist/integrations/llms-txt/strip.d.ts +2 -6
- package/dist/integrations/llms-txt/types.d.ts +9 -13
- package/dist/integrations/search-index/build-emitter.d.ts +4 -7
- package/dist/integrations/search-index/collect.d.ts +2 -5
- package/dist/integrations/search-index/dev-middleware.d.ts +5 -8
- package/dist/integrations/search-index/index.d.ts +7 -5
- package/dist/integrations/search-index/types.d.ts +5 -7
- package/dist/island-types/index.d.ts +44 -0
- package/dist/island-types/index.js +12 -0
- package/dist/locale-merge/index.d.ts +114 -0
- package/dist/locale-merge/index.js +27 -0
- package/dist/math-block/index.d.ts +18 -0
- package/dist/math-block/index.js +29 -0
- package/dist/md-utils/index.d.ts +6 -8
- package/dist/md-utils/index.js +2 -1
- package/dist/mdx-components/index.d.ts +46 -0
- package/dist/mdx-components/index.js +161 -0
- package/dist/mermaid-enlarge/index.d.ts +11 -0
- package/dist/mermaid-enlarge/index.js +290 -0
- package/dist/metainfo/doc-metainfo.d.ts +5 -9
- package/dist/metainfo/doc-tags.d.ts +7 -11
- package/dist/metainfo/frontmatter-preview.d.ts +8 -12
- package/dist/metainfo/index.d.ts +6 -4
- package/dist/nav-data-prep/index.d.ts +72 -0
- package/dist/nav-data-prep/index.js +34 -0
- package/dist/nav-indexing/category-nav.d.ts +4 -8
- package/dist/nav-indexing/category-tree-nav.d.ts +4 -8
- package/dist/nav-indexing/doc-card-grid.d.ts +4 -8
- package/dist/nav-indexing/docs-sitemap.d.ts +4 -8
- package/dist/nav-indexing/index.d.ts +37 -10
- package/dist/nav-indexing/nav-card-grid.d.ts +4 -8
- package/dist/nav-indexing/site-tree-nav-demo.d.ts +4 -8
- package/dist/nav-indexing/tag-nav.d.ts +6 -10
- package/dist/nav-indexing/types.d.ts +6 -8
- package/dist/nav-indexing/versions-page-content.d.ts +4 -8
- package/dist/nav-scope/index.d.ts +41 -0
- package/dist/nav-scope/index.js +41 -0
- package/dist/nav-source-cache/index.d.ts +13 -0
- package/dist/nav-source-cache/index.js +28 -0
- package/dist/nav-source-docs/index.d.ts +169 -0
- package/dist/nav-source-docs/index.js +123 -0
- package/dist/page-loading/index.d.ts +1 -2
- package/dist/page-loading/page-loading-overlay.d.ts +4 -8
- package/dist/plugins/claude-resources.d.ts +3 -0
- package/dist/plugins/claude-resources.js +27 -0
- package/dist/plugins/connect-adapter.d.ts +31 -0
- package/dist/plugins/connect-adapter.js +78 -0
- package/dist/plugins/doc-history.d.ts +3 -0
- package/dist/plugins/doc-history.js +54 -0
- package/dist/plugins/llms-txt.d.ts +3 -0
- package/dist/plugins/llms-txt.js +48 -0
- package/dist/plugins/plugin-utils.d.ts +17 -0
- package/dist/plugins/plugin-utils.js +11 -0
- package/dist/plugins/routes.d.ts +2 -0
- package/dist/plugins/routes.js +100 -0
- package/dist/plugins/search-index.d.ts +3 -0
- package/dist/plugins/search-index.js +24 -0
- package/dist/preset.d.ts +198 -0
- package/dist/preset.js +185 -0
- package/dist/render-markdown/index.d.ts +5 -0
- package/dist/render-markdown/index.js +115 -0
- package/dist/robots.d.ts +36 -0
- package/dist/robots.js +17 -0
- package/dist/route-enumerators/index.d.ts +204 -0
- package/dist/route-enumerators/index.js +156 -0
- package/dist/routes/404.d.ts +7 -0
- package/dist/routes/404.js +47 -0
- package/dist/routes/_chrome.d.ts +32 -0
- package/dist/routes/_chrome.js +365 -0
- package/dist/routes/_context.d.ts +56 -0
- package/dist/routes/_context.js +222 -0
- package/dist/routes/_docs-helpers.d.ts +43 -0
- package/dist/routes/_docs-helpers.js +173 -0
- package/dist/routes/_virtual.d.js +0 -0
- package/dist/routes/api-ai-chat.d.ts +5 -0
- package/dist/routes/api-ai-chat.js +24 -0
- package/dist/routes/docs-slug.d.ts +21 -0
- package/dist/routes/docs-slug.js +26 -0
- package/dist/routes/docs-tags-index.d.ts +7 -0
- package/dist/routes/docs-tags-index.js +11 -0
- package/dist/routes/docs-tags-tag.d.ts +23 -0
- package/dist/routes/docs-tags-tag.js +19 -0
- package/dist/routes/docs-versions.d.ts +7 -0
- package/dist/routes/docs-versions.js +11 -0
- package/dist/routes/index.d.ts +7 -0
- package/dist/routes/index.js +117 -0
- package/dist/routes/locale-docs-slug.d.ts +27 -0
- package/dist/routes/locale-docs-slug.js +40 -0
- package/dist/routes/locale-docs-tags-index.d.ts +18 -0
- package/dist/routes/locale-docs-tags-index.js +15 -0
- package/dist/routes/locale-docs-tags-tag.d.ts +25 -0
- package/dist/routes/locale-docs-tags-tag.js +22 -0
- package/dist/routes/locale-docs-versions.d.ts +18 -0
- package/dist/routes/locale-docs-versions.js +15 -0
- package/dist/routes/locale-index.d.ts +24 -0
- package/dist/routes/locale-index.js +132 -0
- package/dist/routes/robots.txt.d.ts +3 -0
- package/dist/routes/robots.txt.js +8 -0
- package/dist/routes/sitemap.xml.d.ts +5 -0
- package/dist/routes/sitemap.xml.js +31 -0
- package/dist/routes/v-docs-slug.d.ts +27 -0
- package/dist/routes/v-docs-slug.js +34 -0
- package/dist/routes/v-locale-docs-slug.d.ts +31 -0
- package/dist/routes/v-locale-docs-slug.js +50 -0
- package/dist/safelist.css +1 -1
- package/dist/search-widget/index.d.ts +38 -0
- package/dist/search-widget/index.js +169 -0
- package/dist/search-widget-script/index.d.ts +18 -0
- package/dist/search-widget-script/index.js +465 -0
- package/dist/settings.d.ts +270 -0
- package/dist/settings.js +0 -0
- package/dist/sidebar/index.d.ts +21 -3
- package/dist/sidebar/sidebar.d.ts +5 -9
- package/dist/sidebar/types.d.ts +4 -6
- package/dist/sidebar-prepaint/index.d.ts +21 -0
- package/dist/sidebar-prepaint/index.js +23 -0
- package/dist/sidebar-resizer/index.d.ts +2 -6
- package/dist/sidebar-resizer/sidebar-resizer-init.d.ts +6 -10
- package/dist/sidebar-toggle-island/index.d.ts +13 -0
- package/dist/sidebar-toggle-island/index.js +122 -0
- package/dist/sidebar-tree/build-tree.d.ts +4 -8
- package/dist/sidebar-tree/category-meta.d.ts +3 -6
- package/dist/sidebar-tree/index.d.ts +18 -3
- package/dist/sidebar-tree/types.d.ts +6 -8
- package/dist/sidebar-tree-island/index.d.ts +13 -0
- package/dist/sidebar-tree-island/index.js +454 -0
- package/dist/sidebar-utils/index.d.ts +64 -0
- package/dist/sidebar-utils/index.js +88 -0
- package/dist/sidebar-with-defaults/index.d.ts +38 -0
- package/dist/sidebar-with-defaults/index.js +45 -0
- package/dist/site-tree-nav/index.d.ts +71 -0
- package/dist/site-tree-nav/index.js +36 -0
- package/dist/site-tree-nav-island/index.d.ts +11 -0
- package/dist/site-tree-nav-island/index.js +176 -0
- package/dist/slug/index.d.ts +4 -0
- package/dist/slug/index.js +19 -0
- package/dist/smart-break/index.d.ts +55 -0
- package/dist/{toc/smart-break.js → smart-break/index.js} +30 -3
- package/dist/tab-item/index.d.ts +2 -2
- package/dist/tab-item/tab-item.d.ts +6 -7
- package/dist/tag-helpers/index.d.ts +40 -0
- package/dist/tag-helpers/index.js +54 -0
- package/dist/tag-pages/index.d.ts +114 -0
- package/dist/tag-pages/index.js +146 -0
- package/dist/tags-audit.d.ts +107 -0
- package/dist/tags-audit.js +321 -0
- package/dist/theme/color-scheme-provider.d.ts +4 -8
- package/dist/theme/color-tweak-export-modal.d.ts +4 -7
- package/dist/theme/design-token-serde.d.ts +14 -17
- package/dist/theme/design-token-types.d.ts +4 -6
- package/dist/theme/iframe-bridge.d.ts +12 -14
- package/dist/theme/index.d.ts +7 -9
- package/dist/theme/theme-toggle.d.ts +3 -8
- package/dist/theme-toggle/color-scheme-sync.d.ts +5 -7
- package/dist/theme-toggle/index.d.ts +4 -8
- package/dist/toc/cx.d.ts +2 -3
- package/dist/toc/index.d.ts +8 -6
- package/dist/toc/mobile-toc.d.ts +5 -9
- package/dist/toc/mobile-toc.js +1 -1
- package/dist/toc/toc-title.d.ts +1 -3
- package/dist/toc/toc.d.ts +5 -9
- package/dist/toc/toc.js +1 -1
- package/dist/toc/types.d.ts +1 -3
- package/dist/toc/use-active-heading.d.ts +4 -7
- package/dist/transitions/index.d.ts +1 -1
- package/dist/transitions/page-events.d.ts +4 -6
- package/dist/tree-nav-shared/index.d.ts +22 -0
- package/dist/tree-nav-shared/index.js +66 -0
- package/dist/url-helpers/index.d.ts +37 -0
- package/dist/url-helpers/index.js +106 -0
- package/dist/url-normalizer/index.d.ts +39 -1
- package/dist/url-normalizer/normalize.d.ts +3 -5
- package/dist/use-modal-dialog/index.d.ts +70 -0
- package/dist/use-modal-dialog/index.js +73 -0
- package/dist/versions-page/index.d.ts +55 -0
- package/dist/versions-page/index.js +73 -0
- package/eject/breadcrumb/breadcrumb.tsx +194 -0
- package/eject/breadcrumb/find-path.ts +26 -0
- package/eject/breadcrumb/index.ts +4 -0
- package/eject/breadcrumb/types.ts +32 -0
- package/eject/code-group/index.tsx +78 -0
- package/eject/content-admonition/index.tsx +50 -0
- package/eject/details/details.tsx +34 -0
- package/eject/details/index.ts +4 -0
- package/eject/doc-pager/index.tsx +91 -0
- package/eject/footer/footer.tsx +167 -0
- package/eject/footer/index.ts +19 -0
- package/eject/footer/types.ts +60 -0
- package/eject/header/header.tsx +725 -0
- package/eject/header/index.ts +36 -0
- package/eject/header/nav-active.ts +122 -0
- package/eject/header/nav-overflow-script.ts +302 -0
- package/eject/header/right-items.ts +78 -0
- package/eject/header/types.ts +69 -0
- package/eject/page-loading/index.ts +10 -0
- package/eject/page-loading/page-loading-overlay.tsx +103 -0
- package/eject/sidebar/index.ts +27 -0
- package/eject/sidebar/sidebar.tsx +137 -0
- package/eject/sidebar/types.ts +73 -0
- package/eject/tab-item/index.ts +7 -0
- package/eject/tab-item/tab-item.tsx +70 -0
- package/eject/theme-toggle/color-scheme-sync.ts +73 -0
- package/eject/theme-toggle/index.tsx +120 -0
- package/eject/toc/cx.ts +38 -0
- package/eject/toc/index.ts +11 -0
- package/eject/toc/mobile-toc.tsx +133 -0
- package/eject/toc/toc-title.ts +33 -0
- package/eject/toc/toc.tsx +119 -0
- package/eject/toc/types.ts +14 -0
- package/eject/toc/use-active-heading.ts +162 -0
- package/package.json +371 -11
- package/routes-src/404.tsx +49 -0
- package/routes-src/_chrome.tsx +470 -0
- package/routes-src/_context.ts +314 -0
- package/routes-src/_docs-helpers.ts +284 -0
- package/routes-src/_virtual.d.ts +15 -0
- package/routes-src/api-ai-chat.tsx +46 -0
- package/routes-src/docs-slug.tsx +43 -0
- package/routes-src/docs-tags-index.tsx +14 -0
- package/routes-src/docs-tags-tag.tsx +29 -0
- package/routes-src/docs-versions.tsx +15 -0
- package/routes-src/index.tsx +134 -0
- package/routes-src/locale-docs-slug.tsx +69 -0
- package/routes-src/locale-docs-tags-index.tsx +23 -0
- package/routes-src/locale-docs-tags-tag.tsx +39 -0
- package/routes-src/locale-docs-versions.tsx +23 -0
- package/routes-src/locale-index.tsx +150 -0
- package/routes-src/robots.txt.tsx +13 -0
- package/routes-src/sitemap.xml.tsx +48 -0
- package/routes-src/v-docs-slug.tsx +64 -0
- package/routes-src/v-locale-docs-slug.tsx +85 -0
- package/dist/toc/smart-break.d.ts +0 -27
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/** @jsxRuntime automatic */
|
|
4
|
+
/** @jsxImportSource preact */
|
|
5
|
+
|
|
6
|
+
import type { VNode } from "preact";
|
|
7
|
+
import { useMemo, useState } from "preact/hooks";
|
|
8
|
+
|
|
9
|
+
import type { HeadingItem } from "./types.js";
|
|
10
|
+
import { SmartBreak } from "../smart-break/index.js";
|
|
11
|
+
import { cx } from "./cx.js";
|
|
12
|
+
|
|
13
|
+
export interface MobileTocProps {
|
|
14
|
+
headings: readonly HeadingItem[];
|
|
15
|
+
title?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Collapsible TOC for narrow viewports (`xl:hidden`) — a Preact island
|
|
20
|
+
* component. Closed by default; tapping the header toggles, and tapping
|
|
21
|
+
* any entry closes the panel after navigation.
|
|
22
|
+
*
|
|
23
|
+
* Renders the `<div class="xl:hidden …">` panel directly. **The caller
|
|
24
|
+
* is responsible for wrapping this in `<Island when="load">`** so the
|
|
25
|
+
* SSG output emits the `data-zfb-island="MobileToc"` hydration marker
|
|
26
|
+
* around the panel. `<DocLayoutWithDefaults>` does this for you;
|
|
27
|
+
* consumers who render `<MobileToc>` outside the default layout (e.g.
|
|
28
|
+
* via the `mobileTocOverride` prop or in a custom layout) must apply
|
|
29
|
+
* the wrapper themselves — otherwise the open/close toggle never
|
|
30
|
+
* hydrates on the client and the panel stays in its initial closed
|
|
31
|
+
* state.
|
|
32
|
+
*
|
|
33
|
+
* Wave 13 (zudolab/zudo-doc#1355): previously this module exported a
|
|
34
|
+
* `MobileToc` wrapper that called `Island(...)` itself; on hydration
|
|
35
|
+
* the runtime ran `hydrate(<MobileToc/>, dataIslandDiv)` and the
|
|
36
|
+
* wrapper re-emitted *another* `<div data-zfb-island="MobileToc">…</div>`
|
|
37
|
+
* inside the existing one, leaving two `xl:hidden …` panels in the
|
|
38
|
+
* post-hydration DOM. See `./toc.tsx` for the full diagnosis. Moving
|
|
39
|
+
* the `<Island>` wrapper to the call site lets the bundle hydrate the
|
|
40
|
+
* bare panel against the existing DOM in-place.
|
|
41
|
+
*
|
|
42
|
+
* Like `Toc`, this always includes the `title` text in the SSG HTML
|
|
43
|
+
* even when no headings qualify. When no headings are present, a
|
|
44
|
+
* CSS-hidden container carries the title string so no-JS users and
|
|
45
|
+
* crawlers can still find the locale label in the static markup. When
|
|
46
|
+
* headings are present the full interactive toggle UI is rendered.
|
|
47
|
+
*/
|
|
48
|
+
export function MobileToc({
|
|
49
|
+
headings,
|
|
50
|
+
title = "On this page",
|
|
51
|
+
}: MobileTocProps): VNode {
|
|
52
|
+
const filtered = useMemo(
|
|
53
|
+
() => headings.filter((h) => h.depth >= 2 && h.depth <= 4),
|
|
54
|
+
[headings],
|
|
55
|
+
);
|
|
56
|
+
const [open, setOpen] = useState(false);
|
|
57
|
+
|
|
58
|
+
// No qualifying headings: emit a CSS-hidden container that still carries
|
|
59
|
+
// the locale title text. The `hidden` class sets display:none visually,
|
|
60
|
+
// but the text node remains in the serialized HTML so no-JS users and
|
|
61
|
+
// crawlers can still see the locale label ("On this page" / "目次").
|
|
62
|
+
// aria-hidden prevents screen readers from announcing the invisible label.
|
|
63
|
+
if (filtered.length === 0) {
|
|
64
|
+
return (
|
|
65
|
+
<div className="hidden" aria-hidden="true">
|
|
66
|
+
{title}
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<div className="xl:hidden border border-muted mb-vsp-lg">
|
|
73
|
+
<button
|
|
74
|
+
type="button"
|
|
75
|
+
onClick={() => setOpen((prev) => !prev)}
|
|
76
|
+
aria-expanded={open}
|
|
77
|
+
className="flex w-full items-center justify-between px-hsp-lg py-vsp-xs text-small font-medium text-fg"
|
|
78
|
+
>
|
|
79
|
+
<span>{title}</span>
|
|
80
|
+
<svg
|
|
81
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
82
|
+
aria-hidden="true"
|
|
83
|
+
className={cx(
|
|
84
|
+
"h-icon-sm w-icon-sm text-muted transition-transform duration-150",
|
|
85
|
+
open && "rotate-180",
|
|
86
|
+
)}
|
|
87
|
+
fill="none"
|
|
88
|
+
viewBox="0 0 24 24"
|
|
89
|
+
stroke="currentColor"
|
|
90
|
+
strokeWidth={2}
|
|
91
|
+
>
|
|
92
|
+
<path
|
|
93
|
+
strokeLinecap="round"
|
|
94
|
+
strokeLinejoin="round"
|
|
95
|
+
d="M19 9l-7 7-7-7"
|
|
96
|
+
/>
|
|
97
|
+
</svg>
|
|
98
|
+
</button>
|
|
99
|
+
{/* Items list is always in the SSG HTML so anchor links are visible to
|
|
100
|
+
crawlers and JS-off users. Visibility is toggled by the `hidden` CSS
|
|
101
|
+
class — when `open` is false the list is display:none but the <a>
|
|
102
|
+
elements remain in the static markup, satisfying the a11y requirement
|
|
103
|
+
for keyboard accessibility after hydration. */}
|
|
104
|
+
<ul
|
|
105
|
+
className={cx(
|
|
106
|
+
"border-t border-muted px-hsp-lg py-vsp-xs space-y-vsp-2xs",
|
|
107
|
+
!open && "hidden",
|
|
108
|
+
)}
|
|
109
|
+
aria-hidden={!open}
|
|
110
|
+
>
|
|
111
|
+
{filtered.map((heading, index) => (
|
|
112
|
+
<li
|
|
113
|
+
key={`${heading.slug}-${index}`}
|
|
114
|
+
className={cx(
|
|
115
|
+
heading.depth === 3 && "ml-hsp-lg",
|
|
116
|
+
heading.depth === 4 && "ml-hsp-2xl",
|
|
117
|
+
)}
|
|
118
|
+
>
|
|
119
|
+
<a
|
|
120
|
+
href={`#${heading.slug}`}
|
|
121
|
+
onClick={() => setOpen(false)}
|
|
122
|
+
className="block py-vsp-2xs text-small text-muted hover:text-fg hover:underline focus-visible:underline"
|
|
123
|
+
>
|
|
124
|
+
<SmartBreak>{heading.text}</SmartBreak>
|
|
125
|
+
</a>
|
|
126
|
+
</li>
|
|
127
|
+
))}
|
|
128
|
+
</ul>
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
// See `./toc.tsx` for rationale on the explicit displayName pin.
|
|
133
|
+
MobileToc.displayName = "MobileToc";
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locale-to-TOC-title mapping for the v2 package.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the `toc.title` key values from `src/config/i18n.ts` (and the
|
|
5
|
+
* create-zudo-doc template equivalent). This copy lives inside the v2
|
|
6
|
+
* package so `DocLayoutWithDefaults` can derive the correct default title
|
|
7
|
+
* from the `lang` HTML attribute without importing the host project's i18n
|
|
8
|
+
* module — keeping the package self-contained and host-agnostic.
|
|
9
|
+
*
|
|
10
|
+
* When adding a new locale to zudo-doc's supported set, add the
|
|
11
|
+
* corresponding entry here too.
|
|
12
|
+
*/
|
|
13
|
+
const TOC_TITLES: Record<string, string> = {
|
|
14
|
+
en: "On this page",
|
|
15
|
+
ja: "目次",
|
|
16
|
+
de: "Auf dieser Seite",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const DEFAULT_TOC_TITLE = "On this page";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Return the TOC section label for the given BCP-47 language tag.
|
|
23
|
+
*
|
|
24
|
+
* Accepts full tags ("en-US", "ja-JP") by checking the primary subtag
|
|
25
|
+
* first, then falls back to the full tag, then falls back to the English
|
|
26
|
+
* default so callers always receive a non-empty string.
|
|
27
|
+
*/
|
|
28
|
+
export function getTocTitle(lang?: string): string {
|
|
29
|
+
if (!lang) return DEFAULT_TOC_TITLE;
|
|
30
|
+
// "en-US" → try "en" first, then "en-US", then fallback
|
|
31
|
+
const primary = lang.split("-")[0]!;
|
|
32
|
+
return TOC_TITLES[primary] ?? TOC_TITLES[lang] ?? DEFAULT_TOC_TITLE;
|
|
33
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/** @jsxRuntime automatic */
|
|
4
|
+
/** @jsxImportSource preact */
|
|
5
|
+
|
|
6
|
+
import type { VNode } from "preact";
|
|
7
|
+
import { useMemo } from "preact/hooks";
|
|
8
|
+
|
|
9
|
+
import { useActiveHeading } from "./use-active-heading.js";
|
|
10
|
+
import type { HeadingItem } from "./types.js";
|
|
11
|
+
import { SmartBreak } from "../smart-break/index.js";
|
|
12
|
+
import { cx } from "./cx.js";
|
|
13
|
+
|
|
14
|
+
export interface TocProps {
|
|
15
|
+
headings: readonly HeadingItem[];
|
|
16
|
+
/**
|
|
17
|
+
* Section label used by MobileToc. The desktop Toc no longer renders
|
|
18
|
+
* a visible heading (issue #1655 / T1) but the prop is kept so callers
|
|
19
|
+
* can share a single title value for both desktop and mobile surfaces.
|
|
20
|
+
* Defaults to "On this page" (English).
|
|
21
|
+
*/
|
|
22
|
+
title?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Desktop right-rail Table of Contents — a Preact island component.
|
|
27
|
+
*
|
|
28
|
+
* Renders the `<nav aria-label="Table of contents">` directly. **The
|
|
29
|
+
* caller is responsible for wrapping this in `<Island when="load">`**
|
|
30
|
+
* so the SSG output emits the `data-zfb-island="Toc"` hydration marker
|
|
31
|
+
* around the nav. `<DocLayoutWithDefaults>` does this for you; consumers
|
|
32
|
+
* who render `<Toc>` outside the default layout (e.g. via the
|
|
33
|
+
* `tocOverride` prop or in a custom layout) must apply the wrapper
|
|
34
|
+
* themselves — otherwise the active-section scroll-spy never hydrates
|
|
35
|
+
* on the client and the nav stays static.
|
|
36
|
+
*
|
|
37
|
+
* Wave 13 ("smoke-toc duplicate-nav regression", zudolab/zudo-doc#1355):
|
|
38
|
+
* before this refactor, this module exported a `Toc` wrapper that itself
|
|
39
|
+
* called `Island(...)` internally and an inner `TocInner` component that
|
|
40
|
+
* carried `displayName = "Toc"`. The zfb island scanner picked the
|
|
41
|
+
* exported `Toc` (the wrapper) as the hydration target, so on the
|
|
42
|
+
* client `mountIslands` ran `hydrate(<Toc/>, dataIslandDiv)` where the
|
|
43
|
+
* vnode itself rendered to *another* `<div data-zfb-island="Toc">…<nav>…</nav></div>`
|
|
44
|
+
* — Preact appended the new wrapper-div as a child of the existing
|
|
45
|
+
* data-zfb-island element instead of in-place hydrating the inner nav,
|
|
46
|
+
* leaving two `<nav aria-label="Table of contents">` elements in the
|
|
47
|
+
* post-hydration DOM. The host's Wave 12 prop-serialisation pin
|
|
48
|
+
* (`data-props`) made hydration succeed where it previously crashed
|
|
49
|
+
* silently on the missing `headings` prop, which is why the duplicate
|
|
50
|
+
* only became visible in CI smoke runs after Wave 12. Moving the
|
|
51
|
+
* `<Island>` wrapper to the call site lets the bundle hydrate the bare
|
|
52
|
+
* `<nav>` against the existing DOM in-place, eliminating the duplicate.
|
|
53
|
+
*
|
|
54
|
+
* Renders only depth 2–4 headings (h2/h3/h4), matching zudo-doc's
|
|
55
|
+
* historical behavior — the page title (h1) is rendered separately by
|
|
56
|
+
* the layout and h5+ are deemed too granular for the TOC.
|
|
57
|
+
*
|
|
58
|
+
* The `title` prop is kept on the signature (MobileToc and callers still
|
|
59
|
+
* use it) but the desktop Toc no longer renders a visible heading — the
|
|
60
|
+
* `<nav aria-label="Table of contents">` landmark provides the section
|
|
61
|
+
* semantics for screen readers. Issue #1655 / T1.
|
|
62
|
+
*/
|
|
63
|
+
export function Toc({ headings, title = "On this page" }: TocProps): VNode {
|
|
64
|
+
const filtered = useMemo(
|
|
65
|
+
() => headings.filter((h) => h.depth >= 2 && h.depth <= 4),
|
|
66
|
+
[headings],
|
|
67
|
+
);
|
|
68
|
+
const { activeId, activate } = useActiveHeading(filtered);
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<nav
|
|
72
|
+
aria-label="Table of contents"
|
|
73
|
+
className={cx(
|
|
74
|
+
"hidden xl:flex flex-col",
|
|
75
|
+
"w-[280px] shrink-0",
|
|
76
|
+
"sticky top-[3.5rem] self-start z-sidebar",
|
|
77
|
+
"pt-vsp-xl lg:pt-vsp-2xl",
|
|
78
|
+
"h-[calc(100vh-3.5rem)]",
|
|
79
|
+
)}
|
|
80
|
+
>
|
|
81
|
+
{filtered.length > 0 && (
|
|
82
|
+
<ul className="border-l border-muted pl-hsp-lg overflow-y-auto">
|
|
83
|
+
{filtered.map((heading, index) => {
|
|
84
|
+
const isActive = heading.slug === activeId;
|
|
85
|
+
return (
|
|
86
|
+
<li
|
|
87
|
+
key={`${heading.slug}-${index}`}
|
|
88
|
+
className={cx(
|
|
89
|
+
heading.depth === 3 && "ml-hsp-lg",
|
|
90
|
+
heading.depth === 4 && "ml-hsp-2xl",
|
|
91
|
+
)}
|
|
92
|
+
>
|
|
93
|
+
<a
|
|
94
|
+
href={`#${heading.slug}`}
|
|
95
|
+
onClick={() => activate(heading.slug)}
|
|
96
|
+
aria-current={isActive ? "true" : undefined}
|
|
97
|
+
className={cx(
|
|
98
|
+
"block py-vsp-2xs text-small leading-snug transition-colors",
|
|
99
|
+
isActive
|
|
100
|
+
? "bg-fg text-bg font-medium"
|
|
101
|
+
: "text-muted hover:underline focus:underline",
|
|
102
|
+
)}
|
|
103
|
+
>
|
|
104
|
+
<SmartBreak>{heading.text}</SmartBreak>
|
|
105
|
+
</a>
|
|
106
|
+
</li>
|
|
107
|
+
);
|
|
108
|
+
})}
|
|
109
|
+
</ul>
|
|
110
|
+
)}
|
|
111
|
+
</nav>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
// Pin the marker name to "Toc" explicitly so the SSR pass `<Island>` wrapper
|
|
115
|
+
// resolves a stable component identity even after esbuild minification has
|
|
116
|
+
// renamed the function. captureComponentName reads displayName first, then
|
|
117
|
+
// falls back to function.name; setting both means the SSR marker stays
|
|
118
|
+
// "Toc" no matter which path the runtime takes.
|
|
119
|
+
Toc.displayName = "Toc";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Heading metadata consumed by the TOC primitives.
|
|
3
|
+
*
|
|
4
|
+
* Shape mirrors the `headings` array exported from MDX modules by the
|
|
5
|
+
* underlying engine — `depth` (1–6), DOM `slug` (matching the rendered
|
|
6
|
+
* heading element's id), and the rendered `text`. Keep field names and
|
|
7
|
+
* types byte-aligned with the engine so a page module's `headings`
|
|
8
|
+
* export drops in directly.
|
|
9
|
+
*/
|
|
10
|
+
export interface HeadingItem {
|
|
11
|
+
readonly depth: number;
|
|
12
|
+
readonly slug: string;
|
|
13
|
+
readonly text: string;
|
|
14
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
|
|
2
|
+
import type { HeadingItem } from "./types.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Pixel offset from the viewport top used as the "above the fold"
|
|
6
|
+
* threshold. A heading whose top is above this line is treated as
|
|
7
|
+
* having scrolled past — matches the original zudo-doc behavior so the
|
|
8
|
+
* scroll-spy keeps the same feel after the migration.
|
|
9
|
+
*/
|
|
10
|
+
const SCROLL_MARGIN_TOP = 80;
|
|
11
|
+
const DEBOUNCE_MS = 200;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Pure helper that picks the active heading id given the heading slug
|
|
15
|
+
* order and a slug→element map. Extracted from the hook so it is unit-
|
|
16
|
+
* testable without DOM event plumbing.
|
|
17
|
+
*
|
|
18
|
+
* Algorithm: find the first heading whose top is at or below the
|
|
19
|
+
* SCROLL_MARGIN_TOP threshold ("first visible"). If none, the user has
|
|
20
|
+
* scrolled past every heading — return the last one. If the very first
|
|
21
|
+
* heading is the first visible AND its top is above the viewport
|
|
22
|
+
* midline, treat it as already active. Otherwise activate the
|
|
23
|
+
* predecessor of the first visible heading.
|
|
24
|
+
*/
|
|
25
|
+
export function getActiveHeadingId(
|
|
26
|
+
headingIds: readonly string[],
|
|
27
|
+
elementMap: ReadonlyMap<string, HTMLElement>,
|
|
28
|
+
viewportHeight: number,
|
|
29
|
+
): string | null {
|
|
30
|
+
if (headingIds.length === 0) return null;
|
|
31
|
+
|
|
32
|
+
let firstVisibleIndex = -1;
|
|
33
|
+
for (let i = 0; i < headingIds.length; i++) {
|
|
34
|
+
const el = elementMap.get(headingIds[i]!);
|
|
35
|
+
if (!el) continue;
|
|
36
|
+
const { top } = el.getBoundingClientRect();
|
|
37
|
+
if (top >= SCROLL_MARGIN_TOP) {
|
|
38
|
+
firstVisibleIndex = i;
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (firstVisibleIndex === -1) {
|
|
44
|
+
return headingIds[headingIds.length - 1] ?? null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (firstVisibleIndex === 0) {
|
|
48
|
+
const el = elementMap.get(headingIds[0]!);
|
|
49
|
+
if (el) {
|
|
50
|
+
const { top } = el.getBoundingClientRect();
|
|
51
|
+
if (top < viewportHeight / 2) {
|
|
52
|
+
return headingIds[0] ?? null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const el = elementMap.get(headingIds[firstVisibleIndex]!);
|
|
59
|
+
if (el) {
|
|
60
|
+
const { top } = el.getBoundingClientRect();
|
|
61
|
+
if (top < viewportHeight / 2) {
|
|
62
|
+
return headingIds[firstVisibleIndex] ?? null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return headingIds[firstVisibleIndex - 1] ?? null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface UseActiveHeadingResult {
|
|
70
|
+
activeId: string | null;
|
|
71
|
+
activate: (id: string) => void;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Scroll-spy hook. Tracks which heading slug should be highlighted
|
|
76
|
+
* based on the current scroll position. Provides an `activate(id)`
|
|
77
|
+
* imperatively-callable helper used by click handlers — sets the
|
|
78
|
+
* active id immediately and suppresses the scroll-driven update until
|
|
79
|
+
* the smooth-scroll completes (`scrollend`, with a timeout fallback
|
|
80
|
+
* for older Safari).
|
|
81
|
+
*/
|
|
82
|
+
export function useActiveHeading(
|
|
83
|
+
headings: readonly HeadingItem[],
|
|
84
|
+
): UseActiveHeadingResult {
|
|
85
|
+
const [activeId, setActiveId] = useState<string | null>(null);
|
|
86
|
+
const headingIdsRef = useRef<readonly string[]>([]);
|
|
87
|
+
const elementMapRef = useRef<Map<string, HTMLElement>>(new Map());
|
|
88
|
+
const suppressedRef = useRef(false);
|
|
89
|
+
|
|
90
|
+
const activate = useCallback((id: string) => {
|
|
91
|
+
setActiveId(id);
|
|
92
|
+
suppressedRef.current = true;
|
|
93
|
+
// Safety timeout: unsuppress if no scroll event fires (target already in view)
|
|
94
|
+
setTimeout(() => {
|
|
95
|
+
suppressedRef.current = false;
|
|
96
|
+
}, 2000);
|
|
97
|
+
}, []);
|
|
98
|
+
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
const ids = headings.map((h) => h.slug);
|
|
101
|
+
headingIdsRef.current = ids;
|
|
102
|
+
|
|
103
|
+
const map = new Map<string, HTMLElement>();
|
|
104
|
+
for (const id of ids) {
|
|
105
|
+
const el = document.getElementById(id);
|
|
106
|
+
if (el) map.set(id, el);
|
|
107
|
+
}
|
|
108
|
+
elementMapRef.current = map;
|
|
109
|
+
|
|
110
|
+
let timerId: ReturnType<typeof setTimeout> | null = null;
|
|
111
|
+
|
|
112
|
+
function update() {
|
|
113
|
+
timerId = null;
|
|
114
|
+
setActiveId(
|
|
115
|
+
getActiveHeadingId(
|
|
116
|
+
headingIdsRef.current,
|
|
117
|
+
elementMapRef.current,
|
|
118
|
+
window.innerHeight,
|
|
119
|
+
),
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let fallbackTimerId: ReturnType<typeof setTimeout> | null = null;
|
|
124
|
+
|
|
125
|
+
function onScroll() {
|
|
126
|
+
if (suppressedRef.current) {
|
|
127
|
+
// Fallback for browsers without scrollend (Safari < 18)
|
|
128
|
+
if (fallbackTimerId !== null) clearTimeout(fallbackTimerId);
|
|
129
|
+
fallbackTimerId = setTimeout(onScrollEnd, 1500);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (timerId !== null) clearTimeout(timerId);
|
|
133
|
+
timerId = setTimeout(update, DEBOUNCE_MS);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function onScrollEnd() {
|
|
137
|
+
suppressedRef.current = false;
|
|
138
|
+
if (fallbackTimerId !== null) {
|
|
139
|
+
clearTimeout(fallbackTimerId);
|
|
140
|
+
fallbackTimerId = null;
|
|
141
|
+
}
|
|
142
|
+
if (timerId !== null) clearTimeout(timerId);
|
|
143
|
+
update();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
update();
|
|
147
|
+
|
|
148
|
+
window.addEventListener("scroll", onScroll, { passive: true });
|
|
149
|
+
window.addEventListener("resize", onScroll, { passive: true });
|
|
150
|
+
window.addEventListener("scrollend", onScrollEnd, { passive: true });
|
|
151
|
+
|
|
152
|
+
return () => {
|
|
153
|
+
window.removeEventListener("scroll", onScroll);
|
|
154
|
+
window.removeEventListener("resize", onScroll);
|
|
155
|
+
window.removeEventListener("scrollend", onScrollEnd);
|
|
156
|
+
if (timerId !== null) clearTimeout(timerId);
|
|
157
|
+
if (fallbackTimerId !== null) clearTimeout(fallbackTimerId);
|
|
158
|
+
};
|
|
159
|
+
}, [headings]);
|
|
160
|
+
|
|
161
|
+
return { activeId, activate };
|
|
162
|
+
}
|