fumadocs-core 16.15.12 → 16.15.13
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/dist/mdx-plugins/rehype-toc.js +12 -1
- package/dist/toc.js +84 -95
- package/dist/utils/use-media-query.d.ts +3 -0
- package/dist/utils/use-media-query.js +12 -15
- package/package.json +3 -2
|
@@ -12,6 +12,17 @@ const HeadingTags = /* @__PURE__ */ new Set([
|
|
|
12
12
|
"h5",
|
|
13
13
|
"h6"
|
|
14
14
|
]);
|
|
15
|
+
/**
|
|
16
|
+
* Read the step number `remarkSteps` assigns to a heading.
|
|
17
|
+
*/
|
|
18
|
+
function getStep(properties) {
|
|
19
|
+
const raw = properties["data-fd-step"] ?? properties.dataFdStep;
|
|
20
|
+
if (typeof raw === "number") return raw;
|
|
21
|
+
if (typeof raw === "string" && raw.trim() !== "") {
|
|
22
|
+
const step = Number(raw);
|
|
23
|
+
if (Number.isFinite(step)) return step;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
15
26
|
function rehypeToc({ exportToc = true } = {}) {
|
|
16
27
|
if (exportToc === true) exportToc = {
|
|
17
28
|
as: "esm",
|
|
@@ -42,7 +53,7 @@ function rehypeToc({ exportToc = true } = {}) {
|
|
|
42
53
|
title: element,
|
|
43
54
|
depth: Number(element.tagName[1]),
|
|
44
55
|
url: `#${id}`,
|
|
45
|
-
_step:
|
|
56
|
+
_step: getStep(element.properties)
|
|
46
57
|
});
|
|
47
58
|
if (isTocOnly && parent && typeof idx === "number") parent.children.splice(idx, 1);
|
|
48
59
|
return "skip";
|
package/dist/toc.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { t as isEqualShallow } from "./is-equal-LdLqRs0o.js";
|
|
3
|
-
import { createContext, use, useEffect, useEffectEvent, useMemo, useRef,
|
|
3
|
+
import { createContext, use, useEffect, useEffectEvent, useMemo, useRef, useSyncExternalStore } from "react";
|
|
4
4
|
import { jsx } from "react/jsx-runtime";
|
|
5
5
|
import scrollIntoView from "scroll-into-view-if-needed";
|
|
6
6
|
//#region src/utils/merge-refs.ts
|
|
@@ -24,11 +24,7 @@ function ScrollProvider({ containerRef, children }) {
|
|
|
24
24
|
});
|
|
25
25
|
}
|
|
26
26
|
function AnchorProvider({ toc, single = false, children }) {
|
|
27
|
-
const observer = useMemo(() => new Observer(), []);
|
|
28
|
-
observer.single = single;
|
|
29
|
-
useEffect(() => {
|
|
30
|
-
observer.setItems(toc);
|
|
31
|
-
}, [observer, toc]);
|
|
27
|
+
const observer = useMemo(() => new Observer(toc, single), [toc, single]);
|
|
32
28
|
useEffect(() => {
|
|
33
29
|
observer.watch({ threshold: .9 });
|
|
34
30
|
return () => observer.unwatch();
|
|
@@ -38,45 +34,40 @@ function AnchorProvider({ toc, single = false, children }) {
|
|
|
38
34
|
children
|
|
39
35
|
});
|
|
40
36
|
}
|
|
41
|
-
function TOCItem({ ref, onActiveChange
|
|
37
|
+
function TOCItem({ ref, onActiveChange, autoScroll = true, ...props }) {
|
|
42
38
|
const id = props.href ? getItemId(props.href) : null;
|
|
43
39
|
const containerRef = use(ScrollContext);
|
|
44
40
|
const anchorRef = useRef(null);
|
|
45
|
-
const
|
|
41
|
+
const isMountedRef = useRef(false);
|
|
46
42
|
const observer = useObserver();
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
onActiveChange(itemData.active);
|
|
54
|
-
}
|
|
55
|
-
const anchor = anchorRef.current;
|
|
56
|
-
const container = containerRef?.current;
|
|
57
|
-
if (!autoScroll || !anchor || !container) return;
|
|
58
|
-
let lastActive;
|
|
59
|
-
for (const item of items) {
|
|
60
|
-
if (!item.active) continue;
|
|
61
|
-
if (!lastActive || lastActive.t < item.t) lastActive = item;
|
|
62
|
-
}
|
|
63
|
-
if (lastActive?.id === id) scrollIntoView(anchor, {
|
|
64
|
-
behavior: initial ? "instant" : "smooth",
|
|
65
|
-
block: "center",
|
|
66
|
-
inline: "center",
|
|
67
|
-
scrollMode: "always",
|
|
68
|
-
boundary: container
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
useEffect(() => {
|
|
72
|
-
if (autoScroll) onUpdate(observer.items, isInitialRef.current);
|
|
73
|
-
isInitialRef.current = false;
|
|
74
|
-
}, [observer, autoScroll]);
|
|
43
|
+
const active = useObserverValue((observer) => id !== null && observer.activeAnchors.includes(id));
|
|
44
|
+
const prevActiveRef = useRef(active);
|
|
45
|
+
if (prevActiveRef.current !== active) {
|
|
46
|
+
prevActiveRef.current = active;
|
|
47
|
+
onActiveChange?.(active);
|
|
48
|
+
}
|
|
75
49
|
useEffect(() => {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
50
|
+
if (id === null) return;
|
|
51
|
+
const callback = () => {
|
|
52
|
+
const anchor = anchorRef.current;
|
|
53
|
+
const container = containerRef?.current;
|
|
54
|
+
if (autoScroll && observer.activeAnchor === id && anchor && container) scrollIntoView(anchor, {
|
|
55
|
+
behavior: isMountedRef.current ? "smooth" : "instant",
|
|
56
|
+
block: "center",
|
|
57
|
+
inline: "center",
|
|
58
|
+
scrollMode: "always",
|
|
59
|
+
boundary: container
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
callback();
|
|
63
|
+
isMountedRef.current = true;
|
|
64
|
+
return observer.subscribe(callback);
|
|
65
|
+
}, [
|
|
66
|
+
autoScroll,
|
|
67
|
+
id,
|
|
68
|
+
observer,
|
|
69
|
+
containerRef
|
|
70
|
+
]);
|
|
80
71
|
return /* @__PURE__ */ jsx("a", {
|
|
81
72
|
ref: mergeRefs(anchorRef, ref),
|
|
82
73
|
"data-active": active,
|
|
@@ -88,6 +79,12 @@ function useObserver() {
|
|
|
88
79
|
if (!observer) throw new Error(`Component must be used under the <AnchorProvider /> component.`);
|
|
89
80
|
return observer;
|
|
90
81
|
}
|
|
82
|
+
/** subscribe to a value of the observer, it must be stable between updates */
|
|
83
|
+
function useObserverValue(get) {
|
|
84
|
+
const observer = useObserver();
|
|
85
|
+
const getSnapshot = () => get(observer);
|
|
86
|
+
return useSyncExternalStore(observer.subscribe, getSnapshot, getSnapshot);
|
|
87
|
+
}
|
|
91
88
|
/** @returns static info object, useful for custom rendering logic */
|
|
92
89
|
function useTOC() {
|
|
93
90
|
const observer = useObserver();
|
|
@@ -102,75 +99,58 @@ function useTOC() {
|
|
|
102
99
|
function useTOCListener(listener) {
|
|
103
100
|
const observer = useObserver();
|
|
104
101
|
const callback = useEffectEvent(listener);
|
|
105
|
-
useEffect(() =>
|
|
106
|
-
observer.listen(callback);
|
|
107
|
-
return () => observer.unlisten(callback);
|
|
108
|
-
}, [observer]);
|
|
102
|
+
useEffect(() => observer.subscribe(callback), [observer]);
|
|
109
103
|
}
|
|
110
104
|
function useTOCSelector(select, isEqual = isEqualShallow) {
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
105
|
+
const cache = useRef(null);
|
|
106
|
+
return useObserverValue(({ items }) => {
|
|
107
|
+
const prev = cache.current;
|
|
108
|
+
if (prev && prev.items === items && prev.select === select) return prev.value;
|
|
114
109
|
const next = select(items);
|
|
115
|
-
|
|
110
|
+
const value = prev && isEqual(prev.value, next) ? prev.value : next;
|
|
111
|
+
cache.current = {
|
|
112
|
+
items,
|
|
113
|
+
select,
|
|
114
|
+
value
|
|
115
|
+
};
|
|
116
|
+
return value;
|
|
116
117
|
});
|
|
117
|
-
return value;
|
|
118
118
|
}
|
|
119
119
|
/**
|
|
120
120
|
* The estimated active heading ID
|
|
121
121
|
*/
|
|
122
122
|
function useActiveAnchor() {
|
|
123
|
-
return
|
|
124
|
-
let out;
|
|
125
|
-
for (const item of items) {
|
|
126
|
-
if (!item.active) continue;
|
|
127
|
-
if (!out || item.t > out.t) out = item;
|
|
128
|
-
}
|
|
129
|
-
return out?.id;
|
|
130
|
-
});
|
|
123
|
+
return useObserverValue((observer) => observer.activeAnchor);
|
|
131
124
|
}
|
|
132
125
|
/**
|
|
133
126
|
* The id of visible anchors
|
|
134
127
|
*/
|
|
135
128
|
function useActiveAnchors() {
|
|
136
|
-
return
|
|
137
|
-
const out = [];
|
|
138
|
-
for (const item of items) if (item.active) out.push(item.id);
|
|
139
|
-
return out;
|
|
140
|
-
});
|
|
129
|
+
return useObserverValue((observer) => observer.activeAnchors);
|
|
141
130
|
}
|
|
142
131
|
function useItems() {
|
|
143
|
-
return
|
|
132
|
+
return useObserverValue((observer) => observer.items);
|
|
144
133
|
}
|
|
145
134
|
function getItemId(url) {
|
|
146
135
|
if (url.startsWith("#")) return url.slice(1);
|
|
147
136
|
return null;
|
|
148
137
|
}
|
|
149
138
|
var Observer = class {
|
|
150
|
-
constructor() {
|
|
151
|
-
this.
|
|
152
|
-
this.
|
|
139
|
+
constructor(toc, single) {
|
|
140
|
+
this.single = single;
|
|
141
|
+
this.activeAnchors = [];
|
|
153
142
|
this.observer = null;
|
|
154
143
|
this.listeners = /* @__PURE__ */ new Set();
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const observer = this.observer;
|
|
164
|
-
if (observer) for (const item of this.items) {
|
|
165
|
-
const element = document.getElementById(item.id);
|
|
166
|
-
if (!element) continue;
|
|
167
|
-
observer.unobserve(element);
|
|
168
|
-
}
|
|
169
|
-
const next = [];
|
|
170
|
-
for (const item of newItems) {
|
|
144
|
+
this.subscribe = (listener) => {
|
|
145
|
+
this.listeners.add(listener);
|
|
146
|
+
return () => {
|
|
147
|
+
this.listeners.delete(listener);
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
this.items = [];
|
|
151
|
+
for (const item of toc) {
|
|
171
152
|
const id = getItemId(item.url);
|
|
172
|
-
if (
|
|
173
|
-
next.push({
|
|
153
|
+
if (id) this.items.push({
|
|
174
154
|
id,
|
|
175
155
|
active: false,
|
|
176
156
|
fallback: false,
|
|
@@ -178,13 +158,21 @@ var Observer = class {
|
|
|
178
158
|
original: item
|
|
179
159
|
});
|
|
180
160
|
}
|
|
181
|
-
|
|
182
|
-
|
|
161
|
+
}
|
|
162
|
+
listen(listener) {
|
|
163
|
+
this.listeners.add(listener);
|
|
164
|
+
}
|
|
165
|
+
unlisten(listener) {
|
|
166
|
+
this.listeners.delete(listener);
|
|
183
167
|
}
|
|
184
168
|
watch(options) {
|
|
185
169
|
if (this.observer) return;
|
|
186
170
|
this.observer = new IntersectionObserver(this.callback.bind(this), options);
|
|
187
|
-
this.
|
|
171
|
+
for (const item of this.items) {
|
|
172
|
+
const element = document.getElementById(item.id);
|
|
173
|
+
if (!element) continue;
|
|
174
|
+
this.observer.observe(element);
|
|
175
|
+
}
|
|
188
176
|
}
|
|
189
177
|
unwatch() {
|
|
190
178
|
this.observer?.disconnect();
|
|
@@ -228,16 +216,17 @@ var Observer = class {
|
|
|
228
216
|
}
|
|
229
217
|
this.update(updated);
|
|
230
218
|
}
|
|
231
|
-
observeItems() {
|
|
232
|
-
if (!this.observer) return;
|
|
233
|
-
for (const item of this.items) {
|
|
234
|
-
const element = document.getElementById(item.id);
|
|
235
|
-
if (!element) continue;
|
|
236
|
-
this.observer.observe(element);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
219
|
update(next) {
|
|
220
|
+
let latest;
|
|
221
|
+
const activeAnchors = [];
|
|
222
|
+
for (const item of next) {
|
|
223
|
+
if (!item.active) continue;
|
|
224
|
+
activeAnchors.push(item.id);
|
|
225
|
+
if (!latest || item.t > latest.t) latest = item;
|
|
226
|
+
}
|
|
240
227
|
this.items = next;
|
|
228
|
+
this.activeAnchors = activeAnchors;
|
|
229
|
+
this.activeAnchor = latest?.id;
|
|
241
230
|
for (const listener of this.listeners) listener(next);
|
|
242
231
|
}
|
|
243
232
|
};
|
|
@@ -1,20 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useCallback, useSyncExternalStore } from "react";
|
|
2
2
|
//#region src/utils/use-media-query.ts
|
|
3
|
+
const getServerSnapshot = () => null;
|
|
4
|
+
/**
|
|
5
|
+
* @returns `null` on the server, during hydration, or when disabled.
|
|
6
|
+
*/
|
|
3
7
|
function useMediaQuery(query, disabled = false) {
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
handleChange();
|
|
12
|
-
mediaQueryList.addEventListener("change", handleChange);
|
|
13
|
-
return () => {
|
|
14
|
-
mediaQueryList.removeEventListener("change", handleChange);
|
|
15
|
-
};
|
|
16
|
-
}, [disabled, query]);
|
|
17
|
-
return isMatch;
|
|
8
|
+
const subscribe = useCallback((onChange) => {
|
|
9
|
+
if (disabled) return () => {};
|
|
10
|
+
const list = window.matchMedia(query);
|
|
11
|
+
list.addEventListener("change", onChange);
|
|
12
|
+
return () => list.removeEventListener("change", onChange);
|
|
13
|
+
}, [query, disabled]);
|
|
14
|
+
return useSyncExternalStore(subscribe, () => disabled ? null : window.matchMedia(query).matches, getServerSnapshot);
|
|
18
15
|
}
|
|
19
16
|
//#endregion
|
|
20
17
|
export { useMediaQuery };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fumadocs-core",
|
|
3
|
-
"version": "16.15.
|
|
3
|
+
"version": "16.15.13",
|
|
4
4
|
"description": "The React.js library for building a documentation website",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Docs",
|
|
@@ -151,12 +151,13 @@
|
|
|
151
151
|
"next": "16.3.5",
|
|
152
152
|
"path-to-regexp": "^8.4.2",
|
|
153
153
|
"react-router": "^8.4.0",
|
|
154
|
+
"rehype-raw": "^7.0.0",
|
|
154
155
|
"remark-directive": "^4.0.0",
|
|
155
156
|
"remark-mdx": "^3.1.1",
|
|
156
157
|
"tsconfig": "0.0.0",
|
|
157
158
|
"tsdown": "0.23.0",
|
|
158
159
|
"typescript": "^7.0.2",
|
|
159
|
-
"waku": "1.0.0-rc.
|
|
160
|
+
"waku": "1.0.0-rc.1",
|
|
160
161
|
"zod": "4.6.5"
|
|
161
162
|
},
|
|
162
163
|
"peerDependencies": {
|