@takazudo/zudo-doc 6.1.1 → 6.2.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 +19 -0
- package/README.md +2 -2
- package/dist/code-syntax/mermaid-init-script.d.ts +2 -2
- package/dist/code-syntax/mermaid-init-script.js +1 -1
- package/dist/compiled.css +8 -0
- package/dist/sidebar-toggle-island/index.js +5 -0
- package/dist/sidebar-tree-island/index.js +106 -90
- package/dist/sidebar-tree-island/sidebar-scroll-preserve.d.ts +10 -8
- package/dist/sidebar-tree-island/sidebar-scroll-preserve.js +424 -24
- package/dist/sidebar-utils/index.d.ts +8 -0
- package/dist/sidebar-utils/index.js +51 -6
- package/dist/theme-toggle/index.js +76 -27
- package/dist/wind.json +2 -0
- package/eject/sidebar-toggle-island/index.tsx +5 -0
- package/eject/sidebar-tree-island/index.tsx +16 -2
- package/eject/sidebar-tree-island/sidebar-scroll-preserve.ts +482 -35
- package/eject/theme-toggle/index.tsx +79 -35
- package/package.json +9 -9
- package/tsconfig.base.json +1 -2
|
@@ -3,53 +3,350 @@ import {
|
|
|
3
3
|
BEFORE_NAVIGATE_EVENT
|
|
4
4
|
} from "../transitions/index.js";
|
|
5
5
|
const installedControllers = /* @__PURE__ */ new WeakMap();
|
|
6
|
+
const HISTORY_KEY = "zdSidebarScroll";
|
|
7
|
+
const PENDING_KEY = "zd-sidebar-scroll-pending";
|
|
8
|
+
const VIEWPORTS = ["desktop", "mobile"];
|
|
9
|
+
function canonicalPath(pathname) {
|
|
10
|
+
return pathname.length > 1 && pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
|
|
11
|
+
}
|
|
6
12
|
function installSidebarScrollPreserve({
|
|
7
13
|
document: document2,
|
|
8
14
|
requestAnimationFrame,
|
|
9
|
-
cancelAnimationFrame
|
|
15
|
+
cancelAnimationFrame,
|
|
16
|
+
ResizeObserver: ResizeObserverImpl
|
|
10
17
|
}) {
|
|
11
|
-
|
|
18
|
+
const view = document2.defaultView;
|
|
19
|
+
let epoch = 0;
|
|
20
|
+
let shortRetries = 0;
|
|
21
|
+
let traverse = false;
|
|
22
|
+
let awaiting = false;
|
|
12
23
|
let restoreFrame;
|
|
13
|
-
|
|
24
|
+
let didInitial = false;
|
|
25
|
+
let writing = false;
|
|
26
|
+
let observer;
|
|
27
|
+
const observed = /* @__PURE__ */ new Set();
|
|
28
|
+
const skipped = /* @__PURE__ */ new Set();
|
|
29
|
+
const applied = /* @__PURE__ */ new Set();
|
|
30
|
+
let elements = {};
|
|
31
|
+
let memory = {};
|
|
32
|
+
let pending;
|
|
33
|
+
let held = {};
|
|
34
|
+
const cancelFrame = () => {
|
|
14
35
|
if (restoreFrame === void 0) return;
|
|
15
36
|
cancelAnimationFrame(restoreFrame);
|
|
16
37
|
restoreFrame = void 0;
|
|
17
38
|
};
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
snapshot = element ? { element, scrollTop: element.scrollTop } : void 0;
|
|
39
|
+
const disconnectObserver = () => {
|
|
40
|
+
observer?.disconnect();
|
|
41
|
+
observer = void 0;
|
|
22
42
|
};
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
43
|
+
const capture = () => {
|
|
44
|
+
const next = { ...memory };
|
|
45
|
+
for (const kind of VIEWPORTS) {
|
|
46
|
+
const element = findViewport(document2, kind);
|
|
47
|
+
if (!element) {
|
|
48
|
+
delete next[kind];
|
|
49
|
+
delete elements[kind];
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (!measurable(element)) continue;
|
|
53
|
+
if (isEmptyTree(element) && element.scrollTop === 0 && (next[kind]?.scrollTop ?? 0) > 0) continue;
|
|
54
|
+
next[kind] = readSlot(element);
|
|
55
|
+
elements[kind] = element;
|
|
56
|
+
}
|
|
57
|
+
memory = next;
|
|
58
|
+
return next;
|
|
59
|
+
};
|
|
60
|
+
const writeHistory = (slots) => {
|
|
61
|
+
if (!view || traverse) return;
|
|
62
|
+
const state = view.history.state;
|
|
63
|
+
if (!state || typeof state !== "object") return;
|
|
64
|
+
if (JSON.stringify(state.zdSidebarScroll) === JSON.stringify(slots)) return;
|
|
65
|
+
try {
|
|
66
|
+
view.history.replaceState({ ...state, [HISTORY_KEY]: slots }, "");
|
|
67
|
+
} catch {
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const schedule = () => {
|
|
71
|
+
if (restoreFrame !== void 0) return;
|
|
27
72
|
restoreFrame = requestAnimationFrame(() => {
|
|
28
73
|
restoreFrame = void 0;
|
|
29
|
-
|
|
30
|
-
if (current === saved.element) current.scrollTop = saved.scrollTop;
|
|
74
|
+
apply();
|
|
31
75
|
});
|
|
32
76
|
};
|
|
77
|
+
const arm = (kind, element) => {
|
|
78
|
+
const Observer = ResizeObserverImpl ?? view?.ResizeObserver;
|
|
79
|
+
if (!Observer || observed.has(kind)) return false;
|
|
80
|
+
observed.add(kind);
|
|
81
|
+
disconnectObserver();
|
|
82
|
+
const epochAt = epoch;
|
|
83
|
+
try {
|
|
84
|
+
const next = new Observer(() => {
|
|
85
|
+
disconnectObserver();
|
|
86
|
+
if (epochAt !== epoch) return;
|
|
87
|
+
apply();
|
|
88
|
+
});
|
|
89
|
+
next.observe(element);
|
|
90
|
+
observer = next;
|
|
91
|
+
return true;
|
|
92
|
+
} catch {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
const finishIfSettled = (waiting) => {
|
|
97
|
+
if (waiting) return;
|
|
98
|
+
pending = void 0;
|
|
99
|
+
awaiting = false;
|
|
100
|
+
writeHistory(memory);
|
|
101
|
+
};
|
|
102
|
+
const applyHeld = () => {
|
|
103
|
+
let restored = false;
|
|
104
|
+
for (const kind of VIEWPORTS) {
|
|
105
|
+
const slot = held[kind];
|
|
106
|
+
if (!slot) continue;
|
|
107
|
+
const element = findViewport(document2, kind);
|
|
108
|
+
if (!element || !measurable(element)) continue;
|
|
109
|
+
const outcome = restoreViewport(kind, element, slot);
|
|
110
|
+
if (outcome === "wait") {
|
|
111
|
+
arm(kind, element);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
memory[kind] = readSlot(element);
|
|
115
|
+
delete held[kind];
|
|
116
|
+
restored = true;
|
|
117
|
+
}
|
|
118
|
+
if (restored && !awaiting) writeHistory(memory);
|
|
119
|
+
};
|
|
120
|
+
const apply = () => {
|
|
121
|
+
if (!pending) {
|
|
122
|
+
if (Object.keys(held).length) applyHeld();
|
|
123
|
+
if (!didInitial) {
|
|
124
|
+
didInitial = true;
|
|
125
|
+
for (const kind of VIEWPORTS) {
|
|
126
|
+
const element = findViewport(document2, kind);
|
|
127
|
+
if (element && measurable(element)) revealCurrent(element, writeScroll);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
let waiting = false;
|
|
133
|
+
for (const kind of VIEWPORTS) {
|
|
134
|
+
const slot = pending[kind];
|
|
135
|
+
if (!slot || skipped.has(kind) || applied.has(kind)) continue;
|
|
136
|
+
const element = findViewport(document2, kind);
|
|
137
|
+
if (!element || !measurable(element)) {
|
|
138
|
+
if (element) {
|
|
139
|
+
held[kind] = slot;
|
|
140
|
+
memory[kind] = slot;
|
|
141
|
+
}
|
|
142
|
+
applied.add(kind);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const outcome = restoreViewport(kind, element, slot);
|
|
146
|
+
if (outcome === "wait") {
|
|
147
|
+
if (!isEmptyTree(element)) shortRetries += 1;
|
|
148
|
+
arm(kind, element);
|
|
149
|
+
waiting = true;
|
|
150
|
+
if (!isEmptyTree(element) && shortRetries < 8) schedule();
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
memory[kind] = readSlot(element);
|
|
154
|
+
applied.add(kind);
|
|
155
|
+
}
|
|
156
|
+
finishIfSettled(waiting);
|
|
157
|
+
};
|
|
158
|
+
const restoreViewport = (kind, element, slot) => {
|
|
159
|
+
const context = readContext(element);
|
|
160
|
+
if (slot.contextId) {
|
|
161
|
+
if (!context) return observed.has(kind) ? "done" : "wait";
|
|
162
|
+
if (context !== slot.contextId) {
|
|
163
|
+
revealCurrent(element, writeScroll);
|
|
164
|
+
return "done";
|
|
165
|
+
}
|
|
166
|
+
} else if (elements[kind] !== element) {
|
|
167
|
+
return "done";
|
|
168
|
+
}
|
|
169
|
+
const canHold = slot.scrollTop === 0 || element.scrollHeight - element.clientHeight + 2 >= slot.scrollTop;
|
|
170
|
+
if (!canHold) {
|
|
171
|
+
if (!isEmptyTree(element) && shortRetries >= 8) {
|
|
172
|
+
revealCurrent(element, writeScroll);
|
|
173
|
+
return "done";
|
|
174
|
+
}
|
|
175
|
+
return "wait";
|
|
176
|
+
}
|
|
177
|
+
const compatible = readViewKey(element) === slot.viewKey && readScope(element) === slot.scopeId && Math.abs(element.scrollHeight - slot.contentHeight) <= 2;
|
|
178
|
+
if (compatible) {
|
|
179
|
+
writeScroll(element, slot.scrollTop);
|
|
180
|
+
return "done";
|
|
181
|
+
}
|
|
182
|
+
if (slot.anchorId) {
|
|
183
|
+
const row = findAnchor(element, slot.anchorId);
|
|
184
|
+
if (row) {
|
|
185
|
+
const delta = row.getBoundingClientRect().top - element.getBoundingClientRect().top - (slot.anchorOffset ?? 0);
|
|
186
|
+
const next = element.scrollTop + delta;
|
|
187
|
+
if (next > element.scrollHeight - element.clientHeight + 2) return "wait";
|
|
188
|
+
writeScroll(element, next);
|
|
189
|
+
return "done";
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
revealCurrent(element, writeScroll);
|
|
193
|
+
return "done";
|
|
194
|
+
};
|
|
195
|
+
const writeScroll = (element, next) => {
|
|
196
|
+
if (Math.abs(element.scrollTop - next) <= 2) return;
|
|
197
|
+
writing = true;
|
|
198
|
+
element.scrollTop = next;
|
|
199
|
+
writing = false;
|
|
200
|
+
};
|
|
201
|
+
const begin = (slots) => {
|
|
202
|
+
epoch += 1;
|
|
203
|
+
cancelFrame();
|
|
204
|
+
disconnectObserver();
|
|
205
|
+
observed.clear();
|
|
206
|
+
skipped.clear();
|
|
207
|
+
applied.clear();
|
|
208
|
+
pending = slots;
|
|
209
|
+
held = {};
|
|
210
|
+
awaiting = true;
|
|
211
|
+
didInitial = true;
|
|
212
|
+
shortRetries = 0;
|
|
213
|
+
};
|
|
214
|
+
const onBefore = () => {
|
|
215
|
+
if (traverse) {
|
|
216
|
+
traverse = false;
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const slots = capture();
|
|
220
|
+
begin(slots);
|
|
221
|
+
awaiting = false;
|
|
222
|
+
writeHistory(slots);
|
|
223
|
+
};
|
|
224
|
+
const onAfter = () => {
|
|
225
|
+
try {
|
|
226
|
+
view?.sessionStorage.removeItem(PENDING_KEY);
|
|
227
|
+
} catch {
|
|
228
|
+
}
|
|
229
|
+
if (!pending) return;
|
|
230
|
+
awaiting = true;
|
|
231
|
+
};
|
|
232
|
+
const onPopState = () => {
|
|
233
|
+
traverse = true;
|
|
234
|
+
begin(readHistory(view) ?? {});
|
|
235
|
+
};
|
|
236
|
+
const onClick = (event) => {
|
|
237
|
+
const mouse = event;
|
|
238
|
+
if (mouse.button !== 0 || mouse.metaKey || mouse.ctrlKey || mouse.shiftKey || mouse.altKey) return;
|
|
239
|
+
const target = mouse.target;
|
|
240
|
+
if (!(target instanceof Element) || !view) return;
|
|
241
|
+
const link = target.closest("a[href]");
|
|
242
|
+
if (!(link instanceof HTMLAnchorElement) || link.target === "_blank") return;
|
|
243
|
+
if (!link.closest("#desktop-sidebar, [data-zd-mobile-sidebar]")) return;
|
|
244
|
+
let url;
|
|
245
|
+
try {
|
|
246
|
+
url = new URL(link.href, view.location.href);
|
|
247
|
+
} catch {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (url.origin !== view.location.origin) return;
|
|
251
|
+
if (url.pathname === view.location.pathname && url.search === view.location.search) return;
|
|
252
|
+
try {
|
|
253
|
+
view.sessionStorage.setItem(PENDING_KEY, JSON.stringify({ to: canonicalPath(url.pathname), slots: capture() }));
|
|
254
|
+
} catch {
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
const onUserScroll = (event) => {
|
|
258
|
+
if (writing) return;
|
|
259
|
+
const kind = viewportKind(event.target);
|
|
260
|
+
if (!kind || skipped.has(kind)) return;
|
|
261
|
+
if (!awaiting && !held[kind]) return;
|
|
262
|
+
if (event.type === "keydown") {
|
|
263
|
+
const key = event.key;
|
|
264
|
+
if (!["ArrowUp", "ArrowDown", "PageUp", "PageDown", "Home", "End", " "].includes(key)) return;
|
|
265
|
+
}
|
|
266
|
+
skipped.add(kind);
|
|
267
|
+
applied.add(kind);
|
|
268
|
+
delete held[kind];
|
|
269
|
+
cancelFrame();
|
|
270
|
+
schedule();
|
|
271
|
+
};
|
|
272
|
+
const onScroll = (event) => {
|
|
273
|
+
if (writing || traverse || awaiting) return;
|
|
274
|
+
if (!viewportKind(event.target)) return;
|
|
275
|
+
capture();
|
|
276
|
+
scheduleHistory();
|
|
277
|
+
};
|
|
278
|
+
let historyFrame;
|
|
279
|
+
const scheduleHistory = () => {
|
|
280
|
+
if (historyFrame !== void 0) return;
|
|
281
|
+
historyFrame = requestAnimationFrame(() => {
|
|
282
|
+
historyFrame = void 0;
|
|
283
|
+
writeHistory(memory);
|
|
284
|
+
});
|
|
285
|
+
};
|
|
286
|
+
const notify = (root) => {
|
|
287
|
+
if (root.ownerDocument !== document2) return;
|
|
288
|
+
if (awaiting) schedule();
|
|
289
|
+
else if (Object.keys(held).length || !didInitial) schedule();
|
|
290
|
+
};
|
|
291
|
+
const consumePendingStorage = () => {
|
|
292
|
+
if (!view) return;
|
|
293
|
+
try {
|
|
294
|
+
const raw = view.sessionStorage.getItem(PENDING_KEY);
|
|
295
|
+
if (!raw) return;
|
|
296
|
+
view.sessionStorage.removeItem(PENDING_KEY);
|
|
297
|
+
const parsed = JSON.parse(raw);
|
|
298
|
+
if (!isRecord(parsed) || typeof parsed.to !== "string") return;
|
|
299
|
+
if (canonicalPath(parsed.to) !== canonicalPath(view.location.pathname)) return;
|
|
300
|
+
const slots = sanitizeSlots(parsed.slots);
|
|
301
|
+
if (!slots) return;
|
|
302
|
+
begin(slots);
|
|
303
|
+
} catch {
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
consumePendingStorage();
|
|
307
|
+
if (!pending) {
|
|
308
|
+
const stored = readHistory(view);
|
|
309
|
+
if (stored) begin(stored);
|
|
310
|
+
}
|
|
33
311
|
document2.addEventListener(BEFORE_NAVIGATE_EVENT, onBefore);
|
|
34
312
|
document2.addEventListener(AFTER_NAVIGATE_EVENT, onAfter);
|
|
35
|
-
|
|
313
|
+
document2.addEventListener("click", onClick, true);
|
|
314
|
+
document2.addEventListener("pointerdown", onUserScroll, true);
|
|
315
|
+
document2.addEventListener("wheel", onUserScroll, true);
|
|
316
|
+
document2.addEventListener("touchmove", onUserScroll, true);
|
|
317
|
+
document2.addEventListener("keydown", onUserScroll, true);
|
|
318
|
+
document2.addEventListener("scroll", onScroll, true);
|
|
319
|
+
view?.addEventListener("popstate", onPopState);
|
|
320
|
+
const cleanup = () => {
|
|
36
321
|
document2.removeEventListener(BEFORE_NAVIGATE_EVENT, onBefore);
|
|
37
322
|
document2.removeEventListener(AFTER_NAVIGATE_EVENT, onAfter);
|
|
38
|
-
|
|
39
|
-
|
|
323
|
+
document2.removeEventListener("click", onClick, true);
|
|
324
|
+
document2.removeEventListener("pointerdown", onUserScroll, true);
|
|
325
|
+
document2.removeEventListener("wheel", onUserScroll, true);
|
|
326
|
+
document2.removeEventListener("touchmove", onUserScroll, true);
|
|
327
|
+
document2.removeEventListener("keydown", onUserScroll, true);
|
|
328
|
+
document2.removeEventListener("scroll", onScroll, true);
|
|
329
|
+
view?.removeEventListener("popstate", onPopState);
|
|
330
|
+
cancelFrame();
|
|
331
|
+
if (historyFrame !== void 0) cancelAnimationFrame(historyFrame);
|
|
332
|
+
disconnectObserver();
|
|
333
|
+
pending = void 0;
|
|
334
|
+
awaiting = false;
|
|
335
|
+
if (installedControllers.get(document2)?.cleanup === cleanup) installedControllers.delete(document2);
|
|
40
336
|
};
|
|
337
|
+
installedControllers.set(document2, { cleanup, notify });
|
|
338
|
+
return cleanup;
|
|
41
339
|
}
|
|
42
340
|
function ensureSidebarScrollPreserve(options) {
|
|
43
341
|
const resolved = options ?? resolveBrowserOptions();
|
|
44
342
|
if (!resolved || installedControllers.has(resolved.document)) return;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
);
|
|
343
|
+
installSidebarScrollPreserve(resolved);
|
|
344
|
+
}
|
|
345
|
+
function notifySidebarScrollReady(root) {
|
|
346
|
+
installedControllers.get(root.ownerDocument)?.notify(root);
|
|
49
347
|
}
|
|
50
348
|
function resolveBrowserOptions() {
|
|
51
|
-
if (typeof document === "undefined" || typeof window === "undefined")
|
|
52
|
-
return void 0;
|
|
349
|
+
if (typeof document === "undefined" || typeof window === "undefined") return void 0;
|
|
53
350
|
return {
|
|
54
351
|
document,
|
|
55
352
|
requestAnimationFrame: window.requestAnimationFrame.bind(window),
|
|
@@ -57,11 +354,114 @@ function resolveBrowserOptions() {
|
|
|
57
354
|
};
|
|
58
355
|
}
|
|
59
356
|
function disposeSidebarScrollPreserve(document2) {
|
|
60
|
-
installedControllers.get(document2)?.();
|
|
357
|
+
installedControllers.get(document2)?.cleanup();
|
|
61
358
|
installedControllers.delete(document2);
|
|
62
359
|
}
|
|
360
|
+
function findViewport(document2, kind) {
|
|
361
|
+
if (kind === "desktop") return document2.querySelector("#desktop-sidebar");
|
|
362
|
+
return document2.querySelector("aside[data-zd-mobile-sidebar] .flex-1.overflow-y-auto");
|
|
363
|
+
}
|
|
364
|
+
function measurable(element) {
|
|
365
|
+
if (element.clientHeight <= 0 || element.closest("[inert]")) return false;
|
|
366
|
+
const style = element.ownerDocument.defaultView?.getComputedStyle(element);
|
|
367
|
+
return !style || style.display !== "none" && style.visibility !== "hidden";
|
|
368
|
+
}
|
|
369
|
+
function isEmptyTree(element) {
|
|
370
|
+
return element.querySelector("a[href], [data-sidebar-focus-scope]") === null;
|
|
371
|
+
}
|
|
372
|
+
function readContext(element) {
|
|
373
|
+
const value = element.querySelector("nav")?.getAttribute("data-sidebar-context");
|
|
374
|
+
return value || void 0;
|
|
375
|
+
}
|
|
376
|
+
function readScope(element) {
|
|
377
|
+
return element.querySelector("nav")?.getAttribute("data-sidebar-scope") ?? "";
|
|
378
|
+
}
|
|
379
|
+
function readViewKey(element) {
|
|
380
|
+
const parts = [];
|
|
381
|
+
for (const node of element.querySelectorAll("[data-sidebar-focus-scope], a[href]")) {
|
|
382
|
+
const id = node.dataset.sidebarFocusScope || node.getAttribute("href");
|
|
383
|
+
if (id) parts.push(id);
|
|
384
|
+
}
|
|
385
|
+
let hash = 2166136261;
|
|
386
|
+
const serialized = parts.join("\n");
|
|
387
|
+
for (let i = 0; i < serialized.length; i++) hash = Math.imul(hash ^ serialized.charCodeAt(i), 16777619);
|
|
388
|
+
return String(hash >>> 0);
|
|
389
|
+
}
|
|
390
|
+
function readSlot(element) {
|
|
391
|
+
const anchor = captureAnchor(element);
|
|
392
|
+
return {
|
|
393
|
+
scrollTop: element.scrollTop,
|
|
394
|
+
...anchor,
|
|
395
|
+
contextId: readContext(element),
|
|
396
|
+
scopeId: readScope(element),
|
|
397
|
+
viewKey: readViewKey(element),
|
|
398
|
+
contentHeight: element.scrollHeight
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
function captureAnchor(element) {
|
|
402
|
+
const bounds = element.getBoundingClientRect();
|
|
403
|
+
for (const row of element.querySelectorAll("[data-sidebar-focus-scope], a[href]")) {
|
|
404
|
+
const rect = row.getBoundingClientRect();
|
|
405
|
+
if (rect.height === 0 && rect.top === 0 && rect.bottom === 0) continue;
|
|
406
|
+
if (rect.bottom > bounds.top && rect.top < bounds.bottom) {
|
|
407
|
+
const anchorId = row.dataset.sidebarFocusScope || row.getAttribute("href") || void 0;
|
|
408
|
+
return anchorId ? { anchorId, anchorOffset: rect.top - bounds.top } : {};
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return {};
|
|
412
|
+
}
|
|
413
|
+
function findAnchor(element, id) {
|
|
414
|
+
for (const node of element.querySelectorAll("[data-sidebar-focus-scope], a[href]")) {
|
|
415
|
+
if (node.dataset.sidebarFocusScope === id || node.getAttribute("href") === id) return node;
|
|
416
|
+
}
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
function revealCurrent(element, write) {
|
|
420
|
+
const current = element.querySelector('[aria-current="page"]');
|
|
421
|
+
if (!current) return;
|
|
422
|
+
const bounds = element.getBoundingClientRect();
|
|
423
|
+
const row = current.getBoundingClientRect();
|
|
424
|
+
if (row.height === 0 && row.top === 0 && row.bottom === 0) return;
|
|
425
|
+
if (row.top < bounds.top) write(element, element.scrollTop + row.top - bounds.top - 8);
|
|
426
|
+
else if (row.bottom > bounds.bottom) write(element, element.scrollTop + row.bottom - bounds.bottom + 8);
|
|
427
|
+
}
|
|
428
|
+
function viewportKind(target) {
|
|
429
|
+
if (!(target instanceof Element)) return void 0;
|
|
430
|
+
if (target.id === "desktop-sidebar" || target.closest("#desktop-sidebar")) return "desktop";
|
|
431
|
+
if (target.closest("[data-zd-mobile-sidebar]")) return "mobile";
|
|
432
|
+
return void 0;
|
|
433
|
+
}
|
|
434
|
+
function isRecord(value) {
|
|
435
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
436
|
+
}
|
|
437
|
+
function sanitizeSlots(value) {
|
|
438
|
+
if (!isRecord(value)) return void 0;
|
|
439
|
+
const slots = {};
|
|
440
|
+
for (const kind of VIEWPORTS) {
|
|
441
|
+
const slot = value[kind];
|
|
442
|
+
if (!isRecord(slot) || typeof slot.scrollTop !== "number" || !Number.isFinite(slot.scrollTop)) continue;
|
|
443
|
+
if (typeof slot.viewKey !== "string" || typeof slot.scopeId !== "string" || typeof slot.contentHeight !== "number") continue;
|
|
444
|
+
const next = {
|
|
445
|
+
scrollTop: slot.scrollTop,
|
|
446
|
+
scopeId: slot.scopeId,
|
|
447
|
+
viewKey: slot.viewKey,
|
|
448
|
+
contentHeight: slot.contentHeight
|
|
449
|
+
};
|
|
450
|
+
if (typeof slot.contextId === "string") next.contextId = slot.contextId;
|
|
451
|
+
if (typeof slot.anchorId === "string") next.anchorId = slot.anchorId;
|
|
452
|
+
if (typeof slot.anchorOffset === "number") next.anchorOffset = slot.anchorOffset;
|
|
453
|
+
slots[kind] = next;
|
|
454
|
+
}
|
|
455
|
+
return slots;
|
|
456
|
+
}
|
|
457
|
+
function readHistory(view) {
|
|
458
|
+
const state = view?.history.state;
|
|
459
|
+
if (!isRecord(state)) return void 0;
|
|
460
|
+
return sanitizeSlots(state[HISTORY_KEY]);
|
|
461
|
+
}
|
|
63
462
|
export {
|
|
64
463
|
disposeSidebarScrollPreserve,
|
|
65
464
|
ensureSidebarScrollPreserve,
|
|
66
|
-
installSidebarScrollPreserve
|
|
465
|
+
installSidebarScrollPreserve,
|
|
466
|
+
notifySidebarScrollReady
|
|
67
467
|
};
|
|
@@ -36,6 +36,14 @@ export interface SidebarDefinition {
|
|
|
36
36
|
* @default "children"
|
|
37
37
|
*/
|
|
38
38
|
initialPathMode?: "children" | "node";
|
|
39
|
+
/**
|
|
40
|
+
* Highest tree Broaden can reach. `"editorial"` keeps the current forest,
|
|
41
|
+
* including the full automatic tree where that is already reachable.
|
|
42
|
+
* `"configured"` stops at the authored forest. `initialPath` stays the
|
|
43
|
+
* starting view and the Restore baseline.
|
|
44
|
+
* @default "editorial"
|
|
45
|
+
*/
|
|
46
|
+
scopeRoot?: "configured" | "editorial";
|
|
39
47
|
/** Share across sections using the same editorial tree. Locale/version are added by chrome. */
|
|
40
48
|
contextId?: string;
|
|
41
49
|
}
|
|
@@ -78,11 +78,48 @@ function buildSidebarForSection(docs, lang, categoryMatch, categoryMeta, sidebar
|
|
|
78
78
|
const tree = buildNavTree(docs, lang, categoryMeta);
|
|
79
79
|
const sectionKey = categoryMatch ?? "default";
|
|
80
80
|
const config = sidebars[sectionKey];
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
const definition = config && !Array.isArray(config) ? config : void 0;
|
|
82
|
+
scopeRootOf(definition, sectionKey);
|
|
83
|
+
const items = Array.isArray(config) ? config : definition?.items;
|
|
84
|
+
const forest = items?.length ? resolveItems(items, tree, lang) : filterByCategory(tree, categoryMatch, explicitCategoryPrefixes);
|
|
85
|
+
return followInitialPath(forest, definition, sectionKey).nodes;
|
|
86
|
+
}
|
|
87
|
+
function scopeRootOf(definition, sectionKey) {
|
|
88
|
+
const value = definition?.scopeRoot;
|
|
89
|
+
if (value === void 0 || value === "editorial") return "editorial";
|
|
90
|
+
if (value === "configured") return "configured";
|
|
91
|
+
throw new Error(`Invalid sidebar scopeRoot for ${sectionKey}`);
|
|
92
|
+
}
|
|
93
|
+
function retainForest(roots, parents) {
|
|
94
|
+
const ids = /* @__PURE__ */ new Set();
|
|
95
|
+
const visit = (nodes) => {
|
|
96
|
+
for (const node of nodes) {
|
|
97
|
+
if (node.occurrenceId) ids.add(node.occurrenceId);
|
|
98
|
+
visit(node.children);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
visit(roots);
|
|
102
|
+
for (const id of Object.keys(parents)) if (!ids.has(id)) delete parents[id];
|
|
103
|
+
for (const root of roots) if (root.occurrenceId) parents[root.occurrenceId] = null;
|
|
104
|
+
}
|
|
105
|
+
function applyConfiguredCap(items, categoryMatch, prefixes, automatic, state, parents) {
|
|
106
|
+
if (!items?.length) {
|
|
107
|
+
if (!categoryMatch) return;
|
|
108
|
+
state.roots = filterByCategory(automatic, categoryMatch, prefixes);
|
|
109
|
+
state.nodes = state.roots;
|
|
110
|
+
retainForest(state.roots, parents);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const only = items.length === 1 && typeof items[0] !== "string" ? items[0] : void 0;
|
|
114
|
+
if (only?.type === "autogenerated") {
|
|
115
|
+
if (!only.dirName) return;
|
|
116
|
+
const source = findNode(automatic, only.dirName);
|
|
117
|
+
state.roots = source ? [source] : [];
|
|
118
|
+
state.nodes = source?.children ?? [];
|
|
119
|
+
retainForest(state.roots, parents);
|
|
120
|
+
return;
|
|
83
121
|
}
|
|
84
|
-
|
|
85
|
-
return followInitialPath(resolveItems(config.items, tree, lang), config, sectionKey).nodes;
|
|
122
|
+
retainForest(state.roots, parents);
|
|
86
123
|
}
|
|
87
124
|
function followInitialPath(forest, definition, sectionKey) {
|
|
88
125
|
const mode = definition?.initialPathMode;
|
|
@@ -108,8 +145,10 @@ function buildSidebarNavigation(...args) {
|
|
|
108
145
|
}
|
|
109
146
|
}
|
|
110
147
|
const tree = buildTree(docs, lang, meta);
|
|
111
|
-
const
|
|
148
|
+
const sectionKey = categoryMatch ?? "default";
|
|
149
|
+
const config = sidebars[sectionKey];
|
|
112
150
|
const definition = config && !Array.isArray(config) ? config : void 0;
|
|
151
|
+
const scopeRoot = scopeRootOf(definition, sectionKey);
|
|
113
152
|
const items = Array.isArray(config) ? config : definition?.items;
|
|
114
153
|
const parents = {};
|
|
115
154
|
function identify(nodes2, prefix, parent) {
|
|
@@ -165,7 +204,13 @@ function buildSidebarNavigation(...args) {
|
|
|
165
204
|
roots = result.canonical;
|
|
166
205
|
nodes = result.visible;
|
|
167
206
|
}
|
|
168
|
-
|
|
207
|
+
if (scopeRoot === "configured") {
|
|
208
|
+
const state = { roots, nodes };
|
|
209
|
+
applyConfiguredCap(items, categoryMatch, prefixes, automatic, state, parents);
|
|
210
|
+
roots = state.roots;
|
|
211
|
+
nodes = state.nodes;
|
|
212
|
+
}
|
|
213
|
+
const { nodes: local, target } = followInitialPath(nodes, definition, sectionKey);
|
|
169
214
|
nodes = local;
|
|
170
215
|
const localParentId = definition?.initialPathMode === "node" && target?.occurrenceId ? parents[target.occurrenceId] ?? void 0 : target?.occurrenceId;
|
|
171
216
|
let hash = 2166136261;
|