fumadocs-core 16.2.1 → 16.2.3

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.
@@ -1,5 +1,5 @@
1
1
  import { ReactNode } from 'react';
2
- import { R as Root, N as Node } from './definitions-DbCug1P3.js';
2
+ import { R as Root, N as Node } from './definitions-pJ7PybYY.js';
3
3
 
4
4
  interface BreadcrumbItem {
5
5
  name: ReactNode;
@@ -1,9 +1,9 @@
1
- import {
2
- findPath
3
- } from "./chunk-IZPLHEX4.js";
4
1
  import {
5
2
  normalizeUrl
6
3
  } from "./chunk-PFNP6PEB.js";
4
+ import {
5
+ findPath
6
+ } from "./chunk-XKUN7AUK.js";
7
7
  import "./chunk-U67V476Y.js";
8
8
 
9
9
  // src/breadcrumb.tsx
@@ -15,6 +15,20 @@ function remarkImage({
15
15
  const importsToInject = [];
16
16
  const promises = [];
17
17
  async function onImage(src, node) {
18
+ const attributes = [
19
+ {
20
+ type: "mdxJsxAttribute",
21
+ name: "alt",
22
+ value: node.alt ?? "image"
23
+ }
24
+ ];
25
+ if (node.title) {
26
+ attributes.push({
27
+ type: "mdxJsxAttribute",
28
+ name: "title",
29
+ value: node.title
30
+ });
31
+ }
18
32
  if (src.type === "file" && useImport) {
19
33
  const variableName = `__img${importsToInject.length}`;
20
34
  const hasBlur = placeholder === "blur" && VALID_BLUR_EXT.some((ext) => src.file.endsWith(ext));
@@ -27,37 +41,31 @@ function remarkImage({
27
41
  variableName,
28
42
  importPath: getImportPath(src.file, file.dirname)
29
43
  });
44
+ attributes.push({
45
+ type: "mdxJsxAttribute",
46
+ name: "src",
47
+ value: {
48
+ type: "mdxJsxAttributeValueExpression",
49
+ value: variableName,
50
+ data: {
51
+ estree: {
52
+ body: [
53
+ {
54
+ type: "ExpressionStatement",
55
+ expression: { type: "Identifier", name: variableName }
56
+ }
57
+ ],
58
+ type: "Program",
59
+ sourceType: "script"
60
+ }
61
+ }
62
+ }
63
+ });
30
64
  const out = {
31
65
  children: [],
32
66
  type: "mdxJsxFlowElement",
33
67
  name: "img",
34
- attributes: [
35
- {
36
- type: "mdxJsxAttribute",
37
- name: "alt",
38
- value: node.alt ?? "image"
39
- },
40
- {
41
- type: "mdxJsxAttribute",
42
- name: "src",
43
- value: {
44
- type: "mdxJsxAttributeValueExpression",
45
- value: variableName,
46
- data: {
47
- estree: {
48
- body: [
49
- {
50
- type: "ExpressionStatement",
51
- expression: { type: "Identifier", name: variableName }
52
- }
53
- ],
54
- type: "Program",
55
- sourceType: "script"
56
- }
57
- }
58
- }
59
- }
60
- ]
68
+ attributes
61
69
  };
62
70
  if (hasBlur) {
63
71
  out.attributes.push({
@@ -77,32 +85,28 @@ function remarkImage({
77
85
  );
78
86
  });
79
87
  if (!size) return;
88
+ attributes.push(
89
+ {
90
+ type: "mdxJsxAttribute",
91
+ name: "src",
92
+ // `src` doesn't support file paths, we can use `node.url` for files and let the underlying framework handle it
93
+ value: src.type === "url" ? src.url.toString() : node.url
94
+ },
95
+ {
96
+ type: "mdxJsxAttribute",
97
+ name: "width",
98
+ value: size.width.toString()
99
+ },
100
+ {
101
+ type: "mdxJsxAttribute",
102
+ name: "height",
103
+ value: size.height.toString()
104
+ }
105
+ );
80
106
  return {
81
107
  type: "mdxJsxFlowElement",
82
108
  name: "img",
83
- attributes: [
84
- {
85
- type: "mdxJsxAttribute",
86
- name: "alt",
87
- value: node.alt ?? "image"
88
- },
89
- {
90
- type: "mdxJsxAttribute",
91
- name: "src",
92
- // `src` doesn't support file paths, we can use `node.url` for files and let the underlying framework handle it
93
- value: src.type === "url" ? src.url.toString() : node.url
94
- },
95
- {
96
- type: "mdxJsxAttribute",
97
- name: "width",
98
- value: size.width.toString()
99
- },
100
- {
101
- type: "mdxJsxAttribute",
102
- name: "height",
103
- value: size.height.toString()
104
- }
105
- ],
109
+ attributes,
106
110
  children: []
107
111
  };
108
112
  }
@@ -45,36 +45,21 @@ function getPageTreePeers(treeOrTrees, url) {
45
45
  (item) => item.type === "page" && item.url !== url
46
46
  );
47
47
  }
48
- const trees = treeOrTrees;
49
- for (const lang in trees) {
50
- const rootTree = trees[lang];
51
- if (rootTree) {
52
- const parent = findParentFromTree(rootTree, url);
53
- if (parent) {
54
- return parent.children.filter(
55
- (item) => item.type === "page" && item.url !== url
56
- );
57
- }
58
- }
48
+ for (const lang in treeOrTrees) {
49
+ const result = getPageTreePeers(treeOrTrees[lang], url);
50
+ if (result) return result;
59
51
  }
60
52
  return [];
61
53
  }
62
- function findParentFromTree(node, url) {
63
- if ("index" in node && node.index?.url === url) {
64
- return node;
65
- }
66
- for (const child of node.children) {
67
- if (child.type === "folder") {
68
- const parent = findParentFromTree(child, url);
69
- if (parent) return parent;
70
- }
71
- if (child.type === "page" && child.url === url) {
72
- return node;
54
+ function findParentFromTree(from, url) {
55
+ let result;
56
+ visit(from, (node, parent) => {
57
+ if ("type" in node && node.type === "page" && node.url === url) {
58
+ result = parent;
59
+ return "break";
73
60
  }
74
- }
75
- if ("fallback" in node && node.fallback) {
76
- return findParentFromTree(node.fallback, url);
77
- }
61
+ });
62
+ return result;
78
63
  }
79
64
  function findPath(nodes, matcher, options = {}) {
80
65
  const { includeSeparator = true } = options;
@@ -103,11 +88,44 @@ function findPath(nodes, matcher, options = {}) {
103
88
  }
104
89
  return run(nodes) ?? null;
105
90
  }
91
+ var VisitBreak = Symbol("VisitBreak");
92
+ function visit(root, visitor) {
93
+ function onNode(node, parent) {
94
+ const result = visitor(node, parent);
95
+ switch (result) {
96
+ case "skip":
97
+ return node;
98
+ case "break":
99
+ throw VisitBreak;
100
+ default:
101
+ if (result) node = result;
102
+ }
103
+ if ("index" in node && node.index) {
104
+ node.index = onNode(node.index, node);
105
+ }
106
+ if ("fallback" in node && node.fallback) {
107
+ node.fallback = onNode(node.fallback, node);
108
+ }
109
+ if ("children" in node) {
110
+ for (let i = 0; i < node.children.length; i++) {
111
+ node.children[i] = onNode(node.children[i], node);
112
+ }
113
+ }
114
+ return node;
115
+ }
116
+ try {
117
+ return onNode(root);
118
+ } catch (e) {
119
+ if (e === VisitBreak) return root;
120
+ throw e;
121
+ }
122
+ }
106
123
 
107
124
  export {
108
125
  flattenTree,
109
126
  findNeighbour,
110
127
  getPageTreeRoots,
111
128
  getPageTreePeers,
112
- findPath
129
+ findPath,
130
+ visit
113
131
  };
@@ -1,12 +1,12 @@
1
1
  import { ReactNode } from 'react';
2
2
 
3
- interface INode {
3
+ interface ID {
4
4
  /**
5
5
  * ID for the node, unique in all page trees (even across different locales)
6
6
  */
7
7
  $id?: string;
8
8
  }
9
- interface Root extends INode {
9
+ interface Root extends ID {
10
10
  name: ReactNode;
11
11
  children: Node[];
12
12
  /**
@@ -15,7 +15,7 @@ interface Root extends INode {
15
15
  fallback?: Root;
16
16
  }
17
17
  type Node = Item | Separator | Folder;
18
- interface Item extends INode {
18
+ interface Item extends ID {
19
19
  /**
20
20
  * @internal
21
21
  */
@@ -34,12 +34,12 @@ interface Item extends INode {
34
34
  description?: ReactNode;
35
35
  icon?: ReactNode;
36
36
  }
37
- interface Separator extends INode {
37
+ interface Separator extends ID {
38
38
  type: 'separator';
39
39
  name?: ReactNode;
40
40
  icon?: ReactNode;
41
41
  }
42
- interface Folder extends INode {
42
+ interface Folder extends ID {
43
43
  /**
44
44
  * @internal
45
45
  */
@@ -51,6 +51,7 @@ interface Folder extends INode {
51
51
  description?: ReactNode;
52
52
  root?: boolean;
53
53
  defaultOpen?: boolean;
54
+ collapsible?: boolean;
54
55
  index?: Item;
55
56
  icon?: ReactNode;
56
57
  children: Node[];
@@ -8,10 +8,10 @@ async function fetchDocs(query, { api = "/api/search", locale, tag }) {
8
8
  if (locale) url.searchParams.set("locale", locale);
9
9
  if (tag)
10
10
  url.searchParams.set("tag", Array.isArray(tag) ? tag.join(",") : tag);
11
- const key = `${url.pathname}?${url.searchParams}`;
11
+ const key = url.toString();
12
12
  const cached = cache.get(key);
13
13
  if (cached) return cached;
14
- const res = await fetch(key);
14
+ const res = await fetch(url);
15
15
  if (!res.ok) throw new Error(await res.text());
16
16
  const result = await res.json();
17
17
  cache.set(key, result);
@@ -1,4 +1,4 @@
1
- import { R as Root, I as Item, F as Folder, S as Separator } from './definitions-DbCug1P3.js';
1
+ import { R as Root, I as Item, F as Folder, S as Separator } from './definitions-pJ7PybYY.js';
2
2
  import { I18nConfig } from './i18n/index.js';
3
3
  import { ReactNode } from 'react';
4
4
 
@@ -15,6 +15,7 @@ interface MetaData {
15
15
  root?: boolean | undefined;
16
16
  pages?: string[] | undefined;
17
17
  defaultOpen?: boolean | undefined;
18
+ collapsible?: boolean | undefined;
18
19
  description?: string | undefined;
19
20
  }
20
21
  interface PageData {
@@ -280,6 +281,10 @@ interface LoaderOutput<Config extends LoaderConfig> {
280
281
  * @param lang - customise parameter name for lang
281
282
  */
282
283
  generateParams: <TSlug extends string = 'slug', TLang extends string = 'lang'>(slug?: TSlug, lang?: TLang) => (Record<TSlug, string[]> & Record<TLang, string>)[];
284
+ /**
285
+ * serialize page tree for non-RSC environments
286
+ */
287
+ serializePageTree: (tree: Root) => Promise<object>;
283
288
  }
284
289
  declare function createGetUrl(baseUrl: string, i18n?: I18nConfig): ResolvedLoaderConfig['url'];
285
290
  declare function loader<Config extends SourceConfig, I18n extends I18nConfig | undefined = undefined>(source: Source<Config>, options: LoaderOptions<{
@@ -1,9 +1,6 @@
1
- import {
2
- default as default2
3
- } from "../chunk-ONG4RVCR.js";
4
1
  import {
5
2
  remarkImage
6
- } from "../chunk-YDNO7UZ6.js";
3
+ } from "../chunk-5PMI7QDD.js";
7
4
  import {
8
5
  remarkMdxFiles
9
6
  } from "../chunk-ADBHPKXG.js";
@@ -42,6 +39,9 @@ import {
42
39
  import {
43
40
  remarkDirectiveAdmonition
44
41
  } from "../chunk-APKPSBSB.js";
42
+ import {
43
+ default as default2
44
+ } from "../chunk-ONG4RVCR.js";
45
45
  import "../chunk-XN2LKXFZ.js";
46
46
  import {
47
47
  remarkHeading
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  remarkImage
3
- } from "../chunk-YDNO7UZ6.js";
3
+ } from "../chunk-5PMI7QDD.js";
4
4
  import "../chunk-U67V476Y.js";
5
5
  export {
6
6
  remarkImage
@@ -1,5 +1,5 @@
1
- import { N as Node, I as Item, R as Root, F as Folder } from '../definitions-DbCug1P3.js';
2
- export { S as Separator } from '../definitions-DbCug1P3.js';
1
+ import { N as Node, I as Item, R as Root, F as Folder } from '../definitions-pJ7PybYY.js';
2
+ export { S as Separator } from '../definitions-pJ7PybYY.js';
3
3
  import 'react';
4
4
 
5
5
  /**
@@ -28,5 +28,6 @@ declare function getPageTreePeers(treeOrTrees: Root | Record<string, Root>, url:
28
28
  declare function findPath(nodes: Node[], matcher: (node: Node) => boolean, options?: {
29
29
  includeSeparator?: boolean;
30
30
  }): Node[] | null;
31
+ declare function visit<Root extends Node | Root>(root: Root, visitor: <T extends Node | Root>(node: T, parent?: Root | Folder) => 'skip' | 'break' | T | void): Root;
31
32
 
32
- export { Folder, Item, Node, Root, findNeighbour, findPath, flattenTree, getPageTreePeers, getPageTreeRoots };
33
+ export { Folder, Item, Node, Root, findNeighbour, findPath, flattenTree, getPageTreePeers, getPageTreeRoots, visit };
@@ -3,13 +3,15 @@ import {
3
3
  findPath,
4
4
  flattenTree,
5
5
  getPageTreePeers,
6
- getPageTreeRoots
7
- } from "../chunk-IZPLHEX4.js";
6
+ getPageTreeRoots,
7
+ visit
8
+ } from "../chunk-XKUN7AUK.js";
8
9
  import "../chunk-U67V476Y.js";
9
10
  export {
10
11
  findNeighbour,
11
12
  findPath,
12
13
  flattenTree,
13
14
  getPageTreePeers,
14
- getPageTreeRoots
15
+ getPageTreeRoots,
16
+ visit
15
17
  };
@@ -13,7 +13,7 @@ import 'react';
13
13
 
14
14
  interface FetchOptions {
15
15
  /**
16
- * API route for search endpoint
16
+ * API route for search endpoint, support absolute URLs.
17
17
  *
18
18
  * @defaultValue '/api/search'
19
19
  */
@@ -58,7 +58,7 @@ function useDocsSearch(clientOptions) {
58
58
  async function run() {
59
59
  if (debouncedValue.length === 0 && !allowEmpty) return "empty";
60
60
  if (client.type === "fetch") {
61
- const { fetchDocs } = await import("../fetch-2XFMBLBA.js");
61
+ const { fetchDocs } = await import("../fetch-IBTWQCJR.js");
62
62
  return fetchDocs(debouncedValue, client);
63
63
  }
64
64
  if (client.type === "algolia") {
@@ -3,12 +3,12 @@ import { StructuredData } from '../mdx-plugins/remark-structure.js';
3
3
  import { SortedResult } from './index.js';
4
4
  export { HighlightedText, ReactSortedResult, createContentHighlighter } from './index.js';
5
5
  import { I18nConfig } from '../i18n/index.js';
6
- import { g as LoaderOutput, c as LoaderConfig, I as InferPageType } from '../loader-DgL6G4CA.js';
6
+ import { g as LoaderOutput, c as LoaderConfig, I as InferPageType } from '../loader-qkSHi822.js';
7
7
  import 'mdast';
8
8
  import 'unified';
9
9
  import 'mdast-util-mdx-jsx';
10
10
  import 'react';
11
- import '../definitions-DbCug1P3.js';
11
+ import '../definitions-pJ7PybYY.js';
12
12
 
13
13
  type SimpleDocument = TypedDocument<Orama<typeof simpleSchema>>;
14
14
  declare const simpleSchema: {
@@ -12,7 +12,7 @@ import {
12
12
  } from "../chunk-XZSI7AHE.js";
13
13
  import {
14
14
  findPath
15
- } from "../chunk-IZPLHEX4.js";
15
+ } from "../chunk-XKUN7AUK.js";
16
16
  import "../chunk-U67V476Y.js";
17
17
 
18
18
  // src/search/server.ts
@@ -0,0 +1,16 @@
1
+ import { R as Root } from '../../definitions-pJ7PybYY.js';
2
+ import 'react';
3
+
4
+ declare function deserializePageTree(root: Root): Root;
5
+ /**
6
+ * Deserialize data passed from server-side loader.
7
+ *
8
+ * It only receives the serialized data from server-side, hence not sharing plugins and some properties.
9
+ */
10
+ declare function useFumadocsLoader<V extends {
11
+ pageTree?: object;
12
+ }>(serialized: V): {
13
+ pageTree: V["pageTree"] extends object ? Root : undefined;
14
+ };
15
+
16
+ export { deserializePageTree, useFumadocsLoader };
@@ -0,0 +1,41 @@
1
+ import {
2
+ visit
3
+ } from "../../chunk-XKUN7AUK.js";
4
+ import "../../chunk-U67V476Y.js";
5
+
6
+ // src/source/client/index.tsx
7
+ import { useMemo } from "react";
8
+ import { jsx } from "react/jsx-runtime";
9
+ function deserializePageTree(root) {
10
+ function deserializeHTML(html) {
11
+ return /* @__PURE__ */ jsx(
12
+ "span",
13
+ {
14
+ dangerouslySetInnerHTML: {
15
+ __html: html
16
+ }
17
+ }
18
+ );
19
+ }
20
+ visit(root, (item) => {
21
+ if ("icon" in item && typeof item.icon === "string") {
22
+ item.icon = deserializeHTML(item.icon);
23
+ }
24
+ if (typeof item.name === "string") {
25
+ item.name = deserializeHTML(item.name);
26
+ }
27
+ });
28
+ return root;
29
+ }
30
+ function useFumadocsLoader(serialized) {
31
+ const { pageTree } = serialized;
32
+ return useMemo(() => {
33
+ return {
34
+ pageTree: pageTree ? deserializePageTree(pageTree) : void 0
35
+ };
36
+ }, [pageTree]);
37
+ }
38
+ export {
39
+ deserializePageTree,
40
+ useFumadocsLoader
41
+ };
@@ -1,5 +1,5 @@
1
- export { C as ContentStorage, q as ContentStorageFile, F as FileSystem, i as InferMetaType, I as InferPageType, c as LoaderConfig, d as LoaderOptions, g as LoaderOutput, L as LoaderPlugin, s as LoaderPluginOption, f as Meta, M as MetaData, e as Page, P as PageData, o as PageTreeBuilder, j as PageTreeBuilderContext, n as PageTreeOptions, k as PageTreeTransformer, R as ResolvedLoaderConfig, S as Source, a as SourceConfig, V as VirtualFile, _ as _ConfigUnion_, r as buildContentStorage, t as buildPlugins, h as createGetUrl, p as createPageTreeBuilder, l as loader, b as map, m as multiple } from '../loader-DgL6G4CA.js';
2
- import '../definitions-DbCug1P3.js';
1
+ export { C as ContentStorage, q as ContentStorageFile, F as FileSystem, i as InferMetaType, I as InferPageType, c as LoaderConfig, d as LoaderOptions, g as LoaderOutput, L as LoaderPlugin, s as LoaderPluginOption, f as Meta, M as MetaData, e as Page, P as PageData, o as PageTreeBuilder, j as PageTreeBuilderContext, n as PageTreeOptions, k as PageTreeTransformer, R as ResolvedLoaderConfig, S as Source, a as SourceConfig, V as VirtualFile, _ as _ConfigUnion_, r as buildContentStorage, t as buildPlugins, h as createGetUrl, p as createPageTreeBuilder, l as loader, b as map, m as multiple } from '../loader-qkSHi822.js';
2
+ import '../definitions-pJ7PybYY.js';
3
3
  import 'react';
4
4
  import '../i18n/index.js';
5
5
 
@@ -13,6 +13,9 @@ import {
13
13
  import {
14
14
  normalizeUrl
15
15
  } from "../chunk-PFNP6PEB.js";
16
+ import {
17
+ visit
18
+ } from "../chunk-XKUN7AUK.js";
16
19
  import "../chunk-U67V476Y.js";
17
20
 
18
21
  // src/source/source.ts
@@ -330,7 +333,8 @@ function createPageTreeBuilderUtils(ctx) {
330
333
  const dirNode = this.folder(path, false);
331
334
  if (dirNode) folders.push(dirNode);
332
335
  }
333
- return [...items, ...folders];
336
+ items.push(...folders);
337
+ return items;
334
338
  },
335
339
  resolveFolderItem(folderPath, item) {
336
340
  if (item === rest || item === restReversed) return item;
@@ -395,15 +399,14 @@ function createPageTreeBuilderUtils(ctx) {
395
399
  "page"
396
400
  );
397
401
  let meta = storage.read(metaPath);
398
- if (meta?.format !== "meta") {
399
- meta = void 0;
400
- }
401
- const isRoot = meta?.data.root ?? isGlobalRoot;
402
+ if (meta && meta.format !== "meta") meta = void 0;
403
+ const metadata = meta?.data ?? {};
404
+ const { root = isGlobalRoot, pages } = metadata;
402
405
  let index;
403
406
  let children;
404
- if (meta && meta.data.pages) {
405
- const resolved = meta.data.pages.flatMap((item) => this.resolveFolderItem(folderPath, item));
406
- if (!isRoot && !visitedPaths.has(indexPath)) {
407
+ if (pages) {
408
+ const resolved = pages.flatMap((item) => this.resolveFolderItem(folderPath, item));
409
+ if (!root && !visitedPaths.has(indexPath)) {
407
410
  index = this.file(indexPath);
408
411
  }
409
412
  for (let i = 0; i < resolved.length; i++) {
@@ -418,25 +421,24 @@ function createPageTreeBuilderUtils(ctx) {
418
421
  }
419
422
  children = resolved;
420
423
  } else {
421
- if (!isRoot && !visitedPaths.has(indexPath)) {
424
+ if (!root && !visitedPaths.has(indexPath)) {
422
425
  index = this.file(indexPath);
423
426
  }
424
427
  children = this.buildPaths(
425
428
  files.filter((file) => !visitedPaths.has(file))
426
429
  );
427
430
  }
428
- let name = meta?.data.title ?? index?.name;
429
- if (!name) {
430
- const folderName = basename(folderPath);
431
- name = pathToName(group.exec(folderName)?.[1] ?? folderName);
432
- }
433
431
  let node = {
434
432
  type: "folder",
435
- name,
436
- icon: meta?.data.icon ?? index?.icon,
437
- root: meta?.data.root,
438
- defaultOpen: meta?.data.defaultOpen,
439
- description: meta?.data.description,
433
+ name: metadata.title ?? index?.name ?? (() => {
434
+ const folderName = basename(folderPath);
435
+ return pathToName(group.exec(folderName)?.[1] ?? folderName);
436
+ })(),
437
+ icon: metadata.icon,
438
+ root: metadata.root,
439
+ defaultOpen: metadata.defaultOpen,
440
+ description: metadata.description,
441
+ collapsible: metadata.collapsible,
440
442
  index,
441
443
  children,
442
444
  $id: nextNodeId(folderPath),
@@ -618,48 +620,28 @@ function createGetUrl(baseUrl, i18n) {
618
620
  };
619
621
  }
620
622
  function loader(...args) {
621
- const resolved = args.length === 2 ? resolveConfig(args[0], args[1]) : resolveConfig(args[0].source, args[0]);
622
- return createOutput(resolved);
623
- }
624
- function resolveConfig(source, { slugs, icon, plugins = [], baseUrl, url, ...base }) {
625
- let config = {
626
- ...base,
627
- url: url ? (...args) => normalizeUrl(url(...args)) : createGetUrl(baseUrl, base.i18n),
628
- source,
629
- plugins: buildPlugins([
630
- slugsPlugin(slugs),
631
- icon && iconPlugin(icon),
632
- ...typeof plugins === "function" ? plugins({
633
- typedPlugin: (plugin) => plugin
634
- }) : plugins
635
- ])
636
- };
637
- for (const plugin of config.plugins ?? []) {
638
- const result = plugin.config?.(config);
639
- if (result) config = result;
640
- }
641
- return config;
642
- }
643
- function createOutput(loaderConfig) {
623
+ const loaderConfig = args.length === 2 ? resolveConfig(args[0], args[1]) : resolveConfig(args[0].source, args[0]);
644
624
  const { i18n } = loaderConfig;
645
625
  const defaultLanguage = i18n?.defaultLanguage ?? "";
646
626
  const storages = buildContentStorage(loaderConfig, defaultLanguage);
647
627
  const walker = indexPages(storages, loaderConfig);
648
628
  const builder = createPageTreeBuilder(loaderConfig);
649
- let pageTree;
629
+ let pageTrees;
630
+ function getPageTrees() {
631
+ return pageTrees ??= builder.buildI18n(storages);
632
+ }
650
633
  return {
651
634
  _i18n: i18n,
652
635
  get pageTree() {
653
- pageTree ??= builder.buildI18n(storages);
654
- return i18n ? pageTree : pageTree[defaultLanguage];
636
+ const trees = getPageTrees();
637
+ return i18n ? trees : trees[defaultLanguage];
655
638
  },
656
639
  set pageTree(v) {
657
640
  if (i18n) {
658
- pageTree = v;
641
+ pageTrees = v;
659
642
  } else {
660
- pageTree = {
661
- [defaultLanguage]: v
662
- };
643
+ pageTrees ??= {};
644
+ pageTrees[defaultLanguage] = v;
663
645
  }
664
646
  },
665
647
  getPageByHref(href, { dir = "", language = defaultLanguage } = {}) {
@@ -715,11 +697,9 @@ function createOutput(loaderConfig) {
715
697
  if (!ref) return;
716
698
  return walker.pathToPage.get(`${language}.${ref}`);
717
699
  },
718
- getPageTree(locale) {
719
- if (i18n) {
720
- return this.pageTree[locale ?? defaultLanguage];
721
- }
722
- return this.pageTree;
700
+ getPageTree(locale = defaultLanguage) {
701
+ const trees = getPageTrees();
702
+ return trees[locale] ?? trees[defaultLanguage];
723
703
  },
724
704
  // @ts-expect-error -- ignore this
725
705
  generateParams(slug, lang) {
@@ -734,9 +714,44 @@ function createOutput(loaderConfig) {
734
714
  return this.getPages().map((page) => ({
735
715
  [slug ?? "slug"]: page.slugs
736
716
  }));
717
+ },
718
+ async serializePageTree(tree) {
719
+ const { renderToString } = await import("react-dom/server.edge");
720
+ return visit(tree, (node) => {
721
+ node = { ...node };
722
+ if ("icon" in node && node.icon) {
723
+ node.icon = renderToString(node.icon);
724
+ }
725
+ if (node.name) {
726
+ node.name = renderToString(node.name);
727
+ }
728
+ if ("children" in node) {
729
+ node.children = [...node.children];
730
+ }
731
+ return node;
732
+ });
737
733
  }
738
734
  };
739
735
  }
736
+ function resolveConfig(source, { slugs, icon, plugins = [], baseUrl, url, ...base }) {
737
+ let config = {
738
+ ...base,
739
+ url: url ? (...args) => normalizeUrl(url(...args)) : createGetUrl(baseUrl, base.i18n),
740
+ source,
741
+ plugins: buildPlugins([
742
+ slugsPlugin(slugs),
743
+ icon && iconPlugin(icon),
744
+ ...typeof plugins === "function" ? plugins({
745
+ typedPlugin: (plugin) => plugin
746
+ }) : plugins
747
+ ])
748
+ };
749
+ for (const plugin of config.plugins ?? []) {
750
+ const result = plugin.config?.(config);
751
+ if (result) config = result;
752
+ }
753
+ return config;
754
+ }
740
755
  export {
741
756
  FileSystem,
742
757
  path_exports as PathUtils,
@@ -1,6 +1,6 @@
1
- import { L as LoaderPlugin } from '../../loader-DgL6G4CA.js';
1
+ import { L as LoaderPlugin } from '../../loader-qkSHi822.js';
2
2
  import { icons } from 'lucide-react';
3
- import '../../definitions-DbCug1P3.js';
3
+ import '../../definitions-pJ7PybYY.js';
4
4
  import 'react';
5
5
  import '../../i18n/index.js';
6
6
 
@@ -0,0 +1,26 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * Zod 4 schema
5
+ */
6
+ declare const metaSchema: z.ZodObject<{
7
+ title: z.ZodOptional<z.ZodString>;
8
+ pages: z.ZodOptional<z.ZodArray<z.ZodString>>;
9
+ description: z.ZodOptional<z.ZodString>;
10
+ root: z.ZodOptional<z.ZodBoolean>;
11
+ defaultOpen: z.ZodOptional<z.ZodBoolean>;
12
+ collapsible: z.ZodOptional<z.ZodBoolean>;
13
+ icon: z.ZodOptional<z.ZodString>;
14
+ }, z.core.$strip>;
15
+ /**
16
+ * Zod 4 schema
17
+ */
18
+ declare const pageSchema: z.ZodObject<{
19
+ title: z.ZodString;
20
+ description: z.ZodOptional<z.ZodString>;
21
+ icon: z.ZodOptional<z.ZodString>;
22
+ full: z.ZodOptional<z.ZodBoolean>;
23
+ _openapi: z.ZodOptional<z.ZodObject<{}, z.core.$loose>>;
24
+ }, z.core.$strip>;
25
+
26
+ export { metaSchema, pageSchema };
@@ -0,0 +1,25 @@
1
+ import "../chunk-U67V476Y.js";
2
+
3
+ // src/source/schema.ts
4
+ import { z } from "zod";
5
+ var metaSchema = z.object({
6
+ title: z.string().optional(),
7
+ pages: z.array(z.string()).optional(),
8
+ description: z.string().optional(),
9
+ root: z.boolean().optional(),
10
+ defaultOpen: z.boolean().optional(),
11
+ collapsible: z.boolean().optional(),
12
+ icon: z.string().optional()
13
+ });
14
+ var pageSchema = z.object({
15
+ title: z.string(),
16
+ description: z.string().optional(),
17
+ icon: z.string().optional(),
18
+ full: z.boolean().optional(),
19
+ // Fumadocs OpenAPI generated
20
+ _openapi: z.looseObject({}).optional()
21
+ });
22
+ export {
23
+ metaSchema,
24
+ pageSchema
25
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fumadocs-core",
3
- "version": "16.2.1",
3
+ "version": "16.2.3",
4
4
  "description": "The React.js library for building a documentation website",
5
5
  "keywords": [
6
6
  "Fumadocs",
@@ -28,10 +28,6 @@
28
28
  "import": "./dist/content/*.js",
29
29
  "types": "./dist/content/*.d.ts"
30
30
  },
31
- "./hide-if-empty": {
32
- "import": "./dist/hide-if-empty.js",
33
- "types": "./dist/hide-if-empty.d.ts"
34
- },
35
31
  "./negotiation": {
36
32
  "import": "./dist/negotiation/index.js",
37
33
  "types": "./dist/negotiation/index.d.ts"
@@ -52,6 +48,14 @@
52
48
  "import": "./dist/source/index.js",
53
49
  "types": "./dist/source/index.d.ts"
54
50
  },
51
+ "./source/client": {
52
+ "import": "./dist/source/client/index.js",
53
+ "types": "./dist/source/client/index.d.ts"
54
+ },
55
+ "./source/schema": {
56
+ "import": "./dist/source/schema.js",
57
+ "types": "./dist/source/schema.d.ts"
58
+ },
55
59
  "./source/*": {
56
60
  "import": "./dist/source/plugins/*.js",
57
61
  "types": "./dist/source/plugins/*.d.ts"
@@ -107,8 +111,8 @@
107
111
  "dependencies": {
108
112
  "@formatjs/intl-localematcher": "^0.6.2",
109
113
  "@orama/orama": "^3.1.16",
110
- "@shikijs/rehype": "^3.15.0",
111
- "@shikijs/transformers": "^3.15.0",
114
+ "@shikijs/rehype": "^3.19.0",
115
+ "@shikijs/transformers": "^3.19.0",
112
116
  "estree-util-value-to-estree": "^3.5.0",
113
117
  "github-slugger": "^2.0.0",
114
118
  "hast-util-to-estree": "^3.1.3",
@@ -121,34 +125,35 @@
121
125
  "remark-gfm": "^4.0.1",
122
126
  "remark-rehype": "^11.1.2",
123
127
  "scroll-into-view-if-needed": "^3.1.0",
124
- "shiki": "^3.15.0",
128
+ "shiki": "^3.19.0",
125
129
  "unist-util-visit": "^5.0.0"
126
130
  },
127
131
  "devDependencies": {
128
132
  "@mdx-js/mdx": "^3.1.1",
129
- "@mixedbread/sdk": "^0.44.0",
133
+ "@mixedbread/sdk": "^0.45.0",
130
134
  "@orama/core": "^1.2.13",
131
- "@tanstack/react-router": "^1.136.6",
135
+ "@tanstack/react-router": "1.136.18",
132
136
  "@types/estree-jsx": "^1.0.5",
133
137
  "@types/hast": "^3.0.4",
134
138
  "@types/mdast": "^4.0.4",
135
139
  "@types/negotiator": "^0.6.4",
136
140
  "@types/node": "24.10.1",
137
- "@types/react": "^19.2.5",
141
+ "@types/react": "^19.2.7",
138
142
  "@types/react-dom": "^19.2.3",
139
- "algoliasearch": "5.44.0",
140
- "lucide-react": "^0.553.0",
143
+ "algoliasearch": "5.45.0",
144
+ "lucide-react": "^0.555.0",
141
145
  "mdast-util-mdx-jsx": "^3.2.0",
142
146
  "mdast-util-mdxjs-esm": "^2.0.1",
143
- "next": "16.0.3",
144
- "react-router": "^7.9.6",
147
+ "next": "16.0.7",
148
+ "react-router": "^7.10.0",
145
149
  "remark-directive": "^4.0.0",
146
150
  "remark-mdx": "^3.1.1",
147
151
  "remove-markdown": "^0.6.2",
148
152
  "typescript": "^5.9.3",
149
153
  "unified": "^11.0.5",
150
154
  "vfile": "^6.0.3",
151
- "waku": "^0.27.0",
155
+ "waku": "^0.27.2",
156
+ "zod": "^4.1.13",
152
157
  "eslint-config-custom": "0.0.0",
153
158
  "tsconfig": "0.0.0"
154
159
  },
@@ -163,7 +168,8 @@
163
168
  "react": "^19.2.0",
164
169
  "react-dom": "^19.2.0",
165
170
  "react-router": "7.x.x",
166
- "waku": "^0.26.0 || ^0.27.0"
171
+ "waku": "^0.26.0 || ^0.27.0",
172
+ "zod": "*"
167
173
  },
168
174
  "peerDependenciesMeta": {
169
175
  "@mixedbread/sdk": {
@@ -198,6 +204,9 @@
198
204
  },
199
205
  "lucide-react": {
200
206
  "optional": true
207
+ },
208
+ "zod": {
209
+ "optional": true
201
210
  }
202
211
  },
203
212
  "publishConfig": {
@@ -1,18 +0,0 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { ReactNode, HTMLAttributes, FC } from 'react';
3
-
4
- declare function HideIfEmptyProvider({ nonce, children, }: {
5
- nonce?: string;
6
- children: ReactNode;
7
- }): react_jsx_runtime.JSX.Element;
8
- /**
9
- * The built-in CSS `:empty` selector cannot detect if the children is hidden, classes such as `md:hidden` causes it to fail.
10
- * This component is an enhancement to `empty:hidden` to fix the issue described above.
11
- *
12
- * This can be expensive, please avoid this whenever possible.
13
- */
14
- declare function HideIfEmpty<Props extends HTMLAttributes<HTMLElement>>({ as: Comp, ...props }: Props & {
15
- as: FC<Props>;
16
- }): react_jsx_runtime.JSX.Element;
17
-
18
- export { HideIfEmpty, HideIfEmptyProvider };
@@ -1,83 +0,0 @@
1
- "use client";
2
- import "./chunk-U67V476Y.js";
3
-
4
- // src/hide-if-empty.tsx
5
- import {
6
- createContext,
7
- useContext,
8
- useEffect,
9
- useId,
10
- useMemo,
11
- useState
12
- } from "react";
13
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
14
- var Context = createContext({
15
- nonce: void 0
16
- });
17
- function HideIfEmptyProvider({
18
- nonce,
19
- children
20
- }) {
21
- return /* @__PURE__ */ jsx(Context.Provider, { value: useMemo(() => ({ nonce }), [nonce]), children });
22
- }
23
- function getElement(id) {
24
- return document.querySelector(`[data-fd-if-empty="${id}"]`);
25
- }
26
- function isEmpty(node) {
27
- for (let i = 0; i < node.childNodes.length; i++) {
28
- const child = node.childNodes.item(i);
29
- if (child.nodeType === Node.TEXT_NODE || child.nodeType === Node.ELEMENT_NODE && window.getComputedStyle(child).display !== "none") {
30
- return false;
31
- }
32
- }
33
- return true;
34
- }
35
- function HideIfEmpty({
36
- as: Comp,
37
- ...props
38
- }) {
39
- const id = useId();
40
- const { nonce } = useContext(Context);
41
- const [empty, setEmpty] = useState(() => {
42
- const element = typeof window !== "undefined" ? getElement(id) : null;
43
- if (element) return isEmpty(element);
44
- });
45
- useEffect(() => {
46
- const handleResize = () => {
47
- const element = getElement(id);
48
- if (element) setEmpty(isEmpty(element));
49
- };
50
- window.addEventListener("resize", handleResize);
51
- return () => window.removeEventListener("resize", handleResize);
52
- }, [id]);
53
- const init = (id2) => {
54
- const element = getElement(id2);
55
- if (element) {
56
- element.hidden = isEmpty(element);
57
- }
58
- };
59
- return /* @__PURE__ */ jsxs(Fragment, { children: [
60
- /* @__PURE__ */ jsx(
61
- Comp,
62
- {
63
- ...props,
64
- "data-fd-if-empty": id,
65
- hidden: empty ?? false
66
- }
67
- ),
68
- /* @__PURE__ */ jsx(
69
- "script",
70
- {
71
- suppressHydrationWarning: true,
72
- nonce,
73
- dangerouslySetInnerHTML: {
74
- __html: `{${getElement};${isEmpty};(${init})("${id}")}`
75
- }
76
- }
77
- )
78
- ] });
79
- }
80
- export {
81
- HideIfEmpty,
82
- HideIfEmptyProvider
83
- };