starlight-cannoli-plugins 2.8.3 → 2.9.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/README.md CHANGED
@@ -398,7 +398,8 @@ An Astro integration that injects a client-side script to apply opt-in DOM patch
398
398
 
399
399
  - `hideSingleLineGutters` (optional, default: `false`): Hides the line number gutter on Expressive Code blocks that contain only a single line.
400
400
  - `syncTocLabelsFromHeadings` (optional, default: `false`): Copies the rendered HTML of each heading into its matching Starlight TOC anchor label, so the TOC properly reflects any custom markup (e.g. math) present in the heading.
401
- - `wrapDetailsContent` (optional, default: `false`): Wraps the content of every `<details>` element (excluding its `<summary>`) in a `<div class="details-wrapper">`, useful for applying consistent spacing or animation styles.
401
+ - `limitDetailsElementHeight` (optional, default: `false`): Limits the height of expanded `<details>` elements and makes their content scrollable.
402
+ - `offerToggleAllDetails` (optional, default: `false`): Injects an "Expand All Dropdowns" toggle checkbox into the right sidebar (before `nav[aria-labelledby="starlight__on-this-page"]`). Clicking it opens or closes every `<details>` element on the page at once. Only appears on pages that contain at least one visible `<details>` element.
402
403
  - `offerTabbedContent` (optional, default: `false`): Injects a "Tabbed view" toggle checkbox immediately after `#starlight__on-this-page` in the right sidebar. When enabled by the user, the page's markdown content is reorganised into tabs — one per `<h2>` heading, plus an optional "Main" tab for any content that appears before the first `<h2>`. The toggle state is persisted to `localStorage`; active tab selection is not persisted. Only activates when the page has at least two sections (i.e. two or more `<h2>` elements, or one `<h2>` with pre-heading content). Has no effect on pages that lack `#starlight__on-this-page` (e.g. pages with the TOC disabled). Clicking a TOC anchor while tabs are enabled automatically switches to the tab containing the target heading. The generated elements use the classes `tabbed-content`, `tabbed-content-nav`, `tabbed-content-tab`, and `tabbed-content-panel` for styling; toggle button uses the existing `.toggle-checkbox-btn` class.
403
404
 
404
405
  **Usage:**
