@brandon_m_behring/book-scaffold-astro 3.0.0-alpha.7 → 3.0.0-alpha.9

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.
@@ -50,7 +50,9 @@ export default function VersionSelector() {
50
50
  };
51
51
  }, [open]);
52
52
 
53
- const current = STUB_VERSIONS.find((v) => v.current) || STUB_VERSIONS[0];
53
+ // STUB_VERSIONS is a const literal with >=1 entry so the fallback is
54
+ // always defined; the bang silences TS's noUncheckedIndexedAccess.
55
+ const current = STUB_VERSIONS.find((v) => v.current) ?? STUB_VERSIONS[0]!;
54
56
 
55
57
  return (
56
58
  <div class="version-selector" ref={ref}>
@@ -0,0 +1,5 @@
1
+ import * as preact from 'preact';
2
+
3
+ declare function ToolFilter(): preact.JSX.Element;
4
+
5
+ export { ToolFilter as default };
@@ -0,0 +1,118 @@
1
+ // components/ToolFilter.tsx
2
+ import { useState, useRef, useEffect } from "preact/hooks";
3
+ import { jsx, jsxs } from "preact/jsx-runtime";
4
+ var STORAGE_KEY = "book:tool-filter";
5
+ var EVENT_NAME = "book:tool-filter:change";
6
+ var FILTERABLE_TOOLS = ["claude-code", "gemini-cli", "codex-cli"];
7
+ function readSaved() {
8
+ try {
9
+ const raw = localStorage.getItem(STORAGE_KEY);
10
+ if (!raw) return [];
11
+ const parsed = JSON.parse(raw);
12
+ if (!Array.isArray(parsed)) return [];
13
+ return parsed.filter((s) => typeof s === "string");
14
+ } catch {
15
+ return [];
16
+ }
17
+ }
18
+ function writeSaved(selected) {
19
+ try {
20
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(selected));
21
+ } catch {
22
+ }
23
+ }
24
+ function announce(selected) {
25
+ window.dispatchEvent(
26
+ new CustomEvent(EVENT_NAME, { detail: { selected } })
27
+ );
28
+ }
29
+ function ToolFilter() {
30
+ const [open, setOpen] = useState(false);
31
+ const [selected, setSelected] = useState([]);
32
+ const ref = useRef(null);
33
+ useEffect(() => {
34
+ const saved = readSaved();
35
+ setSelected(saved);
36
+ announce(saved);
37
+ }, []);
38
+ useEffect(() => {
39
+ if (!open) return;
40
+ const onClick = (e) => {
41
+ if (ref.current && !ref.current.contains(e.target)) {
42
+ setOpen(false);
43
+ }
44
+ };
45
+ const onKey = (e) => {
46
+ if (e.key === "Escape") setOpen(false);
47
+ };
48
+ window.addEventListener("click", onClick);
49
+ window.addEventListener("keydown", onKey);
50
+ return () => {
51
+ window.removeEventListener("click", onClick);
52
+ window.removeEventListener("keydown", onKey);
53
+ };
54
+ }, [open]);
55
+ function toggle(tool) {
56
+ const next = selected.includes(tool) ? selected.filter((s) => s !== tool) : [...selected, tool];
57
+ setSelected(next);
58
+ writeSaved(next);
59
+ announce(next);
60
+ }
61
+ function clearAll() {
62
+ setSelected([]);
63
+ writeSaved([]);
64
+ announce([]);
65
+ }
66
+ const activeCount = selected.length;
67
+ const buttonLabel = activeCount === 0 ? "Filter chapters by tool (showing all)" : `Filter chapters by tool (${activeCount} active)`;
68
+ return /* @__PURE__ */ jsxs("div", { class: "tool-filter", ref, children: [
69
+ /* @__PURE__ */ jsxs(
70
+ "button",
71
+ {
72
+ type: "button",
73
+ class: "chrome-button tool-filter-trigger",
74
+ "aria-label": buttonLabel,
75
+ title: buttonLabel,
76
+ "aria-expanded": open,
77
+ "aria-haspopup": "true",
78
+ onClick: () => setOpen((v) => !v),
79
+ children: [
80
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2690" }),
81
+ activeCount > 0 && /* @__PURE__ */ jsx("span", { class: "tool-filter-count", "aria-hidden": "true", children: activeCount })
82
+ ]
83
+ }
84
+ ),
85
+ open && /* @__PURE__ */ jsxs("div", { class: "tool-filter-panel", role: "group", "aria-label": "Tool filters", children: [
86
+ /* @__PURE__ */ jsx("p", { class: "tool-filter-heading", children: "Show chapters for" }),
87
+ /* @__PURE__ */ jsx("ul", { class: "tool-filter-chips", children: FILTERABLE_TOOLS.map((tool) => {
88
+ const on = selected.includes(tool);
89
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
90
+ "button",
91
+ {
92
+ type: "button",
93
+ class: `tool-filter-chip${on ? " tool-filter-chip-on" : ""}`,
94
+ "aria-pressed": on,
95
+ onClick: () => toggle(tool),
96
+ children: tool
97
+ }
98
+ ) });
99
+ }) }),
100
+ /* @__PURE__ */ jsxs("div", { class: "tool-filter-footer", children: [
101
+ /* @__PURE__ */ jsx(
102
+ "button",
103
+ {
104
+ type: "button",
105
+ class: "tool-filter-clear",
106
+ onClick: clearAll,
107
+ disabled: activeCount === 0,
108
+ children: "Clear"
109
+ }
110
+ ),
111
+ /* @__PURE__ */ jsx("span", { class: "tool-filter-note", children: "Cross-tool chapters stay visible under any filter." })
112
+ ] })
113
+ ] })
114
+ ] });
115
+ }
116
+ export {
117
+ ToolFilter as default
118
+ };
@@ -0,0 +1,5 @@
1
+ import * as preact from 'preact';
2
+
3
+ declare function VersionSelector(): preact.JSX.Element;
4
+
5
+ export { VersionSelector as default };
@@ -0,0 +1,58 @@
1
+ // components/VersionSelector.tsx
2
+ import { useState, useRef, useEffect } from "preact/hooks";
3
+ import { jsx, jsxs } from "preact/jsx-runtime";
4
+ var STUB_VERSIONS = [
5
+ { id: "", label: "Latest (main)", date: "2026-04-17", current: true },
6
+ { id: "v1.0", label: "v1.0", date: "2026-05-01", current: false }
7
+ ];
8
+ function VersionSelector() {
9
+ const [open, setOpen] = useState(false);
10
+ const ref = useRef(null);
11
+ useEffect(() => {
12
+ if (!open) return;
13
+ const onClick = (e) => {
14
+ if (ref.current && !ref.current.contains(e.target)) {
15
+ setOpen(false);
16
+ }
17
+ };
18
+ const onKey = (e) => {
19
+ if (e.key === "Escape") setOpen(false);
20
+ };
21
+ window.addEventListener("click", onClick);
22
+ window.addEventListener("keydown", onKey);
23
+ return () => {
24
+ window.removeEventListener("click", onClick);
25
+ window.removeEventListener("keydown", onKey);
26
+ };
27
+ }, [open]);
28
+ const current = STUB_VERSIONS.find((v) => v.current) ?? STUB_VERSIONS[0];
29
+ return /* @__PURE__ */ jsxs("div", { class: "version-selector", ref, children: [
30
+ /* @__PURE__ */ jsx(
31
+ "button",
32
+ {
33
+ type: "button",
34
+ class: "chrome-button version-selector-trigger",
35
+ "aria-label": "Select book version",
36
+ title: `Current: ${current.label} (${current.date})`,
37
+ "aria-expanded": open,
38
+ "aria-haspopup": "listbox",
39
+ onClick: () => setOpen((v) => !v),
40
+ children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "v" })
41
+ }
42
+ ),
43
+ open && /* @__PURE__ */ jsx("ul", { class: "version-selector-menu", role: "listbox", children: STUB_VERSIONS.map((v) => /* @__PURE__ */ jsx("li", { role: "option", "aria-selected": v.current, children: /* @__PURE__ */ jsxs(
44
+ "a",
45
+ {
46
+ href: v.id ? `/${v.id}/` : "/",
47
+ class: v.current ? "version-current" : "",
48
+ children: [
49
+ /* @__PURE__ */ jsx("span", { class: "version-label", children: v.label }),
50
+ /* @__PURE__ */ jsx("span", { class: "version-date", children: v.date })
51
+ ]
52
+ }
53
+ ) })) })
54
+ ] });
55
+ }
56
+ export {
57
+ VersionSelector as default
58
+ };
package/dist/index.mjs CHANGED
@@ -173,6 +173,9 @@ function bookScaffoldIntegration(opts) {
173
173
  for (const sheet of styles) {
174
174
  injectScript("page-ssr", `import '${PACKAGE_NAME}/styles/${sheet}';`);
175
175
  }
176
+ if (profile === "academic") {
177
+ injectScript("page-ssr", "import 'katex/dist/katex.min.css';");
178
+ }
176
179
  const routes = profile === "tools" ? [...DEFAULT_ROUTES_ALL, ...DEFAULT_ROUTES_TOOLS] : [...DEFAULT_ROUTES_ALL];
177
180
  for (const route of routes) {
178
181
  injectRoute({
@@ -31,12 +31,9 @@
31
31
  */
32
32
  import '@fontsource-variable/roboto';
33
33
  import '@fontsource-variable/source-code-pro';
34
- // KaTeX stylesheet is always loaded (~60 KB) so that academic-profile
35
- // books render math without additional setup. Minimal/tools profiles
36
- // receive the CSS but have no math elements to style harmless.
37
- // rehype-katex (the JS pipeline) is profile-gated in astro.config.mjs;
38
- // only the academic profile produces katex-classed DOM nodes at build.
39
- import 'katex/dist/katex.min.css';
34
+ // KaTeX CSS is injected by bookScaffoldIntegration for academic profile
35
+ // only (since v3.0 alpha.8) tools/minimal profiles don't install katex
36
+ // as a peer dep, so the import would fail at the consumer's build.
40
37
  import '../styles/tokens.css';
41
38
  import '../styles/typography.css';
42
39
  import '../styles/layout.css';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@brandon_m_behring/book-scaffold-astro",
3
3
  "description": "Astro 6 + MDX toolkit for long-form technical books. Profile-aware (academic / tools / minimal); ships Tufte typography, KaTeX, BibTeX citations, Pagefind, Cloudflare Workers deploy. See PACKAGE_DESIGN.md for the API contract.",
4
- "version": "3.0.0-alpha.7",
4
+ "version": "3.0.0-alpha.9",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "Brandon Behring",
@@ -73,9 +73,15 @@
73
73
  "./components/Tag.astro": "./components/Tag.astro",
74
74
  "./components/Theorem.astro": "./components/Theorem.astro",
75
75
  "./components/TipBox.astro": "./components/TipBox.astro",
76
- "./components/ToolFilter": "./components/ToolFilter.tsx",
76
+ "./components/ToolFilter": {
77
+ "types": "./dist/components/ToolFilter.d.ts",
78
+ "import": "./dist/components/ToolFilter.mjs"
79
+ },
77
80
  "./components/TryThis.astro": "./components/TryThis.astro",
78
- "./components/VersionSelector": "./components/VersionSelector.tsx",
81
+ "./components/VersionSelector": {
82
+ "types": "./dist/components/VersionSelector.d.ts",
83
+ "import": "./dist/components/VersionSelector.mjs"
84
+ },
79
85
  "./components/WarnBox.astro": "./components/WarnBox.astro",
80
86
  "./components/WeekRef.astro": "./components/WeekRef.astro",
81
87
  "./components/XRef.astro": "./components/XRef.astro",