@waveso/docs 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +155 -1
- package/README.md +114 -37
- package/dist/frontmatter.d.ts +8 -0
- package/dist/frontmatter.js +7 -1
- package/dist/next.d.ts +34 -27
- package/dist/next.js +20 -11
- package/dist/react/hero.d.ts +19 -0
- package/dist/react/hero.js +44 -0
- package/dist/react/layout.d.ts +1 -3
- package/dist/react/layout.js +17 -54
- package/dist/react/nav.d.ts +11 -11
- package/dist/react/nav.js +106 -58
- package/dist/react/next-nav.d.ts +5 -1
- package/dist/react/next-nav.js +4 -2
- package/dist/react/shell-labels.d.ts +8 -2
- package/dist/react/toc.d.ts +13 -3
- package/dist/react/toc.js +17 -10
- package/dist/render.js +2 -1
- package/dist/source.js +1 -1
- package/dist/styles.css +1079 -237
- package/dist/types.d.ts +31 -1
- package/package.json +5 -1
package/dist/next.js
CHANGED
|
@@ -6,6 +6,7 @@ import { mapPooled } from "./map-pooled.js";
|
|
|
6
6
|
import { findFunctionValuedOptions } from "./search-options.js";
|
|
7
7
|
import { createMarkdownComponents } from "./react/markdown-components.js";
|
|
8
8
|
import { DocContent } from "./react/doc-content.js";
|
|
9
|
+
import { DocsHero } from "./react/hero.js";
|
|
9
10
|
import { DocsToc } from "./react/toc.js";
|
|
10
11
|
import { wrapNextLink } from "./react/link-adapter.js";
|
|
11
12
|
import { createDocsRenderer } from "./render.js";
|
|
@@ -172,11 +173,15 @@ async function buildNextComponents(labels) {
|
|
|
172
173
|
const [linkMod, imageMod] = await Promise.all([importNext(() => import("next/link"), "next/link"), importNext(() => import("next/image"), "next/image")]);
|
|
173
174
|
const NextLink = readDefaultExport(linkMod, "next/link");
|
|
174
175
|
const NextImage = readDefaultExport(imageMod, "next/image");
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
176
|
+
const link = wrapNextLink(NextLink);
|
|
177
|
+
return {
|
|
178
|
+
components: createMarkdownComponents({
|
|
179
|
+
Link: link,
|
|
180
|
+
Image: wrapNextImage(NextImage),
|
|
181
|
+
...labels === void 0 ? {} : { labels }
|
|
182
|
+
}),
|
|
183
|
+
link
|
|
184
|
+
};
|
|
180
185
|
}
|
|
181
186
|
/**
|
|
182
187
|
* The named subset of `labels`, or `undefined` when none of it is set.
|
|
@@ -220,7 +225,7 @@ function pickLabels(labels, map) {
|
|
|
220
225
|
*/
|
|
221
226
|
function serializableSearchOptions(candidate) {
|
|
222
227
|
const functions = findFunctionValuedOptions(candidate);
|
|
223
|
-
if (functions.length > 0) throw docsError("invalid-config", `the search dialog cannot be given MiniSearch functions from a server component: ${functions.map((name) => `\`miniSearchOptions.${name}\``).join(", ")}. \`docs.Layout\` renders the dialog as a client component, so its props are serialised on the way across and React rejects a function with "Functions cannot be passed directly to Client Components" while prerendering. Keep the function on \`createDocsRoute\` so the index is still built with it, pass \`search={false}\` to \`docs.Layout\`, and render the dialog yourself from a \`'use client'\` module that imports the same function — \`<DocsSearch indexUrl={docs.searchIndexUrl} miniSearchOptions={{ processTerm }} />\`
|
|
228
|
+
if (functions.length > 0) throw docsError("invalid-config", `the search dialog cannot be given MiniSearch functions from a server component: ${functions.map((name) => `\`miniSearchOptions.${name}\``).join(", ")}. \`docs.Layout\` renders the dialog as a client component, so its props are serialised on the way across and React rejects a function with "Functions cannot be passed directly to Client Components" while prerendering. Keep the function on \`createDocsRoute\` so the index is still built with it, pass \`search={false}\` to \`docs.Layout\`, and render the dialog yourself from a \`'use client'\` module that imports the same function — \`<DocsSearch indexUrl={docs.searchIndexUrl} miniSearchOptions={{ processTerm }} />\` in your own layout. Serialisable overrides (\`storeFields\`, \`boost\`, \`searchOptions.fuzzy\`) need none of this and are forwarded as before.`);
|
|
224
229
|
return candidate;
|
|
225
230
|
}
|
|
226
231
|
/**
|
|
@@ -425,12 +430,18 @@ function createDocsRoute(options) {
|
|
|
425
430
|
async function renderRoute(segments) {
|
|
426
431
|
const doc = await getPage(segments);
|
|
427
432
|
if (doc === void 0) return (await loadNotFound())();
|
|
428
|
-
const components = await loadComponents();
|
|
433
|
+
const { components, link } = await loadComponents();
|
|
429
434
|
return createElement(Fragment, null, createElement("main", {
|
|
430
435
|
className: "wave-docs-layout__main",
|
|
431
436
|
id: DOCS_CONTENT_ID,
|
|
432
437
|
tabIndex: -1
|
|
433
|
-
}, createElement(
|
|
438
|
+
}, (doc.frontmatter.actions?.length ?? 0) === 0 ? null : createElement(DocsHero, {
|
|
439
|
+
title: doc.frontmatter.title,
|
|
440
|
+
...doc.frontmatter.description === void 0 ? {} : { description: doc.frontmatter.description },
|
|
441
|
+
...doc.frontmatter.actions === void 0 ? {} : { actions: doc.frontmatter.actions },
|
|
442
|
+
Link: link,
|
|
443
|
+
...routeLabels?.externalLink === void 0 ? {} : { externalLabel: routeLabels.externalLink }
|
|
444
|
+
}), createElement(DocContent, {
|
|
434
445
|
hast: doc.hast,
|
|
435
446
|
components: {
|
|
436
447
|
...components,
|
|
@@ -457,7 +468,7 @@ function createDocsRoute(options) {
|
|
|
457
468
|
async IndexPage() {
|
|
458
469
|
return renderRoute([]);
|
|
459
470
|
},
|
|
460
|
-
async Layout({ children,
|
|
471
|
+
async Layout({ children, search, labels }) {
|
|
461
472
|
const { DocsLayoutShell } = await import("./react/layout.js");
|
|
462
473
|
const host = search === true || search === void 0 || search === false ? void 0 : search;
|
|
463
474
|
const requestedOptions = host?.miniSearchOptions ?? options.miniSearchOptions;
|
|
@@ -474,8 +485,6 @@ function createDocsRoute(options) {
|
|
|
474
485
|
nav: await requestScopedSource.nav(),
|
|
475
486
|
searchIndexUrl,
|
|
476
487
|
search: searchProps,
|
|
477
|
-
...title === void 0 ? {} : { title },
|
|
478
|
-
...actions === void 0 ? {} : { actions },
|
|
479
488
|
...shellLabels === void 0 ? {} : { labels: shellLabels }
|
|
480
489
|
});
|
|
481
490
|
},
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { DocAction } from "../types.js";
|
|
2
|
+
import { DocsLinkComponent } from "./markdown-components.js";
|
|
3
|
+
import { ReactNode } from "react";
|
|
4
|
+
//#region src/react/hero.d.ts
|
|
5
|
+
interface DocsHeroProps {
|
|
6
|
+
/** `frontmatter.title`, rendered as the page's `<h1>`. */
|
|
7
|
+
title: string;
|
|
8
|
+
/** `frontmatter.description`. Omitted rather than rendered empty. */
|
|
9
|
+
description?: string | undefined;
|
|
10
|
+
/** `frontmatter.actions`. An empty list renders no `<nav>` at all. */
|
|
11
|
+
actions?: readonly DocAction[] | undefined;
|
|
12
|
+
/** Client-side router link, e.g. `next/link`. Falls back to `<a>`. */
|
|
13
|
+
Link?: DocsLinkComponent | undefined;
|
|
14
|
+
/** Screen-reader suffix on a link that opens elsewhere. */
|
|
15
|
+
externalLabel?: string | undefined;
|
|
16
|
+
}
|
|
17
|
+
declare function DocsHero({ title, description, actions, Link, externalLabel }: DocsHeroProps): ReactNode;
|
|
18
|
+
//#endregion
|
|
19
|
+
export { DocsHero, DocsHeroProps };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { opensInNewTab } from "../safe-href.js";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
//#region src/react/hero.tsx
|
|
4
|
+
function DocsHero({ title, description, actions, Link, externalLabel = "(opens in a new tab)" }) {
|
|
5
|
+
const Anchor = Link ?? "a";
|
|
6
|
+
return /* @__PURE__ */ jsx("header", {
|
|
7
|
+
className: "wave-docs-hero",
|
|
8
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
9
|
+
className: "wave-docs-hero__body",
|
|
10
|
+
children: [
|
|
11
|
+
/* @__PURE__ */ jsx("h1", {
|
|
12
|
+
className: "wave-docs-hero__title",
|
|
13
|
+
children: title
|
|
14
|
+
}),
|
|
15
|
+
description === void 0 || description === "" ? null : /* @__PURE__ */ jsx("p", {
|
|
16
|
+
className: "wave-docs-hero__tagline",
|
|
17
|
+
children: description
|
|
18
|
+
}),
|
|
19
|
+
actions === void 0 || actions.length === 0 ? null : /* @__PURE__ */ jsx("div", {
|
|
20
|
+
className: "wave-docs-hero__actions",
|
|
21
|
+
children: actions.map((action, index) => {
|
|
22
|
+
const external = opensInNewTab(action.href);
|
|
23
|
+
const variant = action.variant ?? (index === 0 ? "primary" : "secondary");
|
|
24
|
+
return /* @__PURE__ */ jsxs(external ? "a" : Anchor, {
|
|
25
|
+
href: action.href,
|
|
26
|
+
className: "wave-docs-hero__action",
|
|
27
|
+
"data-variant": variant,
|
|
28
|
+
...external ? {
|
|
29
|
+
target: "_blank",
|
|
30
|
+
rel: "noreferrer"
|
|
31
|
+
} : {},
|
|
32
|
+
children: [action.label, external ? /* @__PURE__ */ jsx("span", {
|
|
33
|
+
className: "wave-docs-sr-only",
|
|
34
|
+
children: ` ${externalLabel}`
|
|
35
|
+
}) : null]
|
|
36
|
+
}, action.href);
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
]
|
|
40
|
+
})
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
export { DocsHero };
|
package/dist/react/layout.d.ts
CHANGED
|
@@ -27,8 +27,6 @@ interface DocsLayoutShellProps {
|
|
|
27
27
|
children: ReactNode;
|
|
28
28
|
nav: DocNavNode[];
|
|
29
29
|
searchIndexUrl: string;
|
|
30
|
-
title?: ReactNode;
|
|
31
|
-
actions?: ReactNode;
|
|
32
30
|
/**
|
|
33
31
|
* `false` to omit the trigger; an object to configure it.
|
|
34
32
|
*
|
|
@@ -56,6 +54,6 @@ interface DocsLayoutShellProps {
|
|
|
56
54
|
*/
|
|
57
55
|
labels?: DocsLabels | undefined;
|
|
58
56
|
}
|
|
59
|
-
declare function DocsLayoutShell({ children, nav, searchIndexUrl,
|
|
57
|
+
declare function DocsLayoutShell({ children, nav, searchIndexUrl, search, labels }: DocsLayoutShellProps): ReactNode;
|
|
60
58
|
//#endregion
|
|
61
59
|
export { DocsLayoutSearchProps, DocsLayoutShell, DocsLayoutShellProps };
|
package/dist/react/layout.js
CHANGED
|
@@ -1,68 +1,31 @@
|
|
|
1
1
|
import { DocsSearch } from "./next-search.js";
|
|
2
2
|
import { resolveLabels } from "./shell-labels.js";
|
|
3
|
-
import { DOCS_NAV_ID } from "./nav.js";
|
|
4
3
|
import { DocsNextNav } from "./next-nav.js";
|
|
5
4
|
import { SkipLink } from "./skip-link.js";
|
|
6
5
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
6
|
//#region src/react/layout.tsx
|
|
8
|
-
function DocsLayoutShell({ children, nav, searchIndexUrl,
|
|
7
|
+
function DocsLayoutShell({ children, nav, searchIndexUrl, search = true, labels }) {
|
|
9
8
|
const text = resolveLabels(labels);
|
|
10
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
11
|
-
|
|
12
|
-
/* @__PURE__ */
|
|
13
|
-
className: "wave-docs-layout__header",
|
|
14
|
-
children: /* @__PURE__ */ jsxs("div", {
|
|
15
|
-
className: "wave-docs-layout__header-inner",
|
|
16
|
-
children: [
|
|
17
|
-
/* @__PURE__ */ jsx("button", {
|
|
18
|
-
type: "button",
|
|
19
|
-
className: "wave-docs-layout__nav-trigger",
|
|
20
|
-
"aria-label": text.openNav,
|
|
21
|
-
command: "show-modal",
|
|
22
|
-
commandfor: DOCS_NAV_ID,
|
|
23
|
-
children: /* @__PURE__ */ jsx("svg", {
|
|
24
|
-
"aria-hidden": "true",
|
|
25
|
-
viewBox: "0 0 16 16",
|
|
26
|
-
width: "18",
|
|
27
|
-
height: "18",
|
|
28
|
-
fill: "none",
|
|
29
|
-
stroke: "currentColor",
|
|
30
|
-
strokeWidth: "1.5",
|
|
31
|
-
strokeLinecap: "round",
|
|
32
|
-
children: /* @__PURE__ */ jsx("path", { d: "M2.5 4h11M2.5 8h11M2.5 12h11" })
|
|
33
|
-
})
|
|
34
|
-
}),
|
|
35
|
-
title === void 0 ? null : /* @__PURE__ */ jsx("div", {
|
|
36
|
-
className: "wave-docs-layout__title",
|
|
37
|
-
children: title
|
|
38
|
-
}),
|
|
39
|
-
search === false ? null : /* @__PURE__ */ jsx(DocsSearch, {
|
|
40
|
-
indexUrl: searchIndexUrl,
|
|
41
|
-
...search === true ? {} : search,
|
|
42
|
-
className: ["wave-docs-layout__search", search === true ? void 0 : search?.className].filter(Boolean).join(" ")
|
|
43
|
-
}),
|
|
44
|
-
actions === void 0 ? null : /* @__PURE__ */ jsx("div", {
|
|
45
|
-
className: "wave-docs-layout__actions",
|
|
46
|
-
children: actions
|
|
47
|
-
})
|
|
48
|
-
]
|
|
49
|
-
})
|
|
50
|
-
}),
|
|
51
|
-
/* @__PURE__ */ jsxs("div", {
|
|
9
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(SkipLink, { children: text.skipToContent }), /* @__PURE__ */ jsx("div", {
|
|
10
|
+
className: "wave-docs-shell",
|
|
11
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
52
12
|
className: "wave-docs-layout",
|
|
53
|
-
children: [/* @__PURE__ */ jsx(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
13
|
+
children: [/* @__PURE__ */ jsx(DocsNextNav, {
|
|
14
|
+
nav,
|
|
15
|
+
label: text.nav,
|
|
16
|
+
closeLabel: text.closeNav,
|
|
17
|
+
openLabel: text.openNav,
|
|
18
|
+
...labels?.expandGroup === void 0 ? {} : { expandGroup: labels.expandGroup },
|
|
19
|
+
...labels?.collapseGroup === void 0 ? {} : { collapseGroup: labels.collapseGroup },
|
|
20
|
+
...labels?.externalLink === void 0 ? {} : { externalLink: labels.externalLink },
|
|
21
|
+
children: search === false ? null : /* @__PURE__ */ jsx(DocsSearch, {
|
|
22
|
+
indexUrl: searchIndexUrl,
|
|
23
|
+
...search === true ? {} : search,
|
|
24
|
+
className: ["wave-docs-layout__search", search === true ? void 0 : search?.className].filter(Boolean).join(" ")
|
|
62
25
|
})
|
|
63
26
|
}), children]
|
|
64
27
|
})
|
|
65
|
-
] });
|
|
28
|
+
})] });
|
|
66
29
|
}
|
|
67
30
|
//#endregion
|
|
68
31
|
export { DocsLayoutShell };
|
package/dist/react/nav.d.ts
CHANGED
|
@@ -2,14 +2,7 @@ import { DocNavNode } from "../types.js";
|
|
|
2
2
|
import { DocsLinkComponent } from "./markdown-components.js";
|
|
3
3
|
import { ReactNode } from "react";
|
|
4
4
|
//#region src/react/nav.d.ts
|
|
5
|
-
/**
|
|
6
|
-
* The drawer's `id`, and the header trigger's `commandfor`.
|
|
7
|
-
*
|
|
8
|
-
* A constant rather than a `useId`, for two reasons that both matter: the
|
|
9
|
-
* trigger is rendered on the server in a different subtree and cannot see a
|
|
10
|
-
* hook's value, and `command`/`commandfor` must agree before React hydrates or
|
|
11
|
-
* the button does nothing on the first tap.
|
|
12
|
-
*/
|
|
5
|
+
/** The navigation's `id`, and the trigger's `aria-controls`. */
|
|
13
6
|
declare const DOCS_NAV_ID = "wave-docs-nav";
|
|
14
7
|
interface DocsNavProps {
|
|
15
8
|
/** The tree from `docs.source.nav()`. */
|
|
@@ -18,15 +11,22 @@ interface DocsNavProps {
|
|
|
18
11
|
pathname: string;
|
|
19
12
|
/** Client-side router link, e.g. `next/link`. Falls back to `<a>`. */
|
|
20
13
|
Link?: DocsLinkComponent | undefined;
|
|
21
|
-
/** Accessible name for the nav landmark
|
|
14
|
+
/** Accessible name for the nav landmark. */
|
|
22
15
|
label?: string | undefined;
|
|
23
|
-
/** Accessible name for the
|
|
16
|
+
/** Accessible name for the trigger while the sidebar is open. */
|
|
24
17
|
closeLabel?: string | undefined;
|
|
18
|
+
/** Accessible name for the trigger while the sidebar is closed. */
|
|
19
|
+
openLabel?: string | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Rendered above the tree. The search trigger goes here — it belongs to the
|
|
22
|
+
* navigation, so it moves with it and is never a second thing to place.
|
|
23
|
+
*/
|
|
24
|
+
children?: ReactNode;
|
|
25
25
|
/** Passed through to the tree. See `DocsSidebarProps.expandGroup`. */
|
|
26
26
|
expandGroup?: string | undefined;
|
|
27
27
|
collapseGroup?: string | undefined;
|
|
28
28
|
externalLink?: string | undefined;
|
|
29
29
|
}
|
|
30
|
-
declare function DocsNav({ nav, pathname, Link, label, closeLabel, expandGroup, collapseGroup, externalLink }: DocsNavProps): ReactNode;
|
|
30
|
+
declare function DocsNav({ nav, pathname, Link, label, children, closeLabel, openLabel, expandGroup, collapseGroup, externalLink }: DocsNavProps): ReactNode;
|
|
31
31
|
//#endregion
|
|
32
32
|
export { DOCS_NAV_ID, DocsNav, DocsNavProps };
|
package/dist/react/nav.js
CHANGED
|
@@ -1,73 +1,121 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { DocsSidebar } from "./sidebar.js";
|
|
3
|
-
import { useEffect, useRef } from "react";
|
|
4
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
4
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
5
5
|
//#region src/react/nav.tsx
|
|
6
|
-
/**
|
|
7
|
-
* The drawer's `id`, and the header trigger's `commandfor`.
|
|
8
|
-
*
|
|
9
|
-
* A constant rather than a `useId`, for two reasons that both matter: the
|
|
10
|
-
* trigger is rendered on the server in a different subtree and cannot see a
|
|
11
|
-
* hook's value, and `command`/`commandfor` must agree before React hydrates or
|
|
12
|
-
* the button does nothing on the first tap.
|
|
13
|
-
*/
|
|
6
|
+
/** The navigation's `id`, and the trigger's `aria-controls`. */
|
|
14
7
|
const DOCS_NAV_ID = "wave-docs-nav";
|
|
15
|
-
function DocsNav({ nav, pathname, Link, label = "Documentation", closeLabel = "Close navigation", expandGroup, collapseGroup, externalLink }) {
|
|
16
|
-
const
|
|
8
|
+
function DocsNav({ nav, pathname, Link, label = "Documentation", children, closeLabel = "Close navigation", openLabel = "Open navigation", expandGroup, collapseGroup, externalLink }) {
|
|
9
|
+
const shellRef = useRef(null);
|
|
10
|
+
const navRef = useRef(null);
|
|
11
|
+
const returnFocusRef = useRef(null);
|
|
12
|
+
/**
|
|
13
|
+
* ⚠️ THREE STATES, AND THE THIRD ONE IS WHY THERE IS NO FLASH.
|
|
14
|
+
*
|
|
15
|
+
* `null` is "nobody has chosen yet". The server renders it, no `data-state`
|
|
16
|
+
* reaches the DOM, and the stylesheet decides per mode — closed where the
|
|
17
|
+
* navigation would cover the article, open where it would sit beside it. So
|
|
18
|
+
* the first paint is already right at both shapes, with no JavaScript and
|
|
19
|
+
* nothing to correct afterwards.
|
|
20
|
+
*
|
|
21
|
+
* A boolean would have to be picked before the container's width is known,
|
|
22
|
+
* which is either a flash on a phone or a flash on a desktop.
|
|
23
|
+
*/
|
|
24
|
+
const [choice, setChoice] = useState(null);
|
|
25
|
+
const [mode, setMode] = useState(null);
|
|
26
|
+
const [ready, setReady] = useState(false);
|
|
27
|
+
const open = choice ?? mode === "push";
|
|
28
|
+
const covering = mode === "cover" && open;
|
|
17
29
|
useEffect(() => {
|
|
18
|
-
|
|
30
|
+
const shell = shellRef.current;
|
|
31
|
+
const layout = shell?.parentElement;
|
|
32
|
+
if (shell == null || layout == null) return;
|
|
33
|
+
const read = () => {
|
|
34
|
+
const value = getComputedStyle(shell).getPropertyValue("--wave-docs-sidebar-mode").trim();
|
|
35
|
+
setMode(value === "push" ? "push" : "cover");
|
|
36
|
+
};
|
|
37
|
+
read();
|
|
38
|
+
const frame = requestAnimationFrame(() => {
|
|
39
|
+
setReady(true);
|
|
40
|
+
});
|
|
41
|
+
const observer = new ResizeObserver(read);
|
|
42
|
+
observer.observe(layout);
|
|
43
|
+
return () => {
|
|
44
|
+
cancelAnimationFrame(frame);
|
|
45
|
+
observer.disconnect();
|
|
46
|
+
};
|
|
47
|
+
}, []);
|
|
48
|
+
const close = useCallback(() => {
|
|
49
|
+
setChoice(false);
|
|
50
|
+
}, []);
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (covering) setChoice(false);
|
|
19
53
|
}, [pathname]);
|
|
20
54
|
useEffect(() => {
|
|
21
|
-
if (
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (button === null) return;
|
|
27
|
-
if (button.getAttribute("commandfor") !== "wave-docs-nav") return;
|
|
28
|
-
const dialog = ref.current;
|
|
29
|
-
if (dialog === null) return;
|
|
30
|
-
if (button.getAttribute("command") === "close") dialog.close();
|
|
31
|
-
else dialog.showModal();
|
|
55
|
+
if (!covering) return;
|
|
56
|
+
const onKeyDown = (event) => {
|
|
57
|
+
if (event.key !== "Escape") return;
|
|
58
|
+
event.stopPropagation();
|
|
59
|
+
close();
|
|
32
60
|
};
|
|
33
|
-
document.addEventListener("
|
|
61
|
+
document.addEventListener("keydown", onKeyDown);
|
|
34
62
|
return () => {
|
|
35
|
-
document.removeEventListener("
|
|
63
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
36
64
|
};
|
|
37
|
-
}, []);
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
65
|
+
}, [covering, close]);
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
const shell = shellRef.current;
|
|
68
|
+
const layout = shell?.parentElement;
|
|
69
|
+
if (shell == null || layout == null) return;
|
|
70
|
+
const outside = [...layout.children].filter((child) => child !== shell && !child.classList.contains(SCRIM_CLASS));
|
|
71
|
+
if (!covering) {
|
|
72
|
+
for (const element of outside) element.removeAttribute("inert");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
returnFocusRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
76
|
+
for (const element of outside) element.setAttribute("inert", "");
|
|
77
|
+
navRef.current?.focus();
|
|
78
|
+
return () => {
|
|
79
|
+
for (const element of outside) element.removeAttribute("inert");
|
|
80
|
+
returnFocusRef.current?.focus({ preventScroll: true });
|
|
81
|
+
};
|
|
82
|
+
}, [covering]);
|
|
83
|
+
const state = choice === null && mode === null ? void 0 : open;
|
|
84
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
|
|
85
|
+
ref: shellRef,
|
|
86
|
+
className: "wave-docs-layout__sidebar",
|
|
87
|
+
...state === void 0 ? {} : { "data-state": state ? "open" : "closed" },
|
|
88
|
+
...ready ? { "data-ready": "" } : {},
|
|
89
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
90
|
+
ref: navRef,
|
|
91
|
+
id: DOCS_NAV_ID,
|
|
92
|
+
className: "wave-docs-layout__sidebar-nav",
|
|
93
|
+
tabIndex: -1,
|
|
94
|
+
children: [children, /* @__PURE__ */ jsx(DocsSidebar, {
|
|
95
|
+
nav,
|
|
96
|
+
pathname,
|
|
97
|
+
label,
|
|
98
|
+
Link,
|
|
99
|
+
...expandGroup === void 0 ? {} : { expandGroup },
|
|
100
|
+
...collapseGroup === void 0 ? {} : { collapseGroup },
|
|
101
|
+
...externalLink === void 0 ? {} : { externalLink }
|
|
102
|
+
})]
|
|
103
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
45
104
|
type: "button",
|
|
46
|
-
className: "wave-docs-
|
|
47
|
-
"aria-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
width: "16",
|
|
54
|
-
height: "16",
|
|
55
|
-
fill: "none",
|
|
56
|
-
stroke: "currentColor",
|
|
57
|
-
strokeWidth: "1.5",
|
|
58
|
-
strokeLinecap: "round",
|
|
59
|
-
children: /* @__PURE__ */ jsx("path", { d: "M4 4l8 8M12 4l-8 8" })
|
|
60
|
-
})
|
|
61
|
-
}), /* @__PURE__ */ jsx(DocsSidebar, {
|
|
62
|
-
nav,
|
|
63
|
-
pathname,
|
|
64
|
-
label,
|
|
65
|
-
Link,
|
|
66
|
-
...expandGroup === void 0 ? {} : { expandGroup },
|
|
67
|
-
...collapseGroup === void 0 ? {} : { collapseGroup },
|
|
68
|
-
...externalLink === void 0 ? {} : { externalLink }
|
|
105
|
+
className: "wave-docs-layout__sidebar-trigger",
|
|
106
|
+
"aria-controls": DOCS_NAV_ID,
|
|
107
|
+
"aria-label": open ? closeLabel : openLabel,
|
|
108
|
+
...state === void 0 ? {} : { "aria-expanded": state },
|
|
109
|
+
onClick: () => {
|
|
110
|
+
setChoice(!open);
|
|
111
|
+
}
|
|
69
112
|
})]
|
|
70
|
-
})
|
|
113
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
114
|
+
className: SCRIM_CLASS,
|
|
115
|
+
"aria-hidden": "true",
|
|
116
|
+
onClick: close
|
|
117
|
+
})] });
|
|
71
118
|
}
|
|
119
|
+
const SCRIM_CLASS = "wave-docs-layout__sidebar-scrim";
|
|
72
120
|
//#endregion
|
|
73
121
|
export { DOCS_NAV_ID, DocsNav };
|
package/dist/react/next-nav.d.ts
CHANGED
|
@@ -5,11 +5,15 @@ interface DocsNextNavProps {
|
|
|
5
5
|
nav: DocNavNode[];
|
|
6
6
|
label?: string | undefined;
|
|
7
7
|
closeLabel?: string | undefined;
|
|
8
|
+
/** Accessible name for the trigger while the sidebar is closed. */
|
|
9
|
+
openLabel?: string | undefined;
|
|
10
|
+
/** Rendered above the tree. See `DocsNavProps.children`. */
|
|
11
|
+
children?: ReactNode;
|
|
8
12
|
/** Passed through to the tree. See `DocsSidebarProps.expandGroup`. */
|
|
9
13
|
expandGroup?: string | undefined;
|
|
10
14
|
collapseGroup?: string | undefined;
|
|
11
15
|
externalLink?: string | undefined;
|
|
12
16
|
}
|
|
13
|
-
declare function DocsNextNav({ nav, label, closeLabel, expandGroup, collapseGroup, externalLink }: DocsNextNavProps): ReactNode;
|
|
17
|
+
declare function DocsNextNav({ nav, label, closeLabel, openLabel, children, expandGroup, collapseGroup, externalLink }: DocsNextNavProps): ReactNode;
|
|
14
18
|
//#endregion
|
|
15
19
|
export { DocsNextNav, DocsNextNavProps };
|
package/dist/react/next-nav.js
CHANGED
|
@@ -19,16 +19,18 @@ import { usePathname } from "next/navigation";
|
|
|
19
19
|
*/
|
|
20
20
|
/** Module scope: a fresh identity here remounts every nav link on every render. */
|
|
21
21
|
const Link = wrapNextLink(NextLink);
|
|
22
|
-
function DocsNextNav({ nav, label, closeLabel, expandGroup, collapseGroup, externalLink }) {
|
|
22
|
+
function DocsNextNav({ nav, label, closeLabel, openLabel, children, expandGroup, collapseGroup, externalLink }) {
|
|
23
23
|
return /* @__PURE__ */ jsx(DocsNav, {
|
|
24
24
|
nav,
|
|
25
25
|
pathname: usePathname(),
|
|
26
26
|
Link,
|
|
27
27
|
...label === void 0 ? {} : { label },
|
|
28
28
|
...closeLabel === void 0 ? {} : { closeLabel },
|
|
29
|
+
...openLabel === void 0 ? {} : { openLabel },
|
|
29
30
|
...expandGroup === void 0 ? {} : { expandGroup },
|
|
30
31
|
...collapseGroup === void 0 ? {} : { collapseGroup },
|
|
31
|
-
...externalLink === void 0 ? {} : { externalLink }
|
|
32
|
+
...externalLink === void 0 ? {} : { externalLink },
|
|
33
|
+
children
|
|
32
34
|
});
|
|
33
35
|
}
|
|
34
36
|
//#endregion
|
|
@@ -44,9 +44,15 @@
|
|
|
44
44
|
interface DocsLabels {
|
|
45
45
|
/** The navigation landmark's accessible name. Default `'Documentation'`. */
|
|
46
46
|
nav?: string | undefined;
|
|
47
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* The sidebar's trigger, named for what pressing it does next. One button
|
|
49
|
+
* carries both strings: `openNav` while the sidebar is closed, `closeNav`
|
|
50
|
+
* while it is open.
|
|
51
|
+
*
|
|
52
|
+
* Default `'Open navigation'`.
|
|
53
|
+
*/
|
|
48
54
|
openNav?: string | undefined;
|
|
49
|
-
/** The button
|
|
55
|
+
/** The same button, while the sidebar is open. Default `'Close navigation'`. */
|
|
50
56
|
closeNav?: string | undefined;
|
|
51
57
|
/** The skip link's visible text. Default `'Skip to content'`. */
|
|
52
58
|
skipToContent?: string | undefined;
|
package/dist/react/toc.d.ts
CHANGED
|
@@ -8,9 +8,19 @@ interface DocsTocProps {
|
|
|
8
8
|
label?: string | undefined;
|
|
9
9
|
/**
|
|
10
10
|
* Region of the viewport that counts as "current", as an
|
|
11
|
-
* `IntersectionObserver` root margin. The default
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* `IntersectionObserver` root margin. The default ignores the bottom 60% of
|
|
12
|
+
* the screen, so the active entry tracks what you are reading rather than
|
|
13
|
+
* what has scrolled into view.
|
|
14
|
+
*
|
|
15
|
+
* ⚠️ IT USED TO RESERVE 80px FOR A STICKY HEADER, AND TWO THINGS WERE WRONG
|
|
16
|
+
* WITH THAT. There is no header — above 80rem, where this component
|
|
17
|
+
* is rendered at all, nothing of ours overlays the content. And 80px never
|
|
18
|
+
* matched the header anyway: the token was `3.5rem`, which is 56px, so the
|
|
19
|
+
* figure was never derived from the thing it claimed to reserve for.
|
|
20
|
+
*
|
|
21
|
+
* This component is exported standalone, so its default cannot assume our
|
|
22
|
+
* shell. It assumes nothing overlays the content; a host whose own chrome
|
|
23
|
+
* does sets this prop, which is what the prop is for.
|
|
14
24
|
*
|
|
15
25
|
* Only `px` and `%` are legal here — `IntersectionObserver` throws on any
|
|
16
26
|
* other unit, `rem` included.
|
package/dist/react/toc.js
CHANGED
|
@@ -3,7 +3,7 @@ import { DOCS_CONTENT_ID } from "../docs-content-id.js";
|
|
|
3
3
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
5
|
//#region src/react/toc.tsx
|
|
6
|
-
const DEFAULT_ROOT_MARGIN = "
|
|
6
|
+
const DEFAULT_ROOT_MARGIN = "0px 0px -60% 0px";
|
|
7
7
|
/**
|
|
8
8
|
* How many frames to keep looking for headings that are not in the document
|
|
9
9
|
* yet. ~1s at 60Hz, which covers a `<Suspense>` boundary resolving or a
|
|
@@ -86,15 +86,22 @@ function DocsToc({ entries, label = "On this page", rootMargin = DEFAULT_ROOT_MA
|
|
|
86
86
|
return /* @__PURE__ */ jsxs("nav", {
|
|
87
87
|
"aria-label": label,
|
|
88
88
|
className: ["wave-docs-toc", className].filter(Boolean).join(" "),
|
|
89
|
-
children: [
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
89
|
+
children: [
|
|
90
|
+
/* @__PURE__ */ jsx("p", {
|
|
91
|
+
className: "wave-docs-toc__title",
|
|
92
|
+
children: label
|
|
93
|
+
}),
|
|
94
|
+
/* @__PURE__ */ jsx(TocList, {
|
|
95
|
+
entries,
|
|
96
|
+
activeId,
|
|
97
|
+
onSelect: setActiveId
|
|
98
|
+
}),
|
|
99
|
+
/* @__PURE__ */ jsx("a", {
|
|
100
|
+
className: "wave-docs-toc__top",
|
|
101
|
+
href: `#${DOCS_CONTENT_ID}`,
|
|
102
|
+
children: topLabel
|
|
103
|
+
})
|
|
104
|
+
]
|
|
98
105
|
});
|
|
99
106
|
}
|
|
100
107
|
function TocList({ entries, activeId, onSelect }) {
|
package/dist/render.js
CHANGED
|
@@ -431,7 +431,8 @@ function createDocsRenderer(options) {
|
|
|
431
431
|
relativePath: file.relativePath
|
|
432
432
|
};
|
|
433
433
|
const hast = await processor.run(processor.parse(vfile), vfile);
|
|
434
|
-
|
|
434
|
+
const hasHero = (file.frontmatter.actions?.length ?? 0) > 0;
|
|
435
|
+
if (titleHeading && !hasHero && !hasHeadingOne(hast)) hast.children.unshift(titleHeadingNode(file.frontmatter.title));
|
|
435
436
|
await resolveImages(hast, file, imageResolver);
|
|
436
437
|
assertLinks(file, vfile.data.docLinks ?? []);
|
|
437
438
|
assertOwnAnchors(file, hast, vfile.data.docLinks ?? []);
|
package/dist/source.js
CHANGED
|
@@ -18,7 +18,7 @@ const INDEX_NAME = "index";
|
|
|
18
18
|
* ⚠️ THE SCAN USED TO OPEN EVERY MARKDOWN FILE AT ONCE. `scanDir` recursed into
|
|
19
19
|
* its subdirectories in parallel and read that directory's pages with a bare
|
|
20
20
|
* `Promise.all`, so the number of `readFile` calls in flight equalled the number
|
|
21
|
-
* of markdown files in the entire tree. On a 1,
|
|
21
|
+
* of markdown files in the entire tree. On a 1,201-page corpus and the common
|
|
22
22
|
* 1,024-descriptor soft limit, `next build` died with a bare `EMFILE: too many
|
|
23
23
|
* open files` — no error code, no mention that this was the docs scan, and
|
|
24
24
|
* nothing pointing at the fix. Exactly the large content set this package is
|