@takazudo/zudo-doc 9.0.0 → 9.1.1
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 +24 -0
- package/README.md +5 -4
- package/dist/compiled.css +109 -3
- package/dist/config-assertions/index.d.ts +2 -2
- package/dist/design-token-panel-bootstrap-island.d.ts +1 -1
- package/dist/features.css +110 -0
- package/dist/html-preview-wrapper/preview-base.js +2 -2
- package/dist/icons/index.d.ts +2 -0
- package/dist/icons/index.js +36 -0
- package/dist/nav-scope/index.d.ts +13 -1
- package/dist/nav-scope/index.js +17 -13
- package/dist/page-loading/page-loading-overlay.d.ts +5 -1
- package/dist/page-loading/page-loading-overlay.js +1 -1
- package/dist/page-loading.css +2 -1
- package/dist/plugins/zdtp-loader.d.ts +1 -0
- package/dist/plugins/zdtp-loader.js +5 -1
- package/dist/sidebar-tree-island/branch-preview.d.ts +19 -0
- package/dist/sidebar-tree-island/branch-preview.js +199 -0
- package/dist/sidebar-tree-island/index.js +82 -33
- package/dist/sidebar-tree-island/scope-state.d.ts +15 -6
- package/dist/sidebar-tree-island/scope-state.js +30 -9
- package/dist/sidebar-utils/index.js +4 -4
- package/dist/site-schema/nav-tree.js +2 -1
- package/dist/tree-nav-shared/index.d.ts +5 -1
- package/dist/tree-nav-shared/index.js +7 -3
- package/dist/wind.json +6 -1
- package/dist/zdtp-loader.d.ts +1 -1
- package/eject/header/gen-nav-overflow-script.mjs +2 -2
- package/eject/page-loading/page-loading-overlay.tsx +7 -3
- package/eject/sidebar-tree-island/branch-preview.ts +247 -0
- package/eject/sidebar-tree-island/index.tsx +86 -19
- package/eject/sidebar-tree-island/scope-state.ts +40 -10
- package/package.json +14 -13
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { computed, signal } from "@takazudo/zfb/zudo-react";
|
|
2
|
+
const TIP_ATTR = "data-sidebar-tip";
|
|
3
|
+
const TIP_DETAIL_ATTR = "data-sidebar-tip-detail";
|
|
4
|
+
const TRIGGER_SELECTOR = `[${TIP_ATTR}]`;
|
|
5
|
+
const FRAME_SELECTOR = "[data-sidebar-focus]";
|
|
6
|
+
const POINTER_SHOW_DELAY_MS = 250;
|
|
7
|
+
const HIDE_DELAY_MS = 120;
|
|
8
|
+
const GAP_PX = 6;
|
|
9
|
+
const VIEWPORT_MARGIN_PX = 8;
|
|
10
|
+
let instanceCount = 0;
|
|
11
|
+
const isHoverPointer = (event) => {
|
|
12
|
+
const type = event.pointerType;
|
|
13
|
+
return type === "mouse" || type === "pen";
|
|
14
|
+
};
|
|
15
|
+
const isFocusVisible = (element) => {
|
|
16
|
+
try {
|
|
17
|
+
return element.matches(":focus-visible");
|
|
18
|
+
} catch {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
const isActionableFrame = (element) => element.matches(FRAME_SELECTOR) && element.getAttribute("aria-disabled") !== "true";
|
|
23
|
+
const previewKey = (element) => isActionableFrame(element) ? element.dataset.sidebarFocusScope || null : null;
|
|
24
|
+
function createBranchPreview(navRef) {
|
|
25
|
+
const pointerKey = signal(null);
|
|
26
|
+
const focusKey = signal(null);
|
|
27
|
+
const active = computed(() => pointerKey.value ?? focusKey.value);
|
|
28
|
+
let tooltip = null;
|
|
29
|
+
let owner = null;
|
|
30
|
+
let showTimer;
|
|
31
|
+
let hideTimer;
|
|
32
|
+
const hideTooltip = () => {
|
|
33
|
+
clearTimeout(showTimer);
|
|
34
|
+
clearTimeout(hideTimer);
|
|
35
|
+
if (owner?.getAttribute("aria-describedby") === tooltip?.id) owner?.removeAttribute("aria-describedby");
|
|
36
|
+
owner = null;
|
|
37
|
+
if (tooltip) tooltip.hidden = true;
|
|
38
|
+
};
|
|
39
|
+
const clear = () => {
|
|
40
|
+
pointerKey.value = null;
|
|
41
|
+
focusKey.value = null;
|
|
42
|
+
hideTooltip();
|
|
43
|
+
};
|
|
44
|
+
const place = (trigger, tip) => {
|
|
45
|
+
tip.style.left = "0px";
|
|
46
|
+
tip.style.top = "0px";
|
|
47
|
+
const anchor = trigger.getBoundingClientRect();
|
|
48
|
+
const box = tip.getBoundingClientRect();
|
|
49
|
+
const maxLeft = window.innerWidth - box.width - VIEWPORT_MARGIN_PX;
|
|
50
|
+
const edge = Math.max(anchor.right, navRef.current?.getBoundingClientRect().right ?? anchor.right);
|
|
51
|
+
let left = edge + GAP_PX;
|
|
52
|
+
let top = anchor.top + anchor.height / 2 - box.height / 2;
|
|
53
|
+
if (left > maxLeft) {
|
|
54
|
+
left = Math.max(VIEWPORT_MARGIN_PX, Math.min(anchor.left + anchor.width / 2 - box.width / 2, maxLeft));
|
|
55
|
+
top = anchor.bottom + GAP_PX;
|
|
56
|
+
if (top + box.height > window.innerHeight - VIEWPORT_MARGIN_PX) top = anchor.top - GAP_PX - box.height;
|
|
57
|
+
}
|
|
58
|
+
top = Math.max(VIEWPORT_MARGIN_PX, Math.min(top, window.innerHeight - box.height - VIEWPORT_MARGIN_PX));
|
|
59
|
+
tip.style.left = `${Math.round(left)}px`;
|
|
60
|
+
tip.style.top = `${Math.round(top)}px`;
|
|
61
|
+
};
|
|
62
|
+
const showTooltip = (trigger) => {
|
|
63
|
+
if (!tooltip) return;
|
|
64
|
+
clearTimeout(hideTimer);
|
|
65
|
+
if (owner && owner !== trigger && owner.getAttribute("aria-describedby") === tooltip.id) owner.removeAttribute("aria-describedby");
|
|
66
|
+
owner = trigger;
|
|
67
|
+
const title = document.createElement("strong");
|
|
68
|
+
title.textContent = trigger.getAttribute(TIP_ATTR) ?? "";
|
|
69
|
+
const parts = [title];
|
|
70
|
+
const detail = trigger.getAttribute(TIP_DETAIL_ATTR);
|
|
71
|
+
if (detail) {
|
|
72
|
+
const line = document.createElement("span");
|
|
73
|
+
line.textContent = detail;
|
|
74
|
+
parts.push(line);
|
|
75
|
+
}
|
|
76
|
+
tooltip.replaceChildren(...parts);
|
|
77
|
+
tooltip.hidden = false;
|
|
78
|
+
trigger.setAttribute("aria-describedby", tooltip.id);
|
|
79
|
+
place(trigger, tooltip);
|
|
80
|
+
};
|
|
81
|
+
const scheduleHide = () => {
|
|
82
|
+
clearTimeout(showTimer);
|
|
83
|
+
clearTimeout(hideTimer);
|
|
84
|
+
hideTimer = setTimeout(hideTooltip, HIDE_DELAY_MS);
|
|
85
|
+
};
|
|
86
|
+
const triggerOf = (target) => {
|
|
87
|
+
const nav = navRef.current;
|
|
88
|
+
if (!nav || !(target instanceof Element)) return null;
|
|
89
|
+
const trigger = target.closest(TRIGGER_SELECTOR);
|
|
90
|
+
return trigger && nav.contains(trigger) ? trigger : null;
|
|
91
|
+
};
|
|
92
|
+
const activate = () => {
|
|
93
|
+
const tip = document.createElement("div");
|
|
94
|
+
tip.id = `zd-sidebar-tip-${++instanceCount}`;
|
|
95
|
+
tip.setAttribute("role", "tooltip");
|
|
96
|
+
tip.setAttribute("data-sidebar-tooltip", "");
|
|
97
|
+
tip.hidden = true;
|
|
98
|
+
document.body.append(tip);
|
|
99
|
+
tooltip = tip;
|
|
100
|
+
const onPointerOver = (event) => {
|
|
101
|
+
const trigger = triggerOf(event.target);
|
|
102
|
+
if (!trigger || !isHoverPointer(event) || trigger.contains(event.relatedTarget)) return;
|
|
103
|
+
pointerKey.value = previewKey(trigger);
|
|
104
|
+
clearTimeout(hideTimer);
|
|
105
|
+
clearTimeout(showTimer);
|
|
106
|
+
if (owner === trigger && !tip.hidden) return;
|
|
107
|
+
showTimer = setTimeout(() => showTooltip(trigger), POINTER_SHOW_DELAY_MS);
|
|
108
|
+
};
|
|
109
|
+
const onPointerOut = (event) => {
|
|
110
|
+
const trigger = triggerOf(event.target);
|
|
111
|
+
const next = event.relatedTarget;
|
|
112
|
+
if (!trigger || trigger.contains(next)) return;
|
|
113
|
+
pointerKey.value = null;
|
|
114
|
+
if (next && tip.contains(next)) return;
|
|
115
|
+
if (owner === trigger && trigger === document.activeElement && isFocusVisible(trigger)) return;
|
|
116
|
+
scheduleHide();
|
|
117
|
+
};
|
|
118
|
+
const onFocusIn = (event) => {
|
|
119
|
+
const trigger = triggerOf(event.target);
|
|
120
|
+
if (!trigger || !isFocusVisible(trigger)) return;
|
|
121
|
+
focusKey.value = previewKey(trigger);
|
|
122
|
+
clearTimeout(showTimer);
|
|
123
|
+
showTooltip(trigger);
|
|
124
|
+
};
|
|
125
|
+
const onFocusOut = (event) => {
|
|
126
|
+
const trigger = triggerOf(event.target);
|
|
127
|
+
if (!trigger) return;
|
|
128
|
+
focusKey.value = null;
|
|
129
|
+
if (owner === trigger) scheduleHide();
|
|
130
|
+
};
|
|
131
|
+
const onTipEnter = () => clearTimeout(hideTimer);
|
|
132
|
+
const onTipLeave = (event) => {
|
|
133
|
+
const next = event.relatedTarget;
|
|
134
|
+
if (owner && next && owner.contains(next)) return;
|
|
135
|
+
if (owner && owner === document.activeElement && isFocusVisible(owner)) return;
|
|
136
|
+
scheduleHide();
|
|
137
|
+
};
|
|
138
|
+
const onKeyDown = (event) => {
|
|
139
|
+
const key = event;
|
|
140
|
+
if (key.key !== "Escape" || key.isComposing || tip.hidden) return;
|
|
141
|
+
const target = key.target;
|
|
142
|
+
if (target instanceof Element && target !== document.body && !navRef.current?.contains(target) && !tip.contains(target)) {
|
|
143
|
+
hideTooltip();
|
|
144
|
+
pointerKey.value = null;
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
key.preventDefault();
|
|
148
|
+
key.stopPropagation();
|
|
149
|
+
hideTooltip();
|
|
150
|
+
pointerKey.value = null;
|
|
151
|
+
};
|
|
152
|
+
let scrollFrame = 0;
|
|
153
|
+
const onScroll = () => {
|
|
154
|
+
if (tip.hidden || scrollFrame) return;
|
|
155
|
+
scrollFrame = requestAnimationFrame(() => {
|
|
156
|
+
scrollFrame = 0;
|
|
157
|
+
if (tip.hidden) return;
|
|
158
|
+
if (owner?.isConnected) place(owner, tip);
|
|
159
|
+
else hideTooltip();
|
|
160
|
+
});
|
|
161
|
+
};
|
|
162
|
+
const drawer = navRef.current ? navRef.current.closest("[data-zd-mobile-sidebar]") : document.querySelector("[data-zd-mobile-sidebar]");
|
|
163
|
+
const observer = drawer ? new MutationObserver(() => {
|
|
164
|
+
if (drawer.hasAttribute("inert")) clear();
|
|
165
|
+
}) : void 0;
|
|
166
|
+
observer?.observe(drawer, { attributes: true, attributeFilter: ["inert"] });
|
|
167
|
+
const media = window.matchMedia?.("(min-width: 1024px)");
|
|
168
|
+
const onBreakpoint = () => clear();
|
|
169
|
+
media?.addEventListener?.("change", onBreakpoint);
|
|
170
|
+
document.addEventListener("pointerover", onPointerOver);
|
|
171
|
+
document.addEventListener("pointerout", onPointerOut);
|
|
172
|
+
document.addEventListener("focusin", onFocusIn);
|
|
173
|
+
document.addEventListener("focusout", onFocusOut);
|
|
174
|
+
document.addEventListener("keydown", onKeyDown, true);
|
|
175
|
+
document.addEventListener("scroll", onScroll, true);
|
|
176
|
+
tip.addEventListener("pointerenter", onTipEnter);
|
|
177
|
+
tip.addEventListener("pointerleave", onTipLeave);
|
|
178
|
+
return () => {
|
|
179
|
+
cancelAnimationFrame(scrollFrame);
|
|
180
|
+
clear();
|
|
181
|
+
observer?.disconnect();
|
|
182
|
+
media?.removeEventListener?.("change", onBreakpoint);
|
|
183
|
+
document.removeEventListener("pointerover", onPointerOver);
|
|
184
|
+
document.removeEventListener("pointerout", onPointerOut);
|
|
185
|
+
document.removeEventListener("focusin", onFocusIn);
|
|
186
|
+
document.removeEventListener("focusout", onFocusOut);
|
|
187
|
+
document.removeEventListener("keydown", onKeyDown, true);
|
|
188
|
+
document.removeEventListener("scroll", onScroll, true);
|
|
189
|
+
tip.remove();
|
|
190
|
+
tooltip = null;
|
|
191
|
+
};
|
|
192
|
+
};
|
|
193
|
+
return { active, clear, activate };
|
|
194
|
+
}
|
|
195
|
+
export {
|
|
196
|
+
TIP_ATTR,
|
|
197
|
+
TIP_DETAIL_ATTR,
|
|
198
|
+
createBranchPreview
|
|
199
|
+
};
|
|
@@ -10,15 +10,17 @@ import {
|
|
|
10
10
|
import {
|
|
11
11
|
INDENT,
|
|
12
12
|
BASE_PAD,
|
|
13
|
+
CONNECTOR_OFFSET,
|
|
13
14
|
connectorLeft,
|
|
14
15
|
ConnectorLines,
|
|
15
16
|
CategoryLinkIcon
|
|
16
17
|
} from "../tree-nav-shared/index.js";
|
|
17
|
-
import { ChevronRight, ChevronLeft, Search } from "../icons/index.js";
|
|
18
|
+
import { ChevronRight, ChevronLeft, Search, FocusFrame, RestoreArrow } from "../icons/index.js";
|
|
18
19
|
import { ThemeToggle } from "../theme-toggle/index.js";
|
|
19
20
|
import { smartBreakToHtml } from "../smart-break/index.js";
|
|
20
21
|
import { AFTER_NAVIGATE_EVENT } from "../transitions/index.js";
|
|
21
22
|
import { useSidebarScope } from "./scope-state.js";
|
|
23
|
+
import { createBranchPreview } from "./branch-preview.js";
|
|
22
24
|
import { filterTree } from "../sidebar-filter/index.js";
|
|
23
25
|
import { findActiveSlug, normalizePath } from "../sidebar-active-slug/index.js";
|
|
24
26
|
import {
|
|
@@ -36,6 +38,7 @@ import {
|
|
|
36
38
|
import { formatMonthDay } from "../format-date/index.js";
|
|
37
39
|
const STORAGE_KEY = "zd-sidebar-open";
|
|
38
40
|
const GROUPED_TRAY_ITEM_DEPTH = 3;
|
|
41
|
+
const TOGGLE_BOX_SIZE = "1.5rem";
|
|
39
42
|
function ToggleChevron({
|
|
40
43
|
isExpanded,
|
|
41
44
|
className
|
|
@@ -56,6 +59,9 @@ function padLeft(depth, forCategory) {
|
|
|
56
59
|
return `calc(${BASE_PAD} + ${forCategory ? "0.15rem" : "0rem"})`;
|
|
57
60
|
return `calc(${depth} * ${INDENT} + 1.25rem + 5px)`;
|
|
58
61
|
}
|
|
62
|
+
function toggleGuideLeft(depth) {
|
|
63
|
+
return `calc(${padLeft(depth, true)} + ${TOGGLE_BOX_SIZE} / 2 - 0.5px)`;
|
|
64
|
+
}
|
|
59
65
|
function getOpenSet() {
|
|
60
66
|
try {
|
|
61
67
|
const raw = sessionStorage.getItem(STORAGE_KEY);
|
|
@@ -207,13 +213,31 @@ function SidebarTree({
|
|
|
207
213
|
const scope = getScope();
|
|
208
214
|
const activeSlug = useActiveSlug(navigation?.roots ?? nodes, currentSlug, currentPath);
|
|
209
215
|
const query = signal("");
|
|
210
|
-
const { selected, selectedNodes, broader, labels, canRestore, changeScope, save, navRef, scopeControls } = useSidebarScope({
|
|
216
|
+
const { selected, selectedNodes, broader, labels, canRestore, changeScope, save, navRef, scopeControls: baseScopeControls } = useSidebarScope({
|
|
211
217
|
nodes,
|
|
212
218
|
navigation,
|
|
213
219
|
activeSlug,
|
|
214
220
|
query,
|
|
215
221
|
locale: localeProp ?? localeLinks?.find((link) => link.active)?.code ?? "en"
|
|
216
222
|
});
|
|
223
|
+
const branchPreview = createBranchPreview(navRef);
|
|
224
|
+
const scopeControls = baseScopeControls ? { ...baseScopeControls, preview: branchPreview.active } : void 0;
|
|
225
|
+
if (navigation) {
|
|
226
|
+
scope.onActivate(() => {
|
|
227
|
+
const dispose = branchPreview.activate();
|
|
228
|
+
const onNavigate = () => branchPreview.clear();
|
|
229
|
+
document.addEventListener(AFTER_NAVIGATE_EVENT, onNavigate);
|
|
230
|
+
return () => {
|
|
231
|
+
document.removeEventListener(AFTER_NAVIGATE_EVENT, onNavigate);
|
|
232
|
+
dispose();
|
|
233
|
+
};
|
|
234
|
+
});
|
|
235
|
+
scope.effect(() => {
|
|
236
|
+
void selected.value;
|
|
237
|
+
void query.value;
|
|
238
|
+
branchPreview.clear();
|
|
239
|
+
});
|
|
240
|
+
}
|
|
217
241
|
const showingRootMenu = signal(false);
|
|
218
242
|
const filterRef = { current: null };
|
|
219
243
|
const filterPlaceholder = signal("Filter...");
|
|
@@ -330,15 +354,11 @@ function SidebarTree({
|
|
|
330
354
|
type: "button",
|
|
331
355
|
"data-sidebar-restore": true,
|
|
332
356
|
"on:click": () => changeScope(null),
|
|
333
|
-
title: labels.restore,
|
|
334
357
|
"aria-label": labels.restore,
|
|
358
|
+
"data-sidebar-tip": labels.restoreTip,
|
|
335
359
|
style: "width:40px;height:40px",
|
|
336
|
-
class: "grid place-items-center shrink-0 rounded text-muted hover:text-
|
|
337
|
-
children: /* @__PURE__ */
|
|
338
|
-
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
|
|
339
|
-
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "6" }),
|
|
340
|
-
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "2" })
|
|
341
|
-
] })
|
|
360
|
+
class: "grid place-items-center shrink-0 rounded text-muted hover:text-accent hover:bg-surface focus-visible:text-accent focus-visible:bg-surface focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent",
|
|
361
|
+
children: /* @__PURE__ */ jsx(RestoreArrow, { class: "h-[1.0625rem] w-[1.0625rem]" })
|
|
342
362
|
}
|
|
343
363
|
) })
|
|
344
364
|
] }) }) }) : null,
|
|
@@ -478,6 +498,7 @@ function TrayItem({
|
|
|
478
498
|
isLast,
|
|
479
499
|
showDate = false,
|
|
480
500
|
depth = 1,
|
|
501
|
+
guideLeft,
|
|
481
502
|
locale,
|
|
482
503
|
dateFormats
|
|
483
504
|
}) {
|
|
@@ -491,7 +512,9 @@ function TrayItem({
|
|
|
491
512
|
{
|
|
492
513
|
depth,
|
|
493
514
|
isLast,
|
|
494
|
-
topPad: "var(--spacing-vsp-2xs)"
|
|
515
|
+
topPad: "var(--spacing-vsp-2xs)",
|
|
516
|
+
left: guideLeft,
|
|
517
|
+
width: guideLeft ? `calc(${padLeft(depth, false)} - ${CONNECTOR_OFFSET} - ${guideLeft})` : void 0
|
|
495
518
|
}
|
|
496
519
|
),
|
|
497
520
|
/* @__PURE__ */ jsxs(
|
|
@@ -536,14 +559,16 @@ function TrayItem({
|
|
|
536
559
|
function ConnectorLinesLive({
|
|
537
560
|
depth,
|
|
538
561
|
isLast,
|
|
539
|
-
topPad
|
|
562
|
+
topPad,
|
|
563
|
+
left,
|
|
564
|
+
width
|
|
540
565
|
}) {
|
|
541
566
|
return /* @__PURE__ */ jsx(
|
|
542
567
|
Show,
|
|
543
568
|
{
|
|
544
569
|
when: isLast,
|
|
545
|
-
fallback: () => /* @__PURE__ */ jsx(ConnectorLines, { depth, isLast: false, topPad }),
|
|
546
|
-
children: () => /* @__PURE__ */ jsx(ConnectorLines, { depth, isLast: true, topPad })
|
|
570
|
+
fallback: () => /* @__PURE__ */ jsx(ConnectorLines, { depth, isLast: false, topPad, left, width }),
|
|
571
|
+
children: () => /* @__PURE__ */ jsx(ConnectorLines, { depth, isLast: true, topPad, left, width })
|
|
547
572
|
}
|
|
548
573
|
);
|
|
549
574
|
}
|
|
@@ -647,20 +672,34 @@ function TrayGroupNode({
|
|
|
647
672
|
}
|
|
648
673
|
)
|
|
649
674
|
] }),
|
|
650
|
-
/* @__PURE__ */ jsx(Show, { when: expanded, children: () => /* @__PURE__ */
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
675
|
+
/* @__PURE__ */ jsx(Show, { when: expanded, children: () => /* @__PURE__ */ jsxs("div", { class: "relative", children: [
|
|
676
|
+
/* @__PURE__ */ jsx(
|
|
677
|
+
"div",
|
|
678
|
+
{
|
|
679
|
+
class: "absolute border-l border-dashed border-muted",
|
|
680
|
+
style: {
|
|
681
|
+
left: toggleGuideLeft(1),
|
|
682
|
+
top: "calc(-1 * var(--spacing-vsp-xs))",
|
|
683
|
+
height: "var(--spacing-vsp-xs)"
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
),
|
|
687
|
+
/* @__PURE__ */ jsx(For, { each: items, by: (item) => item.slug, children: (item, index) => /* @__PURE__ */ jsx(
|
|
688
|
+
TrayItem,
|
|
689
|
+
{
|
|
690
|
+
item,
|
|
691
|
+
currentSlug,
|
|
692
|
+
isLast: computed(
|
|
693
|
+
() => index.value === items.value.length - 1
|
|
694
|
+
),
|
|
695
|
+
showDate: true,
|
|
696
|
+
locale,
|
|
697
|
+
dateFormats,
|
|
698
|
+
depth: GROUPED_TRAY_ITEM_DEPTH,
|
|
699
|
+
guideLeft: toggleGuideLeft(1)
|
|
700
|
+
}
|
|
701
|
+
) })
|
|
702
|
+
] }) })
|
|
664
703
|
]
|
|
665
704
|
}
|
|
666
705
|
);
|
|
@@ -753,6 +792,9 @@ function CategoryNode({
|
|
|
753
792
|
if (scopeControls) open.value = scopeControls.isOpen(occurrenceKey(), scopeControls.defaultOpen(node.value));
|
|
754
793
|
});
|
|
755
794
|
const expanded = computed(() => forceOpen.value || open.value);
|
|
795
|
+
const focusedRoot = computed(() => !!scopeControls?.isFocusedRoot(node.value.occurrenceId));
|
|
796
|
+
const previewed = computed(() => !!node.value.occurrenceId && scopeControls?.preview.value === node.value.occurrenceId);
|
|
797
|
+
const arrived = computed(() => !!node.value.occurrenceId && scopeControls?.arrived.value === node.value.occurrenceId);
|
|
756
798
|
const focusButton = scopeControls ? /* @__PURE__ */ jsx(
|
|
757
799
|
"button",
|
|
758
800
|
{
|
|
@@ -760,16 +802,22 @@ function CategoryNode({
|
|
|
760
802
|
"data-sidebar-focus": true,
|
|
761
803
|
"data-sidebar-focus-scope": computed(() => node.value.occurrenceId ?? ""),
|
|
762
804
|
"on:click": () => scopeControls.focus(node.value),
|
|
763
|
-
"aria-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
805
|
+
"aria-disabled": computed(() => focusedRoot.value ? "true" : null),
|
|
806
|
+
tabindex: computed(() => focusedRoot.value ? -1 : null),
|
|
807
|
+
"aria-label": computed(() => focusedRoot.value ? scopeControls.labels.focused(node.value.label) : scopeControls.labels.focus(node.value.label)),
|
|
808
|
+
"data-sidebar-tip": computed(() => focusedRoot.value ? scopeControls.labels.focusedTip : scopeControls.labels.focusTip),
|
|
809
|
+
"data-sidebar-tip-detail": computed(() => node.value.label),
|
|
810
|
+
class: computed(() => `ml-auto grid shrink-0 place-items-center rounded w-[1.875rem] h-[1.875rem] ${focusedRoot.value ? `cursor-default opacity-50 ${active.value ? "text-bg" : "text-muted"}` : active.value ? "text-bg hover:bg-bg/10 focus-visible:bg-bg/10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent" : "text-muted hover:bg-accent/10 hover:text-accent focus-visible:bg-accent/10 focus-visible:text-accent focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"}`),
|
|
811
|
+
children: /* @__PURE__ */ jsx(FocusFrame, { class: "h-[1.125rem] w-[1.125rem]" })
|
|
767
812
|
}
|
|
768
813
|
) : null;
|
|
769
814
|
const paddingLeft = padLeft(depth, true);
|
|
770
815
|
return /* @__PURE__ */ jsxs(
|
|
771
816
|
"div",
|
|
772
817
|
{
|
|
818
|
+
"data-sidebar-branch": true,
|
|
819
|
+
"data-sidebar-preview": computed(() => previewed.value ? "" : null),
|
|
820
|
+
"data-sidebar-arrived": computed(() => arrived.value ? "" : null),
|
|
773
821
|
class: computed(
|
|
774
822
|
() => `${depth === 0 ? "border-t border-muted first:border-t-0" : ""} ${depth >= 1 && !isLast.value ? "relative" : ""}`
|
|
775
823
|
),
|
|
@@ -782,12 +830,13 @@ function CategoryNode({
|
|
|
782
830
|
"div",
|
|
783
831
|
{
|
|
784
832
|
class: "absolute border-l border-solid border-muted z-local-1",
|
|
833
|
+
"data-tree-connector": true,
|
|
785
834
|
style: { left: connectorLeft(depth), top: "0px", bottom: "0px" }
|
|
786
835
|
}
|
|
787
836
|
)
|
|
788
837
|
}
|
|
789
838
|
),
|
|
790
|
-
/* @__PURE__ */ jsxs("div", { class: "relative", children: [
|
|
839
|
+
/* @__PURE__ */ jsxs("div", { class: "relative", "data-sidebar-row": true, "data-sidebar-current": computed(() => active.value ? "" : null), children: [
|
|
791
840
|
/* @__PURE__ */ jsx(
|
|
792
841
|
ConnectorLinesLive,
|
|
793
842
|
{
|
|
@@ -886,7 +935,7 @@ function CategoryNode({
|
|
|
886
935
|
}
|
|
887
936
|
)
|
|
888
937
|
] }),
|
|
889
|
-
/* @__PURE__ */ jsx(Show, { when: expanded, children: () => /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
|
|
938
|
+
/* @__PURE__ */ jsx(Show, { when: expanded, children: () => /* @__PURE__ */ jsx("div", { "data-sidebar-children": true, children: /* @__PURE__ */ jsx(
|
|
890
939
|
NodeList,
|
|
891
940
|
{
|
|
892
941
|
nodes: computed(() => node.value.children),
|
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
import { type ReadonlySignal, type Ref } from "@takazudo/zfb/zudo-react";
|
|
2
2
|
import type { SidebarNavNode, SidebarNavigationContext } from "../sidebar/types.js";
|
|
3
|
+
export interface ScopeLabels {
|
|
4
|
+
broaden: string;
|
|
5
|
+
restore: string;
|
|
6
|
+
restoreTip: string;
|
|
7
|
+
focus: (label: string) => string;
|
|
8
|
+
focusTip: string;
|
|
9
|
+
focused: (label: string) => string;
|
|
10
|
+
focusedTip: string;
|
|
11
|
+
}
|
|
3
12
|
export interface ScopeControls {
|
|
4
13
|
focus: (node: SidebarNavNode) => void;
|
|
5
|
-
|
|
14
|
+
labels: ScopeLabels;
|
|
15
|
+
/** True when the unfiltered scope is exactly this one occurrence as its single root. */
|
|
16
|
+
isFocusedRoot: (occurrenceId: string | undefined) => boolean;
|
|
17
|
+
/** The single root a scope change just arrived at, briefly; null otherwise. */
|
|
18
|
+
arrived: ReadonlySignal<string | null>;
|
|
6
19
|
/** Effective disclosure: the user's explicit choice, else revealed/scope root, else `baseline`. */
|
|
7
20
|
isOpen: (key: string, baseline: boolean) => boolean;
|
|
8
21
|
/** Records an explicit user disclosure choice. */
|
|
@@ -28,11 +41,7 @@ export declare function useSidebarScope({ nodes, navigation, activeSlug, query,
|
|
|
28
41
|
selected: import("@takazudo/zfb/zudo-react").Signal<string | null>;
|
|
29
42
|
selectedNodes: ReadonlySignal<SidebarNavNode[]>;
|
|
30
43
|
broader: ReadonlySignal<string | null>;
|
|
31
|
-
labels:
|
|
32
|
-
broaden: string;
|
|
33
|
-
restore: string;
|
|
34
|
-
focus: string;
|
|
35
|
-
};
|
|
44
|
+
labels: ScopeLabels;
|
|
36
45
|
canRestore: ReadonlySignal<boolean>;
|
|
37
46
|
changeScope: (id: string | null) => void;
|
|
38
47
|
save: (broadcast?: boolean) => void;
|
|
@@ -3,6 +3,7 @@ import { broaderSidebarScope, sidebarScopeNodes, reconcileSidebarScope, indexSid
|
|
|
3
3
|
import { getNoteTrayItems, groupItems } from "../note-tray-model/index.js";
|
|
4
4
|
import { AFTER_NAVIGATE_EVENT } from "../transitions/index.js";
|
|
5
5
|
const STATE_VERSION = 2;
|
|
6
|
+
const ARRIVAL_MS = 700;
|
|
6
7
|
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7
8
|
function useSidebarScope({ nodes, navigation, activeSlug, query, locale }) {
|
|
8
9
|
const selected = signal(null);
|
|
@@ -14,13 +15,23 @@ function useSidebarScope({ nodes, navigation, activeSlug, query, locale }) {
|
|
|
14
15
|
const syncEvent = "zd:sidebar-scope";
|
|
15
16
|
const labels = locale === "ja" ? {
|
|
16
17
|
broaden: "\u30C4\u30EA\u30FC\u3092\u5E83\u3052\u308B",
|
|
17
|
-
restore: "\u73FE\u5728\u306E\u30DA\u30FC\u30B8\u306E\
|
|
18
|
-
|
|
18
|
+
restore: "\u73FE\u5728\u306E\u30DA\u30FC\u30B8\u306E\u30B5\u30A4\u30C9\u30D0\u30FC\u306B\u623B\u3059",
|
|
19
|
+
restoreTip: "\u73FE\u5728\u306E\u30DA\u30FC\u30B8\u306E\u30B5\u30A4\u30C9\u30D0\u30FC\u306B\u623B\u3059",
|
|
20
|
+
focus: (label) => `\u30B5\u30A4\u30C9\u30D0\u30FC\u306B\u300C${label}\u300D\u3060\u3051\u3092\u8868\u793A`,
|
|
21
|
+
focusTip: "\u3053\u3053\u3060\u3051\u3092\u8868\u793A",
|
|
22
|
+
focused: (label) => `\u3059\u3067\u306B\u300C${label}\u300D\u3060\u3051\u3092\u8868\u793A\u3057\u3066\u3044\u307E\u3059`,
|
|
23
|
+
focusedTip: "\u3059\u3067\u306B\u3053\u3053\u3060\u3051\u3092\u8868\u793A\u3057\u3066\u3044\u307E\u3059"
|
|
19
24
|
} : {
|
|
20
25
|
broaden: "Broaden tree",
|
|
21
|
-
restore: "Restore current page\u2019s
|
|
22
|
-
|
|
26
|
+
restore: "Restore current page\u2019s sidebar",
|
|
27
|
+
restoreTip: "Restore this page\u2019s sidebar",
|
|
28
|
+
focus: (label) => `Focus sidebar on ${label}`,
|
|
29
|
+
focusTip: "Focus sidebar here",
|
|
30
|
+
focused: (label) => `Sidebar is already focused on ${label}`,
|
|
31
|
+
focusedTip: "Already focused here"
|
|
23
32
|
};
|
|
33
|
+
const arrived = signal(null);
|
|
34
|
+
let arrivalTimer;
|
|
24
35
|
const selectedNodes = computed(() => navigation ? sidebarScopeNodes(navigation, nodes, selected.value) : nodes);
|
|
25
36
|
const broader = computed(() => navigation ? broaderSidebarScope(navigation, nodes, selected.value) : null);
|
|
26
37
|
const occurrenceIndex = navigation ? indexSidebarOccurrences(navigation.roots) : /* @__PURE__ */ new Map();
|
|
@@ -87,17 +98,23 @@ function useSidebarScope({ nodes, navigation, activeSlug, query, locale }) {
|
|
|
87
98
|
revealed.value = /* @__PURE__ */ new Set([...revealed.value, ...withAncestors(previous), ...withAncestors(id)]);
|
|
88
99
|
}
|
|
89
100
|
save();
|
|
101
|
+
clearTimeout(arrivalTimer);
|
|
102
|
+
arrived.value = id === null ? null : scopeRoot.value ?? null;
|
|
103
|
+
if (arrived.value !== null) arrivalTimer = setTimeout(() => {
|
|
104
|
+
arrived.value = null;
|
|
105
|
+
}, ARRIVAL_MS);
|
|
90
106
|
queueMicrotask(() => {
|
|
91
107
|
const nav = navRef.current;
|
|
92
|
-
const controls = [...nav?.querySelectorAll(
|
|
108
|
+
const controls = [...nav?.querySelectorAll('[data-sidebar-focus]:not([aria-disabled="true"])') ?? []];
|
|
93
109
|
const activeBranch = controls.reverse().find((button) => {
|
|
94
110
|
const node = occurrenceIndex.get(button.dataset.sidebarFocusScope ?? "");
|
|
95
111
|
const contains = (n) => n.slug === activeSlug.value || n.children.some(contains);
|
|
96
112
|
return node && contains(node);
|
|
97
113
|
});
|
|
98
|
-
const
|
|
114
|
+
const rootAction = nav?.querySelector("[data-sidebar-tree-root] a") ?? nav?.querySelector("[data-sidebar-tree-root] button[aria-expanded]");
|
|
115
|
+
const rootLink = id && id !== SIDEBAR_FOREST_SCOPE && broader.value !== null ? rootAction : null;
|
|
99
116
|
const currentLink = nav?.querySelector('[aria-current="page"]');
|
|
100
|
-
const control = (id === null ? currentLink : rootLink) ?? nav?.querySelector("[data-sidebar-broaden]") ?? activeBranch ?? currentLink ?? nav?.querySelector(
|
|
117
|
+
const control = (id === null ? currentLink : rootLink) ?? nav?.querySelector("[data-sidebar-broaden]") ?? activeBranch ?? currentLink ?? rootAction ?? nav?.querySelector('[data-sidebar-focus]:not([aria-disabled="true"]), [data-sidebar-tree-root] a, [data-sidebar-tree-root] button[aria-expanded], input');
|
|
101
118
|
const target = control;
|
|
102
119
|
let viewport = nav?.parentElement;
|
|
103
120
|
while (viewport && viewport !== document.body && viewport.scrollHeight <= viewport.clientHeight) viewport = viewport.parentElement;
|
|
@@ -110,11 +127,14 @@ function useSidebarScope({ nodes, navigation, activeSlug, query, locale }) {
|
|
|
110
127
|
control?.focus({ preventScroll: true });
|
|
111
128
|
});
|
|
112
129
|
};
|
|
130
|
+
const isFocusedRoot = (occurrenceId) => occurrenceId !== void 0 && scopeRoot.value === occurrenceId;
|
|
113
131
|
const scopeControls = navigation ? {
|
|
114
132
|
focus: (node) => {
|
|
115
|
-
if (node.occurrenceId) changeScope(node.occurrenceId);
|
|
133
|
+
if (node.occurrenceId && !isFocusedRoot(node.occurrenceId)) changeScope(node.occurrenceId);
|
|
116
134
|
},
|
|
117
|
-
|
|
135
|
+
labels,
|
|
136
|
+
isFocusedRoot,
|
|
137
|
+
arrived,
|
|
118
138
|
defaultOpen,
|
|
119
139
|
defaultGroupOpen,
|
|
120
140
|
isOpen: (key, baseline) => expansion.value[key] ?? automaticOpen(key, baseline),
|
|
@@ -161,6 +181,7 @@ function useSidebarScope({ nodes, navigation, activeSlug, query, locale }) {
|
|
|
161
181
|
document.addEventListener(syncEvent, sync);
|
|
162
182
|
document.addEventListener(AFTER_NAVIGATE_EVENT, navigate);
|
|
163
183
|
return () => {
|
|
184
|
+
clearTimeout(arrivalTimer);
|
|
164
185
|
document.removeEventListener(syncEvent, sync);
|
|
165
186
|
document.removeEventListener(AFTER_NAVIGATE_EVENT, navigate);
|
|
166
187
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveCategoryOwner } from "../nav-scope/index.js";
|
|
1
2
|
function findNode(tree, slug) {
|
|
2
3
|
for (const node of tree) {
|
|
3
4
|
if (node.slug === slug) return node;
|
|
@@ -68,11 +69,10 @@ function resolveItem(item, tree, lang) {
|
|
|
68
69
|
function filterByCategory(tree, categoryMatch, explicitPrefixes) {
|
|
69
70
|
if (!categoryMatch) return tree;
|
|
70
71
|
if (categoryMatch === "!") {
|
|
71
|
-
return tree.filter(
|
|
72
|
-
(node) => !explicitPrefixes.some((prefix) => node.slug.startsWith(prefix))
|
|
73
|
-
);
|
|
72
|
+
return tree.filter((node) => resolveCategoryOwner(node.slug, explicitPrefixes) === void 0);
|
|
74
73
|
}
|
|
75
|
-
|
|
74
|
+
const candidates = explicitPrefixes.includes(categoryMatch) ? explicitPrefixes : [...explicitPrefixes, categoryMatch];
|
|
75
|
+
return tree.filter((node) => resolveCategoryOwner(node.slug, candidates) === categoryMatch);
|
|
76
76
|
}
|
|
77
77
|
function buildSidebarForSection(docs, lang, categoryMatch, categoryMeta, sidebars, buildNavTree, explicitCategoryPrefixes) {
|
|
78
78
|
const tree = buildNavTree(docs, lang, categoryMeta);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { buildSidebarTree } from "../sidebar-tree/build-tree.js";
|
|
2
2
|
import { toRouteSlug } from "../slug/index.js";
|
|
3
|
+
import { resolveCategoryOwner } from "../nav-scope/index.js";
|
|
3
4
|
function isNavVisible(doc) {
|
|
4
5
|
return !doc.data.unlisted && !doc.data.standalone;
|
|
5
6
|
}
|
|
@@ -88,7 +89,7 @@ function groupSatelliteNodes(tree, prefixes) {
|
|
|
88
89
|
const satelliteIdxs = [];
|
|
89
90
|
for (let i = 0; i < result.length; i++) {
|
|
90
91
|
const node = result[i];
|
|
91
|
-
if (node && i !== primaryIdx && node.slug.startsWith(`${prefix}-`)) {
|
|
92
|
+
if (node && i !== primaryIdx && node.slug.startsWith(`${prefix}-`) && resolveCategoryOwner(node.slug, prefixes) === prefix) {
|
|
92
93
|
satelliteIdxs.push(i);
|
|
93
94
|
}
|
|
94
95
|
}
|
|
@@ -9,10 +9,14 @@ export declare function connectorLabelStart(depth: number, widthScale: number, g
|
|
|
9
9
|
export declare function CategoryLinkIcon({ class: className }: {
|
|
10
10
|
class?: string;
|
|
11
11
|
}): import("@takazudo/zfb").Description;
|
|
12
|
-
export declare function ConnectorLines({ depth, isLast, widthScale, topPad, }: {
|
|
12
|
+
export declare function ConnectorLines({ depth, isLast, widthScale, topPad, left: leftOverride, width: widthOverride, }: {
|
|
13
13
|
depth: number;
|
|
14
14
|
isLast: boolean;
|
|
15
15
|
widthScale?: number;
|
|
16
|
+
/** Overrides the depth-derived x position of the vertical line. */
|
|
17
|
+
left?: string;
|
|
18
|
+
/** Overrides the horizontal connector's length. */
|
|
19
|
+
width?: string;
|
|
16
20
|
/**
|
|
17
21
|
* Padding-top of the inner content row that holds the link text. The horizontal
|
|
18
22
|
* connector and (when `isLast`) the vertical clip both anchor to the first-line
|
|
@@ -29,17 +29,20 @@ function ConnectorLines({
|
|
|
29
29
|
depth,
|
|
30
30
|
isLast,
|
|
31
31
|
widthScale = 1,
|
|
32
|
-
topPad = "0px"
|
|
32
|
+
topPad = "0px",
|
|
33
|
+
left: leftOverride,
|
|
34
|
+
width: widthOverride
|
|
33
35
|
}) {
|
|
34
36
|
if (depth === 0) return null;
|
|
35
|
-
const left = connectorLeft(depth);
|
|
36
|
-
const width = connectorWidth(widthScale);
|
|
37
|
+
const left = leftOverride ?? connectorLeft(depth);
|
|
38
|
+
const width = widthOverride ?? connectorWidth(widthScale);
|
|
37
39
|
const firstLineMid = `calc(${topPad} + 0.5lh)`;
|
|
38
40
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
39
41
|
/* @__PURE__ */ jsx(
|
|
40
42
|
"div",
|
|
41
43
|
{
|
|
42
44
|
class: "absolute border-l border-dashed border-muted",
|
|
45
|
+
"data-tree-connector": true,
|
|
43
46
|
style: {
|
|
44
47
|
left,
|
|
45
48
|
top: "0px",
|
|
@@ -51,6 +54,7 @@ function ConnectorLines({
|
|
|
51
54
|
"div",
|
|
52
55
|
{
|
|
53
56
|
class: "absolute border-t border-dashed border-muted",
|
|
57
|
+
"data-tree-connector": true,
|
|
54
58
|
style: {
|
|
55
59
|
left,
|
|
56
60
|
width,
|