@@ -414,7 +415,8 @@ export default defineConfig({
414
415
  starlightDomPatches({
415
416
  hideSingleLineGutters: true,
416
417
  syncTocLabelsFromHeadings: true,
417
- wrapDetailsContent: true,
418
+ limitDetailsElementHeight: true,
419
+ offerToggleAllDetails: true,
418
420
  offerTabbedContent: true,
419
421
  }),
420
422
  starlight({ title: "My Docs" }),
@@ -0,0 +1,96 @@
1
+ // src/plugins/starlight-dom-patches/index.ts
2
+ import { fileURLToPath } from "url";
3
+ var css = String.raw;
4
+ var js = String.raw;
5
+ var CONDITIONAL_CSS = {
6
+ limitDetailsElementHeight: {
7
+ enabled: css`
8
+ .main-pane details[open] {
9
+ max-width: 100%;
10
+ }
11
+
12
+ .main-pane details[open] > div.details-wrapper {
13
+ overflow: auto;
14
+ max-height: 67vh;
15
+ padding: 0 1em;
16
+ }
17
+
18
+ .main-pane details[open] p > img {
19
+ height: auto;
20
+ width: auto;
21
+ }
22
+ `,
23
+ disabled: css`
24
+ .main-pane details > *:not(summary) {
25
+ padding: 0 1rem;
26
+ margin: 1rem 0;
27
+ }
28
+ `
29
+ }
30
+ };
31
+ function starlightDomPatches(options = {}) {
32
+ const {
33
+ hideSingleLineGutters = false,
34
+ syncTocLabelsFromHeadings = false,
35
+ limitDetailsElementHeight: limitDetailsElementHeightOption = false,
36
+ offerTabbedContent = false,
37
+ offerToggleAllDetails = false,
38
+ wrapDetailsContent
39
+ } = options;
40
+ if (wrapDetailsContent !== void 0) {
41
+ console.warn(
42
+ "[starlight-dom-patches] `wrapDetailsContent` is deprecated \u2014 use `limitDetailsElementHeight` instead."
43
+ );
44
+ }
45
+ const limitDetailsElementHeight = wrapDetailsContent ?? limitDetailsElementHeightOption;
46
+ return {
47
+ name: "starlight-dom-patches",
48
+ hooks: {
49
+ "astro:config:setup": ({ injectScript }) => {
50
+ const currentFile = fileURLToPath(import.meta.url);
51
+ let pageScriptUrl;
52
+ if (currentFile.endsWith(".ts")) {
53
+ pageScriptUrl = new URL("./page-script.ts", import.meta.url);
54
+ } else if (currentFile.endsWith("starlight-dom-patches.js")) {
55
+ pageScriptUrl = new URL(
56
+ "./starlight-dom-patches/page-script.js",
57
+ import.meta.url
58
+ );
59
+ } else {
60
+ pageScriptUrl = new URL(
61
+ "./plugins/starlight-dom-patches/page-script.js",
62
+ import.meta.url
63
+ );
64
+ }
65
+ for (const [key, entry] of Object.entries(CONDITIONAL_CSS)) {
66
+ const cssString = options[key] ? entry.enabled : entry.disabled;
67
+ if (cssString) {
68
+ injectScript(
69
+ "page",
70
+ js`{ const s = document.createElement("style"); s.textContent = ${JSON.stringify(cssString)}; document.head.appendChild(s); }`
71
+ );
72
+ }
73
+ }
74
+ const scriptPath = JSON.stringify(fileURLToPath(pageScriptUrl));
75
+ const imports = [
76
+ hideSingleLineGutters ? "hideSingleLineGutters" : null,
77
+ syncTocLabelsFromHeadings ? "syncTocLabelsFromHeadings" : null,
78
+ limitDetailsElementHeight ? "limitDetailsElementHeight" : null,
79
+ offerTabbedContent ? "tabbedH2Content" : null,
80
+ offerToggleAllDetails ? "toggleAllDetails" : null
81
+ ].filter(Boolean);
82
+ if (imports.length === 0) return;
83
+ const calls = imports.map((fn) => `${fn}();`).join("\n");
84
+ injectScript(
85
+ "page",
86
+ `import { ${imports.join(", ")} } from ${scriptPath};
87
+ ${calls}`
88
+ );
89
+ }
90
+ }
91
+ };
92
+ }
93
+
94
+ export {
95
+ starlightDomPatches
96
+ };
package/dist/index.js CHANGED
@@ -24,7 +24,7 @@ import {
24
24
  } from "./chunk-NCXV367P.js";
25
25
  import {
26
26
  starlightDomPatches
27
- } from "./chunk-OXJMUUGP.js";
27
+ } from "./chunk-D3DPYGKF.js";
28
28
  import "./chunk-4VNS5WPM.js";
29
29
 
30
30
  // src/plugins/starlight-index-sourced-sidebar/index.ts
@@ -1,6 +1,7 @@
1
1
  declare function hideSingleLineGutters(): void;
2
2
  declare function syncTocLabelsFromHeadings(): void;
3
3
  declare function tabbedH2Content(): void;
4
- declare function wrapDetailsContent(): void;
4
+ declare function toggleAllDetails(): void;
5
+ declare function limitDetailsElementHeight(): void;
5
6
 
6
- export { hideSingleLineGutters, syncTocLabelsFromHeadings, tabbedH2Content, wrapDetailsContent };
7
+ export { hideSingleLineGutters, limitDetailsElementHeight, syncTocLabelsFromHeadings, tabbedH2Content, toggleAllDetails };
@@ -1,6 +1,21 @@
1
1
  import "../../chunk-4VNS5WPM.js";
2
2
 
3
3
  // src/plugins/starlight-dom-patches/page-script.ts
4
+ var TOC_NAV_SELECTOR = "nav[aria-labelledby='starlight__on-this-page']";
5
+ function appendToActionPanel(element) {
6
+ const tocNav = document.querySelector(TOC_NAV_SELECTOR);
7
+ if (!tocNav) return false;
8
+ let panel = tocNav.parentElement?.querySelector(
9
+ ":scope > div.cannoli-actionable"
10
+ ) ?? null;
11
+ if (!panel) {
12
+ panel = document.createElement("div");
13
+ panel.className = "cannoli-actionable";
14
+ tocNav.parentNode.insertBefore(panel, tocNav);
15
+ }
16
+ panel.appendChild(element);
17
+ return true;
18
+ }
4
19
  function hideSingleLineGutters() {
5
20
  console.log("Hiding Single Line Gutters");
6
21
  const codeElements = document.querySelectorAll(
@@ -48,15 +63,6 @@ function tabbedH2Content() {
48
63
  );
49
64
  return;
50
65
  }
51
- const tocNav = document.querySelector(
52
- "nav[aria-labelledby='starlight__on-this-page']"
53
- );
54
- if (!tocNav) {
55
- console.log(
56
- "[tabbedH2Content] nav[aria-labelledby='starlight__on-this-page'] not found \u2014 aborting"
57
- );
58
- return;
59
- }
60
66
  const children = Array.from(container.children);
61
67
  if (children.length === 0) {
62
68
  console.log("[tabbedH2Content] content container is empty \u2014 aborting");
@@ -168,9 +174,9 @@ function tabbedH2Content() {
168
174
  toggleText.textContent = "Tabbed view";
169
175
  toggleLabel.appendChild(checkbox);
170
176
  toggleLabel.appendChild(toggleText);
171
- tocNav.parentNode.insertBefore(toggleLabel, tocNav);
177
+ appendToActionPanel(toggleLabel);
172
178
  console.log(
173
- "[tabbedH2Content] toggle checkbox injected after #starlight__on-this-page"
179
+ "[tabbedH2Content] toggle checkbox injected into .cannoli-actionable panel"
174
180
  );
175
181
  const initialEnabled = localStorage.getItem(LS_KEY) === "enabled";
176
182
  console.log(
@@ -183,7 +189,38 @@ function tabbedH2Content() {
183
189
  localStorage.setItem(LS_KEY, checkbox.checked ? "enabled" : "disabled");
184
190
  });
185
191
  }
186
- function wrapDetailsContent() {
192
+ function toggleAllDetails() {
193
+ const detailsElements = document.querySelectorAll(
194
+ ".main-pane details:not(.visually-hidden)"
195
+ );
196
+ if (detailsElements.length === 0) return;
197
+ const label = document.createElement("label");
198
+ label.id = "toggle-all-details-btn";
199
+ label.htmlFor = "toggle-all-details-checkbox";
200
+ label.className = "toggle-checkbox-btn";
201
+ const checkbox = document.createElement("input");
202
+ checkbox.type = "checkbox";
203
+ checkbox.id = "toggle-all-details-checkbox";
204
+ checkbox.setAttribute("aria-label", "Toggle all details open/closed");
205
+ const span = document.createElement("span");
206
+ span.className = "toggle-label";
207
+ span.textContent = "Expand All Dropdowns";
208
+ label.appendChild(checkbox);
209
+ label.appendChild(span);
210
+ appendToActionPanel(label);
211
+ let allOpen = false;
212
+ checkbox.addEventListener("change", () => {
213
+ document.body.dataset.bulkToggleActive = "true";
214
+ detailsElements.forEach((details) => {
215
+ details.open = !allOpen;
216
+ });
217
+ allOpen = !allOpen;
218
+ setTimeout(() => {
219
+ delete document.body.dataset.bulkToggleActive;
220
+ }, 10);
221
+ });
222
+ }
223
+ function limitDetailsElementHeight() {
187
224
  console.log("Wrapping details element contents");
188
225
  const detailsElements = document.querySelectorAll(".main-pane details");
189
226
  detailsElements.forEach((details) => {
@@ -213,7 +250,8 @@ function wrapDetailsContent() {
213
250
  }
214
251
  export {
215
252
  hideSingleLineGutters,
253
+ limitDetailsElementHeight,
216
254
  syncTocLabelsFromHeadings,
217
255
  tabbedH2Content,
218
- wrapDetailsContent
256
+ toggleAllDetails
219
257
  };
@@ -5,14 +5,25 @@ interface DomPatchesOptions {
5
5
  hideSingleLineGutters?: boolean;
6
6
  /** Copy rendered heading innerHTML into matching Starlight TOC anchor labels. @default false */
7
7
  syncTocLabelsFromHeadings?: boolean;
8
- /** Wrap `<details>` content (excluding `<summary>`) in a `.details-wrapper` div. @default false */
9
- wrapDetailsContent?: boolean;
8
+ /** Limit the height of expanded `<details>` elements and make their content scrollable. @default false */
9
+ limitDetailsElementHeight?: boolean;
10
10
  /**
11
11
  * Inject a toggle checkbox (after `#starlight__on-this-page`) that reorganises page content
12
12
  * into tabs driven by `<h2>` boundaries. Toggle state is persisted to localStorage.
13
13
  * @default false
14
14
  */
15
15
  offerTabbedContent?: boolean;
16
+ /**
17
+ * Inject a "Expand All Dropdowns" toggle checkbox that opens or closes every
18
+ * `<details>` element on the page at once. Only appears on pages that contain
19
+ * at least one visible `<details>` element.
20
+ * @default false
21
+ */
22
+ offerToggleAllDetails?: boolean;
23
+ /**
24
+ * @deprecated Use `limitDetailsElementHeight` instead.
25
+ */
26
+ wrapDetailsContent?: boolean;
16
27
  }
17
28
  /**
18
29
  * Astro integration that injects a client-side script to apply DOM patches
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  starlightDomPatches
3
- } from "../chunk-OXJMUUGP.js";
3
+ } from "../chunk-D3DPYGKF.js";
4
4
  import "../chunk-4VNS5WPM.js";
5
5
  export {
6
6
  starlightDomPatches
@@ -76,21 +76,6 @@ starlight-toc ul > li {
76
76
  html[data-theme="light"] & {
77
77
  background: rgba(162, 171, 173, 0.08);
78
78
  }
79
-
80
- &[open] {
81
- max-width: 100%;
82
-
83
- > div.details-wrapper {
84
- overflow: auto;
85
- max-height: 67vh;
86
- padding: 0 1em;
87
- }
88
-
89
- p > img {
90
- height: auto;
91
- width: auto;
92
- }
93
- }
94
79
  }
95
80
 
96
81
  /********** MathJax/LaTeX Styling **********/
@@ -186,7 +171,7 @@ div > div[class="page"] > svg {
186
171
  }
187
172
  }
188
173
 
189
- starlight-toc .toggle-checkbox-btn:last-of-type {
174
+ .cannoli-actionable {
190
175
  margin-bottom: 1rem;
191
176
  }
192
177
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "starlight-cannoli-plugins",
3
3
  "type": "module",
4
- "version": "2.8.3",
4
+ "version": "2.9.1",
5
5
  "description": "Starlight plugins for automatic sidebar generation and link validation",
6
6
  "license": "ISC",
7
7
  "main": "./dist/index.js",
@@ -1,50 +0,0 @@
1
- // src/plugins/starlight-dom-patches/index.ts
2
- import { fileURLToPath } from "url";
3
- function starlightDomPatches(options = {}) {
4
- const {
5
- hideSingleLineGutters = false,
6
- syncTocLabelsFromHeadings = false,
7
- wrapDetailsContent = false,
8
- offerTabbedContent = false
9
- } = options;
10
- return {
11
- name: "starlight-dom-patches",
12
- hooks: {
13
- "astro:config:setup": ({ injectScript }) => {
14
- const currentFile = fileURLToPath(import.meta.url);
15
- let pageScriptUrl;
16
- if (currentFile.endsWith(".ts")) {
17
- pageScriptUrl = new URL("./page-script.ts", import.meta.url);
18
- } else if (currentFile.endsWith("starlight-dom-patches.js")) {
19
- pageScriptUrl = new URL(
20
- "./starlight-dom-patches/page-script.js",
21
- import.meta.url
22
- );
23
- } else {
24
- pageScriptUrl = new URL(
25
- "./plugins/starlight-dom-patches/page-script.js",
26
- import.meta.url
27
- );
28
- }
29
- const scriptPath = JSON.stringify(fileURLToPath(pageScriptUrl));
30
- const imports = [
31
- hideSingleLineGutters ? "hideSingleLineGutters" : null,
32
- syncTocLabelsFromHeadings ? "syncTocLabelsFromHeadings" : null,
33
- wrapDetailsContent ? "wrapDetailsContent" : null,
34
- offerTabbedContent ? "tabbedH2Content" : null
35
- ].filter(Boolean);
36
- if (imports.length === 0) return;
37
- const calls = imports.map((fn) => `${fn}();`).join("\n");
38
- injectScript(
39
- "page",
40
- `import { ${imports.join(", ")} } from ${scriptPath};
41
- ${calls}`
42
- );
43
- }
44
- }
45
- };
46
- }
47
-
48
- export {
49
- starlightDomPatches
50
- };