@takazudo/zudo-doc 5.26.2 → 5.26.4
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
CHANGED
|
@@ -8,6 +8,23 @@ The format is based on Keep a Changelog, and release notes are generated from th
|
|
|
8
8
|
|
|
9
9
|
No unreleased changes.
|
|
10
10
|
|
|
11
|
+
## [5.26.4] - 2026-09-22
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
- The mobile sidebar drawer now closes when Escape is pressed, and focus returns to the toggle button. The drawer is a modal-style surface — it renders above a full-viewport backdrop, locks body scroll while open, and is `inert` while closed — so Escape is the dismissal that keyboard-only and assistive-technology users reach for first, and it previously did nothing at all: the `SidebarToggle` island registered no key handler. The listener is now attached to `document` only while the drawer is open, so a closed drawer never swallows an Escape meant for another surface such as the language switcher or the theme-pack flyout. Focus is restored to the toggle as the drawer closes; because the panel becomes `inert` again, focus left inside it would otherwise strand on `<body>`. The handler ignores an Escape that ends an IME composition, so cancelling a Japanese conversion in the drawer's own filter field no longer dismisses the drawer. (10036db43, 4445f1972)
|
|
16
|
+
- The toggle's X now closes the drawer when clicked. While the drawer was open the toggle rendered the close icon but sat underneath the backdrop — `z-modal-backdrop` (50) covers the header's `z-toolbar` (20), so `document.elementFromPoint()` at the button's own centre returned the backdrop element and every real pointer event was intercepted. The toggle now joins the drawer's own tier (`relative z-modal`, 60) for exactly as long as the drawer is open, so the X dismisses the drawer as its icon implies; the rest of the header stays dimmed and non-interactive underneath, and the closed-state markup is unchanged. Together with the Escape fix above, this restores both the keyboard and the pointer dismissal — before the two, clicking the backdrop was the only working way out of an open drawer, and nothing in the interface advertised it. (ffe0f728b)
|
|
17
|
+
|
|
18
|
+
## [5.26.3] - 2026-09-21
|
|
19
|
+
|
|
20
|
+
### Bug Fixes
|
|
21
|
+
|
|
22
|
+
- The mobile sidebar toggle no longer renders both of its icons at once in a project whose global CSS contains an unlayered media reset such as `img, svg, video, canvas, audio, iframe, embed, object { display: block }`. The inactive icon used to be hidden with Tailwind's `.hidden` utility, which ships inside `@layer utilities` — an unlayered author rule outranks every layered rule by cascade-layer precedence regardless of source order, so both icons computed to `display: block`, stacked vertically, and made the toggle a 48px control showing the X and the hamburger together. The inactive icon is now hidden with an inline declaration, which no consumer stylesheet can outrank. (30d6625e8)
|
|
23
|
+
|
|
24
|
+
### Other Changes
|
|
25
|
+
|
|
26
|
+
- The `@takazudo/zfb`, `@takazudo/zfb-runtime`, and `@takazudo/zfb-md-wasm` peer dependency floors are now `^2.20.1`. The 2.20.1 release is lockstep metadata only — no upstream package reports a package-specific change, and the published-tarball delta is limited to each package's own version field plus its platform-binary pins. No zudo-doc config migration is required. (07d700356)
|
|
27
|
+
|
|
11
28
|
## [5.26.2] - 2026-09-20
|
|
12
29
|
|
|
13
30
|
### Bug Fixes
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "preact/jsx-runtime";
|
|
3
|
-
import { useState, useEffect } from "preact/hooks";
|
|
3
|
+
import { useState, useEffect, useRef } from "preact/hooks";
|
|
4
4
|
import { AFTER_NAVIGATE_EVENT, ensureNestedIslandPropsRefresh } from "../transitions/index.js";
|
|
5
5
|
import { SidebarTree } from "../sidebar-tree-island/index.js";
|
|
6
6
|
ensureNestedIslandPropsRefresh();
|
|
7
7
|
const cx = (...classes) => classes.filter(Boolean).join(" ");
|
|
8
|
+
const HIDDEN_ICON_STYLE = "display:none";
|
|
8
9
|
function SidebarToggle({
|
|
9
10
|
nodes,
|
|
10
11
|
currentSlug,
|
|
@@ -16,6 +17,7 @@ function SidebarToggle({
|
|
|
16
17
|
dateFormats
|
|
17
18
|
}) {
|
|
18
19
|
const [open, setOpen] = useState(false);
|
|
20
|
+
const hamburgerRef = useRef(null);
|
|
19
21
|
useEffect(() => {
|
|
20
22
|
if (open) {
|
|
21
23
|
document.body.style.overflow = "hidden";
|
|
@@ -33,13 +35,39 @@ function SidebarToggle({
|
|
|
33
35
|
document.addEventListener(AFTER_NAVIGATE_EVENT, handleSwap);
|
|
34
36
|
return () => document.removeEventListener(AFTER_NAVIGATE_EVENT, handleSwap);
|
|
35
37
|
}, []);
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (!open) return;
|
|
40
|
+
function handleKeyDown(event) {
|
|
41
|
+
if (event.isComposing) return;
|
|
42
|
+
if (event.key === "Escape") {
|
|
43
|
+
setOpen(false);
|
|
44
|
+
hamburgerRef.current?.focus();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
48
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
49
|
+
}, [open]);
|
|
36
50
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
37
51
|
/* @__PURE__ */ jsxs(
|
|
38
52
|
"button",
|
|
39
53
|
{
|
|
54
|
+
ref: hamburgerRef,
|
|
40
55
|
type: "button",
|
|
41
56
|
onClick: () => setOpen(!open),
|
|
42
|
-
className:
|
|
57
|
+
className: cx(
|
|
58
|
+
"lg:hidden shrink-0 px-hsp-sm py-vsp-xs -ml-hsp-sm mr-hsp-sm text-muted hover:text-fg",
|
|
59
|
+
// While open, the button IS the close control (it shows the X), so it
|
|
60
|
+
// has to sit in the drawer's own tier rather than under the backdrop —
|
|
61
|
+
// `z-modal-backdrop` (50) otherwise intercepts every pointer event at
|
|
62
|
+
// the button's own centre and the X is unclickable
|
|
63
|
+
// (zudolab/zudo-doc#4369). `relative` is required: a bare `z-index`
|
|
64
|
+
// has no effect on a statically-positioned element.
|
|
65
|
+
// Conditioning on `open` is load-bearing, not cosmetic: SSR always
|
|
66
|
+
// renders `open=false`, so the closed-state markup stays
|
|
67
|
+
// byte-identical (hydration-stable, and the A2 no-stub parity hashes
|
|
68
|
+
// for this directory do not move).
|
|
69
|
+
open && "relative z-modal"
|
|
70
|
+
),
|
|
43
71
|
"aria-label": open ? "Close sidebar" : "Open sidebar",
|
|
44
72
|
"aria-expanded": open,
|
|
45
73
|
children: [
|
|
@@ -47,7 +75,8 @@ function SidebarToggle({
|
|
|
47
75
|
"svg",
|
|
48
76
|
{
|
|
49
77
|
xmlns: "http://www.w3.org/2000/svg",
|
|
50
|
-
className:
|
|
78
|
+
className: "h-icon-lg w-icon-lg",
|
|
79
|
+
style: open ? void 0 : HIDDEN_ICON_STYLE,
|
|
51
80
|
"aria-hidden": "true",
|
|
52
81
|
fill: "none",
|
|
53
82
|
viewBox: "0 0 24 24",
|
|
@@ -67,7 +96,8 @@ function SidebarToggle({
|
|
|
67
96
|
"svg",
|
|
68
97
|
{
|
|
69
98
|
xmlns: "http://www.w3.org/2000/svg",
|
|
70
|
-
className:
|
|
99
|
+
className: "h-icon-lg w-icon-lg",
|
|
100
|
+
style: open ? HIDDEN_ICON_STYLE : void 0,
|
|
71
101
|
"aria-hidden": "true",
|
|
72
102
|
fill: "none",
|
|
73
103
|
viewBox: "0 0 24 24",
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
/** @jsxImportSource preact */
|
|
5
5
|
// Use preact hook entrypoints directly — the "react" → "preact/compat" alias
|
|
6
6
|
// lets us consume React-typed components in this Preact app.
|
|
7
|
-
import { useState, useEffect } from "preact/hooks";
|
|
7
|
+
import { useState, useEffect, useRef } from "preact/hooks";
|
|
8
8
|
// After zudolab/zudo-doc#1335 the host components pull lifecycle event names
|
|
9
9
|
// from the v2 transitions module rather than hard-coding `astro:*` literals.
|
|
10
10
|
// `ensureNestedIslandPropsRefresh` is imported through the barrel (not the
|
|
@@ -27,6 +27,17 @@ ensureNestedIslandPropsRefresh();
|
|
|
27
27
|
const cx = (...classes: Array<string | false | null | undefined>) =>
|
|
28
28
|
classes.filter(Boolean).join(" ");
|
|
29
29
|
|
|
30
|
+
// Icon visibility deliberately does NOT ride on Tailwind's `.hidden` utility.
|
|
31
|
+
// `.hidden` is emitted inside `@layer utilities`, and a consumer component pack
|
|
32
|
+
// that ships an UNLAYERED media reset — `img, svg, video, … { display: block }`
|
|
33
|
+
// — outranks every layered rule by cascade-layer precedence, no matter the
|
|
34
|
+
// source order. Both icons then computed to `display: block`, stacked, and made
|
|
35
|
+
// the mobile toggle 48px tall (zudolab/zudo-doc#4355). An inline declaration
|
|
36
|
+
// sits above all author rules, layered or not, so the state holds against any
|
|
37
|
+
// consumer stylesheet. It is a plain string so SSR and the initial client
|
|
38
|
+
// render serialise byte-identically for hydration.
|
|
39
|
+
const HIDDEN_ICON_STYLE = "display:none";
|
|
40
|
+
|
|
30
41
|
// Mobile drawer hosts the SidebarTree directly (rather than receiving it as
|
|
31
42
|
// JSX children) so the tree's data props ride across the SSR → hydrate
|
|
32
43
|
// boundary inside the Island marker's `data-props` attribute. zfb's
|
|
@@ -76,6 +87,9 @@ export function SidebarToggle({
|
|
|
76
87
|
// same shape regardless of `open`, preventing Preact from re-mounting
|
|
77
88
|
// the subtree (which can drop click handlers on the hamburger button).
|
|
78
89
|
const [open, setOpen] = useState(false);
|
|
90
|
+
// Focus-restore target for the Escape handler below. A `ref` does not
|
|
91
|
+
// serialise, so SSR/hydration markup is unaffected (zudolab/zudo-doc#4366).
|
|
92
|
+
const hamburgerRef = useRef<HTMLButtonElement>(null);
|
|
79
93
|
|
|
80
94
|
useEffect(() => {
|
|
81
95
|
if (open) {
|
|
@@ -97,25 +111,69 @@ export function SidebarToggle({
|
|
|
97
111
|
return () => document.removeEventListener(AFTER_NAVIGATE_EVENT, handleSwap);
|
|
98
112
|
}, []);
|
|
99
113
|
|
|
114
|
+
// Escape-to-close (zudolab/zudo-doc#4366). Deliberately inlined rather than
|
|
115
|
+
// reusing `connectEscapeToClose` from theme-pack-switcher/switcher-state.js
|
|
116
|
+
// — this island is ejectable and eject's `rewireImports` would rewrite that
|
|
117
|
+
// relative import to `@takazudo/zudo-doc/theme-pack-switcher`, a subpath
|
|
118
|
+
// absent from the package's `exports` map, shipping a broken ejected copy.
|
|
119
|
+
// The listener is registered only while `open` is true: a closed drawer
|
|
120
|
+
// must never swallow an Escape meant for another document-level listener
|
|
121
|
+
// (language switcher, find bar, theme-pack flyout). Focus is restored to
|
|
122
|
+
// the hamburger synchronously in the handler — the `<aside>` goes `inert`
|
|
123
|
+
// on close, so without this, focus left inside the drawer would strand on
|
|
124
|
+
// `<body>` and defeat the point of the fix for keyboard/AT users.
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
if (!open) return;
|
|
127
|
+
function handleKeyDown(event: KeyboardEvent) {
|
|
128
|
+
// An Escape that ends an IME composition belongs to the composition, not
|
|
129
|
+
// to the drawer: cancelling a Japanese conversion in the drawer's own
|
|
130
|
+
// "Filter navigation" input would otherwise dismiss the whole drawer and
|
|
131
|
+
// yank focus to the hamburger. Same guard the sibling document-level
|
|
132
|
+
// shortcut in `sidebar-tree-island/index.tsx` already uses.
|
|
133
|
+
if (event.isComposing) return;
|
|
134
|
+
if (event.key === "Escape") {
|
|
135
|
+
setOpen(false);
|
|
136
|
+
hamburgerRef.current?.focus();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
140
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
141
|
+
}, [open]);
|
|
142
|
+
|
|
100
143
|
return (
|
|
101
144
|
<>
|
|
102
145
|
{/* Hamburger button - visible only on mobile.
|
|
103
146
|
Both icons are always rendered so the SSR output has the same
|
|
104
|
-
DOM shape as the post-hydration tree. The
|
|
105
|
-
|
|
106
|
-
hydration walk sees byte-stable markup and keeps the
|
|
107
|
-
handler attached. */}
|
|
147
|
+
DOM shape as the post-hydration tree. The inactive one is hidden
|
|
148
|
+
via an inline `display:none` (see HIDDEN_ICON_STYLE above), so
|
|
149
|
+
Preact's hydration walk sees byte-stable markup and keeps the
|
|
150
|
+
click handler attached. */}
|
|
108
151
|
<button
|
|
152
|
+
ref={hamburgerRef}
|
|
109
153
|
type="button"
|
|
110
154
|
onClick={() => setOpen(!open)}
|
|
111
|
-
className=
|
|
155
|
+
className={cx(
|
|
156
|
+
"lg:hidden shrink-0 px-hsp-sm py-vsp-xs -ml-hsp-sm mr-hsp-sm text-muted hover:text-fg",
|
|
157
|
+
// While open, the button IS the close control (it shows the X), so it
|
|
158
|
+
// has to sit in the drawer's own tier rather than under the backdrop —
|
|
159
|
+
// `z-modal-backdrop` (50) otherwise intercepts every pointer event at
|
|
160
|
+
// the button's own centre and the X is unclickable
|
|
161
|
+
// (zudolab/zudo-doc#4369). `relative` is required: a bare `z-index`
|
|
162
|
+
// has no effect on a statically-positioned element.
|
|
163
|
+
// Conditioning on `open` is load-bearing, not cosmetic: SSR always
|
|
164
|
+
// renders `open=false`, so the closed-state markup stays
|
|
165
|
+
// byte-identical (hydration-stable, and the A2 no-stub parity hashes
|
|
166
|
+
// for this directory do not move).
|
|
167
|
+
open && "relative z-modal",
|
|
168
|
+
)}
|
|
112
169
|
aria-label={open ? "Close sidebar" : "Open sidebar"}
|
|
113
170
|
aria-expanded={open}
|
|
114
171
|
>
|
|
115
172
|
{/* X icon — visible only when open */}
|
|
116
173
|
<svg
|
|
117
174
|
xmlns="http://www.w3.org/2000/svg"
|
|
118
|
-
className=
|
|
175
|
+
className="h-icon-lg w-icon-lg"
|
|
176
|
+
style={open ? undefined : HIDDEN_ICON_STYLE}
|
|
119
177
|
aria-hidden="true"
|
|
120
178
|
fill="none"
|
|
121
179
|
viewBox="0 0 24 24"
|
|
@@ -131,7 +189,8 @@ export function SidebarToggle({
|
|
|
131
189
|
{/* Hamburger icon — visible only when closed */}
|
|
132
190
|
<svg
|
|
133
191
|
xmlns="http://www.w3.org/2000/svg"
|
|
134
|
-
className=
|
|
192
|
+
className="h-icon-lg w-icon-lg"
|
|
193
|
+
style={open ? HIDDEN_ICON_STYLE : undefined}
|
|
135
194
|
aria-hidden="true"
|
|
136
195
|
fill="none"
|
|
137
196
|
viewBox="0 0 24 24"
|
|
@@ -152,9 +211,13 @@ export function SidebarToggle({
|
|
|
152
211
|
mount/unmount across the hydration boundary).
|
|
153
212
|
`z-modal-backdrop` (50) intentionally sits ABOVE the header
|
|
154
213
|
(`z-toolbar`, 20): the open mobile drawer is a modal surface that
|
|
155
|
-
dims the whole viewport, header included.
|
|
156
|
-
|
|
157
|
-
it is
|
|
214
|
+
dims the whole viewport, header included. The toggle button is the
|
|
215
|
+
one exception — while open it renders the X and advertises itself as
|
|
216
|
+
the close control, so it is lifted to `z-modal` (60) for exactly as
|
|
217
|
+
long as the drawer is open (see its className above). Everything else
|
|
218
|
+
in the header stays dimmed and non-interactive underneath.
|
|
219
|
+
Backdrop tapping (onClick below) remains a valid dismissal, as does
|
|
220
|
+
Escape (zudolab/zudo-doc#4366). */}
|
|
158
221
|
<div
|
|
159
222
|
className={cx("fixed inset-0 z-modal-backdrop bg-overlay/30 lg:hidden", !open && "hidden")}
|
|
160
223
|
aria-hidden={!open}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@takazudo/zudo-doc",
|
|
3
|
-
"version": "5.26.
|
|
3
|
+
"version": "5.26.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "zudo-doc framework primitives layer that sits on top of zfb's engine — sidebar, theme, TOC, breadcrumb, layouts, head injection, View Transitions, SSR-skip wrappers (per ADR-003).",
|
|
6
6
|
"license": "MIT",
|
|
@@ -717,9 +717,9 @@
|
|
|
717
717
|
],
|
|
718
718
|
"peerDependencies": {
|
|
719
719
|
"@takazudo/zdtp": "^0.5.2 || ^0.6.0 || ^0.7.0 || ^0.8.0",
|
|
720
|
-
"@takazudo/zfb": "^2.20.
|
|
721
|
-
"@takazudo/zfb-md-wasm": "^2.20.
|
|
722
|
-
"@takazudo/zfb-runtime": "^2.20.
|
|
720
|
+
"@takazudo/zfb": "^2.20.1",
|
|
721
|
+
"@takazudo/zfb-md-wasm": "^2.20.1",
|
|
722
|
+
"@takazudo/zfb-runtime": "^2.20.1",
|
|
723
723
|
"@takazudo/zudo-doc-history-server": "^5.17.2",
|
|
724
724
|
"diff": "^8.0.0",
|
|
725
725
|
"katex": "^0.16.0",
|
|
@@ -757,9 +757,9 @@
|
|
|
757
757
|
},
|
|
758
758
|
"devDependencies": {
|
|
759
759
|
"@takazudo/mdx-formatter": "1.3.0-next.4",
|
|
760
|
-
"@takazudo/zfb": "2.20.
|
|
761
|
-
"@takazudo/zfb-md-wasm": "2.20.
|
|
762
|
-
"@takazudo/zfb-runtime": "2.20.
|
|
760
|
+
"@takazudo/zfb": "2.20.1",
|
|
761
|
+
"@takazudo/zfb-md-wasm": "2.20.1",
|
|
762
|
+
"@takazudo/zfb-runtime": "2.20.1",
|
|
763
763
|
"@types/fs-extra": "^11.0.4",
|
|
764
764
|
"@types/minimist": "^1.2.5",
|
|
765
765
|
"@types/node": "^25.3.5",
|
|
@@ -771,7 +771,7 @@
|
|
|
771
771
|
"typescript": "^5.0.0",
|
|
772
772
|
"vitest": "^4.1.0",
|
|
773
773
|
"zod": "^4.3.6",
|
|
774
|
-
"@takazudo/zudo-doc-history-server": "5.26.
|
|
774
|
+
"@takazudo/zudo-doc-history-server": "5.26.4"
|
|
775
775
|
},
|
|
776
776
|
"scripts": {
|
|
777
777
|
"gen:search-widget-script": "node scripts/gen-search-widget-script.mjs",
|