explico 0.8.0 → 0.11.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.
Files changed (230) hide show
  1. package/CHANGELOG.md +1257 -1
  2. package/README.md +60 -15
  3. package/dist/compiler/astro.d.ts +6 -1
  4. package/dist/compiler/cli.d.ts +46 -2
  5. package/dist/compiler/cli.js +70 -4
  6. package/dist/compiler/collections.d.ts +35 -2
  7. package/dist/compiler/collections.js +68 -5
  8. package/dist/compiler/course-config.d.ts +3 -194
  9. package/dist/compiler/course-config.js +78 -5
  10. package/dist/compiler/exports/standalone-html.d.ts +6 -1
  11. package/dist/compiler/knowledge-version.js +1 -1
  12. package/dist/compiler/lesson-derivation.d.ts +27 -10
  13. package/dist/compiler/lesson-derivation.js +23 -8
  14. package/dist/compiler/logging.d.ts +4 -1
  15. package/dist/compiler/logging.js +4 -1
  16. package/dist/compiler/manifest.d.ts +204 -31
  17. package/dist/compiler/manifest.js +270 -55
  18. package/dist/compiler/resolution-report.d.ts +6 -1
  19. package/dist/course-config-Dsnri9Tk.d.ts +464 -0
  20. package/dist/reference/base-accents.d.mts +26 -0
  21. package/dist/reference/base-accents.mjs +31 -0
  22. package/dist/reference/base-roman-labels.d.mts +2 -2
  23. package/dist/reference/base-roman-labels.mjs +2 -2
  24. package/dist/reference/consistency.d.ts +14 -2
  25. package/dist/reference/consistency.js +108 -4
  26. package/dist/reference/delimiter-size.d.ts +3 -2
  27. package/dist/reference/diagrams-validate.d.ts +7 -2
  28. package/dist/reference/diagrams-validate.js +9 -1
  29. package/dist/reference/equations-validate.d.ts +8 -5
  30. package/dist/reference/equations-validate.js +1 -0
  31. package/dist/reference/equations.d.mts +60 -31
  32. package/dist/reference/equations.mjs +83 -43
  33. package/dist/reference/examples-validate.d.ts +31 -0
  34. package/dist/reference/examples-validate.js +48 -0
  35. package/dist/reference/figures-validate.d.ts +1 -1
  36. package/dist/reference/figures-validate.js +4 -1
  37. package/dist/reference/folds-validate.d.ts +33 -0
  38. package/dist/reference/folds-validate.js +32 -0
  39. package/dist/reference/gate-abbreviations.d.ts +3 -1
  40. package/dist/reference/gate-abbreviations.js +1 -0
  41. package/dist/reference/gate-course-rules.d.ts +32 -0
  42. package/dist/reference/gate-course-rules.js +85 -0
  43. package/dist/reference/gate-heading-length.d.ts +5 -2
  44. package/dist/reference/gate-heading-length.js +45 -21
  45. package/dist/reference/gate-heading-structure.d.ts +41 -0
  46. package/dist/reference/gate-heading-structure.js +113 -0
  47. package/dist/reference/gate-math-punctuation.d.ts +71 -0
  48. package/dist/reference/gate-math-punctuation.js +251 -0
  49. package/dist/reference/gate-math.d.ts +22 -6
  50. package/dist/reference/gate-math.js +42 -8
  51. package/dist/reference/gate-part-pages.d.ts +50 -0
  52. package/dist/reference/gate-part-pages.js +118 -0
  53. package/dist/reference/gate-prose-notation.d.ts +21 -11
  54. package/dist/reference/gate-prose-notation.js +25 -7
  55. package/dist/reference/katex-delimiters.mjs +20 -7
  56. package/dist/reference/keyed-float-validate.d.ts +27 -10
  57. package/dist/reference/keyed-float-validate.js +15 -1
  58. package/dist/reference/keyed-jsx-floats.d.mts +11 -5
  59. package/dist/reference/keyed-jsx-floats.mjs +73 -20
  60. package/dist/reference/knowledge-checks-validate.d.ts +1 -1
  61. package/dist/reference/knowledge-checks.d.mts +3 -1
  62. package/dist/reference/knowledge-checks.mjs +9 -4
  63. package/dist/reference/lab-math-scope.d.ts +1 -0
  64. package/dist/reference/lab-math-slot.d.ts +34 -0
  65. package/dist/reference/lab-math-slot.js +6 -0
  66. package/dist/reference/math-glyphs.d.mts +80 -4
  67. package/dist/reference/math-glyphs.mjs +572 -37
  68. package/dist/reference/page-notation.d.ts +6 -1
  69. package/dist/reference/raw-floats-validate.js +1 -1
  70. package/dist/reference/raw-floats.mjs +1 -1
  71. package/dist/reference/rehype-heading-anchors.d.mts +14 -0
  72. package/dist/reference/rehype-heading-anchors.mjs +116 -17
  73. package/dist/reference/rehype-table-header-labels.d.mts +6 -1
  74. package/dist/reference/rehype-table-header-labels.mjs +21 -3
  75. package/dist/reference/remark-asides.d.mts +3 -0
  76. package/dist/reference/remark-asides.mjs +180 -0
  77. package/dist/reference/remark-memo.d.mts +3 -1
  78. package/dist/reference/remark-memo.mjs +42 -11
  79. package/dist/reference/remark-notation.d.mts +60 -10
  80. package/dist/reference/remark-notation.mjs +387 -36
  81. package/dist/reference/render-lab-math.d.ts +4 -26
  82. package/dist/reference/render-lab-math.js +4 -4
  83. package/dist/reference/sections-validate.d.ts +27 -0
  84. package/dist/reference/sections-validate.js +41 -0
  85. package/dist/reference/sections.d.mts +97 -0
  86. package/dist/reference/sections.mjs +306 -0
  87. package/dist/reference/tables-validate.d.ts +1 -1
  88. package/dist/reference/types.d.ts +17 -1
  89. package/dist/reference/unit-words.d.ts +37 -0
  90. package/dist/reference/unit-words.js +170 -0
  91. package/dist/rules/catalog.d.ts +176 -0
  92. package/dist/rules/catalog.js +983 -0
  93. package/dist/rules/course-rules.d.ts +115 -0
  94. package/dist/rules/course-rules.js +292 -0
  95. package/dist/rules/course-rules.schema.json +138 -0
  96. package/dist/rules/in-effect.d.ts +53 -0
  97. package/dist/rules/in-effect.js +142 -0
  98. package/dist/rules/rules.schema.json +1072 -0
  99. package/dist/rules/settings.d.ts +3 -0
  100. package/dist/rules/settings.js +290 -0
  101. package/dist/rules/waivers.d.ts +53 -0
  102. package/dist/rules/waivers.js +132 -0
  103. package/dist/session/PreferenceStore.d.ts +5 -0
  104. package/dist/session/PreferenceStore.js +9 -0
  105. package/docs/VERSIONING.md +5 -3
  106. package/docs/authoring/README.md +232 -40
  107. package/docs/authoring/agents.md +55 -9
  108. package/docs/authoring/components.md +178 -22
  109. package/docs/authoring/migrating.md +1152 -2
  110. package/docs/authoring/notation.md +576 -322
  111. package/docs/authoring/prose.md +551 -0
  112. package/docs/authoring/retired.md +35 -0
  113. package/docs/authoring/structure-rules.md +422 -40
  114. package/docs/authoring/structure.md +445 -62
  115. package/docs/authoring/versioning.md +20 -28
  116. package/docs/authoring/visualizations.md +142 -20
  117. package/package.json +2 -2
  118. package/src/compiler/cli.ts +116 -6
  119. package/src/compiler/collections.ts +111 -7
  120. package/src/compiler/course-config.ts +273 -34
  121. package/src/compiler/knowledge-version.ts +1 -1
  122. package/src/compiler/lesson-derivation.ts +54 -15
  123. package/src/compiler/logging.ts +8 -2
  124. package/src/compiler/manifest.ts +573 -104
  125. package/src/components/BlockHeading.astro +113 -0
  126. package/src/components/CourseContents.astro +221 -0
  127. package/src/components/CurriculumMap.astro +5 -6
  128. package/src/components/FloatIndex.astro +200 -0
  129. package/src/components/HoverPanel.astro +431 -79
  130. package/src/components/PartBridge.astro +27 -0
  131. package/src/components/PartOverview.astro +401 -0
  132. package/src/components/PartScope.astro +27 -0
  133. package/src/components/abbreviation/AbbreviationLayer.astro +49 -51
  134. package/src/components/assessments/AssessmentRunner.tsx +8 -5
  135. package/src/components/assessments/AssessmentSet.astro +23 -26
  136. package/src/components/block-heading.ts +146 -0
  137. package/src/components/card-arrow.ts +243 -0
  138. package/src/components/card-drag.ts +631 -0
  139. package/src/components/card-jump.ts +45 -0
  140. package/src/components/card-manager.ts +1589 -0
  141. package/src/components/card-memory.ts +177 -0
  142. package/src/components/card-placement.ts +504 -0
  143. package/src/components/card-side.ts +336 -0
  144. package/src/components/card-travel.ts +197 -0
  145. package/src/components/citation/CitationLayer.astro +77 -76
  146. package/src/components/diagram/Diagram.astro +161 -316
  147. package/src/components/disclosure/course-disclosure.ts +24 -0
  148. package/src/components/disclosure/hover-preview.ts +122 -42
  149. package/src/components/disclosure/persisted-disclosure.ts +3 -3
  150. package/src/components/disclosure/print-as-shown.ts +252 -0
  151. package/src/components/examples/CompactExample.astro +3 -1
  152. package/src/components/examples/CompactExamples.astro +101 -94
  153. package/src/components/figure/Figure.astro +174 -274
  154. package/src/components/figure/FloatCaption.astro +87 -0
  155. package/src/components/figure/figure-state.ts +58 -0
  156. package/src/components/figure/figure-tools.ts +87 -0
  157. package/src/components/float-name.ts +101 -0
  158. package/src/components/fold/Fold.astro +125 -0
  159. package/src/components/fold/fold-id.ts +40 -0
  160. package/src/components/memo/Memo.astro +162 -231
  161. package/src/components/memo/ShortNote.astro +6 -6
  162. package/src/components/notation/NotationFormula.astro +5 -1
  163. package/src/components/notation/NotationGlossary.astro +3 -4
  164. package/src/components/notation/NotationLayer.astro +169 -162
  165. package/src/components/notation/NotationMath.astro +10 -1
  166. package/src/components/reference/FloatRefLayer.astro +485 -423
  167. package/src/components/starlight/AboutPanel.astro +5 -4
  168. package/src/components/starlight/CollapsibleSections.astro +207 -93
  169. package/src/components/starlight/DiagramEnhancer.astro +15 -138
  170. package/src/components/starlight/HeadingAnchorEnhancer.astro +193 -1
  171. package/src/components/starlight/KeyedFloatEnhancer.astro +100 -9
  172. package/src/components/starlight/LayoutHeader.astro +298 -19
  173. package/src/components/starlight/LayoutPageSidebar.astro +99 -12
  174. package/src/components/starlight/LayoutPageTitle.astro +120 -0
  175. package/src/components/starlight/LayoutSidebar.astro +229 -11
  176. package/src/components/starlight/LessonExportActions.astro +53 -72
  177. package/src/components/starlight/LessonFooter.astro +26 -4
  178. package/src/components/starlight/section-parts.ts +15 -2
  179. package/src/components/starlight/section-summary.ts +181 -0
  180. package/src/components/table/Table.astro +95 -76
  181. package/src/components/table/TableOverflowExpander.astro +65 -28
  182. package/src/content-config.ts +40 -4
  183. package/src/reference/base-accents.mjs +31 -0
  184. package/src/reference/base-roman-labels.mjs +2 -2
  185. package/src/reference/consistency.ts +176 -5
  186. package/src/reference/delimiter-size.ts +3 -2
  187. package/src/reference/diagrams-validate.ts +17 -1
  188. package/src/reference/equations-validate.ts +10 -5
  189. package/src/reference/equations.mjs +83 -43
  190. package/src/reference/examples-validate.ts +97 -0
  191. package/src/reference/figures-validate.ts +6 -1
  192. package/src/reference/folds-validate.ts +65 -0
  193. package/src/reference/gate-abbreviations.ts +1 -0
  194. package/src/reference/gate-course-rules.ts +134 -0
  195. package/src/reference/gate-heading-length.ts +66 -35
  196. package/src/reference/gate-heading-structure.ts +193 -0
  197. package/src/reference/gate-math-punctuation.ts +381 -0
  198. package/src/reference/gate-math.ts +69 -10
  199. package/src/reference/gate-part-pages.ts +217 -0
  200. package/src/reference/gate-prose-notation.ts +59 -12
  201. package/src/reference/katex-delimiters.mjs +20 -7
  202. package/src/reference/keyed-float-validate.ts +45 -11
  203. package/src/reference/keyed-jsx-floats.mjs +73 -20
  204. package/src/reference/knowledge-checks-validate.ts +1 -1
  205. package/src/reference/knowledge-checks.mjs +9 -4
  206. package/src/reference/lab-math-slot.ts +36 -0
  207. package/src/reference/math-glyphs.mjs +572 -37
  208. package/src/reference/raw-floats-validate.ts +1 -1
  209. package/src/reference/raw-floats.mjs +1 -1
  210. package/src/reference/rehype-heading-anchors.mjs +116 -17
  211. package/src/reference/rehype-table-header-labels.mjs +21 -3
  212. package/src/reference/remark-asides.mjs +180 -0
  213. package/src/reference/remark-memo.mjs +42 -11
  214. package/src/reference/remark-notation.mjs +387 -36
  215. package/src/reference/render-lab-math.ts +16 -31
  216. package/src/reference/sections-validate.ts +81 -0
  217. package/src/reference/sections.mjs +306 -0
  218. package/src/reference/tables-validate.ts +2 -1
  219. package/src/reference/types.ts +17 -0
  220. package/src/reference/unit-words.ts +233 -0
  221. package/src/rules/catalog.ts +1298 -0
  222. package/src/rules/course-rules.ts +456 -0
  223. package/src/rules/in-effect.ts +235 -0
  224. package/src/rules/settings.ts +448 -0
  225. package/src/rules/waivers.ts +249 -0
  226. package/src/session/PreferenceStore.ts +15 -0
  227. package/src/styles/global.css +1467 -163
  228. package/src/styles/surfaces.css +182 -0
  229. package/src/components/hover-panel.ts +0 -339
  230. package/src/components/popup-stack.ts +0 -345
@@ -20,11 +20,23 @@
20
20
  * Box sizes are a generous estimate (`reference/diagram-layout.ts`) unless a
21
21
  * node gives an explicit `width`/`height` — there is no DOM at build time to
22
22
  * measure a real render against.
23
+ *
24
+ * Named like every float (docs/plans/titles-and-containers.md Part 3): the
25
+ * long `caption` sits below the graph with its number, in the same caption
26
+ * line a `<Figure>` has (`../figure/FloatCaption.astro`), and an optional
27
+ * `short` caption heads the `[[dia-…]]` preview card. A diagram is always
28
+ * its graph and then that line (docs/plans/figures-and-short-captions.md,
29
+ * decision 1); the line ends with the widen button ↔, shown while the graph
30
+ * overflows its column (`../starlight/DiagramEnhancer.astro`). To let a
31
+ * reader fold a diagram away, wrap it in a `<Fold>` or put it in a
32
+ * collapsible section.
23
33
  */
24
34
  import { estimateBoxSize, layoutDiagram } from '../../reference/diagram-layout';
25
35
  import { renderNotationFormula } from '../notation/render-notation-math';
26
36
  import type { SafeMathNode as SafeMathNodeType } from '../notation/render-notation-math';
27
37
  import SafeMathNode from '../notation/SafeMathNode.astro';
38
+ import FloatCaption from '../figure/FloatCaption.astro';
39
+ import { DIAGRAM_NO_LONGER_FOLDS } from '../../reference/diagrams-validate';
28
40
  import type { GlyphScopeEntry } from '../../reference/gloss';
29
41
  import {
30
42
  notationKeysInMathNodes,
@@ -50,8 +62,10 @@ export interface DiagramEdge {
50
62
  export interface Props {
51
63
  /** Unique on the page — namespaces the arrowhead marker id. */
52
64
  id: string;
53
- /** Visible caption; also the diagram's accessible name. */
54
- label: string;
65
+ /** The long caption, shown below the diagram with its number; also its accessible name. */
66
+ caption: string;
67
+ /** Optional short caption: heads the `[[dia-…]]` preview card; rule S1 measures it. */
68
+ short?: string;
55
69
  nodes: readonly DiagramNode[];
56
70
  edges: readonly DiagramEdge[];
57
71
  /** Glyph scope `formula` resolves against — omit if no node uses `formula`. */
@@ -61,10 +75,6 @@ export interface Props {
61
75
  number?: string;
62
76
  /** Computed by `remark-notation.mjs` for a top-level lesson diagram. */
63
77
  anchorId?: string;
64
- /** Author-chosen state for a reader with nothing saved yet. Default: `true`. */
65
- defaultOpen?: boolean;
66
- /** Persist the reader's open/closed choice across visits. Default: `true`. */
67
- rememberState?: boolean;
68
78
  /**
69
79
  * Emit a build warning when the laid-out SVG is wider than this many pixels.
70
80
  * Set `false` only when horizontal overflow is deliberate. Default: `800`.
@@ -74,15 +84,14 @@ export interface Props {
74
84
 
75
85
  const {
76
86
  id,
77
- label,
87
+ caption,
88
+ short,
78
89
  nodes,
79
90
  edges,
80
91
  scope = [],
81
92
  direction = 'TB',
82
93
  number,
83
94
  anchorId,
84
- defaultOpen = true,
85
- rememberState = true,
86
95
  warnAboveWidth = 800,
87
96
  } = Astro.props;
88
97
 
@@ -90,6 +99,26 @@ const idPattern = /^[A-Za-z][A-Za-z0-9_-]*$/;
90
99
  if (!idPattern.test(id)) {
91
100
  throw new Error(`Unsafe Diagram id: ${id}`);
92
101
  }
102
+ for (const removed of ['label', 'title'] as const) {
103
+ if (removed in Astro.props) {
104
+ throw new Error(
105
+ `<Diagram id="${id}"> has no \`${removed}\` prop: a diagram is named by its \`caption\` (shown below it with its number) and an optional \`short\` caption.`,
106
+ );
107
+ }
108
+ }
109
+ // Also caught in the MDX by `dia-removed-prop`; this catches a diagram a
110
+ // wrapper component renders, whose props the MDX scan never sees.
111
+ for (const removed of ['defaultOpen', 'rememberState'] as const) {
112
+ if (removed in Astro.props) {
113
+ throw new Error(`<Diagram id="${id}"> has no \`${removed}\` prop: ${DIAGRAM_NO_LONGER_FOLDS}`);
114
+ }
115
+ }
116
+ if (typeof caption !== 'string' || !caption.trim()) {
117
+ throw new Error(`Diagram "${id}" requires a non-empty \`caption\` prop.`);
118
+ }
119
+ if (short !== undefined && (typeof short !== 'string' || !short.trim())) {
120
+ throw new Error(`Diagram "${id}" \`short\` must be a non-empty string when given.`);
121
+ }
93
122
  if (nodes.length === 0) {
94
123
  throw new Error(`Diagram "${id}" has no nodes.`);
95
124
  }
@@ -172,6 +201,7 @@ const markerId = `diagram-arrow-${id}`;
172
201
  const legacyAnchorId = `diagram-${id}`;
173
202
  const renderedAnchorId = anchorId ?? legacyAnchorId;
174
203
  const viewportId = `${legacyAnchorId}-viewport`;
204
+ const captionTextId = `${legacyAnchorId}-caption`;
175
205
 
176
206
  if (warnAboveWidth !== false && layout.width > warnAboveWidth) {
177
207
  console.warn(
@@ -193,158 +223,140 @@ function pathFrom(points: readonly { x: number; y: number }[]): string {
193
223
  <figure
194
224
  class="diagram"
195
225
  id={renderedAnchorId}
196
- aria-labelledby={`${id}-caption`}
197
226
  data-dia-number={number}
198
227
  data-dia-key={number ? id : undefined}
228
+ data-dia-short={short}
199
229
  data-diagram-width={layout.width}
200
230
  data-diagram-expandable
201
231
  >
202
- <div class="diagram__titlebar">
203
- <details
204
- class="diagram__details"
205
- data-diagram-details
206
- data-remember-state={String(rememberState)}
207
- data-state={defaultOpen ? 'open' : 'closed'}
208
- open={defaultOpen}
232
+ <div class="diagram__body">
233
+ <div
234
+ class="diagram-stage"
235
+ id={renderedAnchorId === legacyAnchorId ? undefined : legacyAnchorId}
209
236
  >
210
- <summary class="diagram__summary" id={`${id}-caption`}>
211
- {number && <span class="diagram__number">Diagram {number}</span>}
212
- <span class="diagram__caption-text">{label}</span>
213
- </summary>
214
- <div class="diagram__body" data-diagram-body>
237
+ <div
238
+ id={viewportId}
239
+ class="diagram-scroll"
240
+ tabindex="0"
241
+ role="group"
242
+ aria-labelledby={captionTextId}
243
+ >
215
244
  <div
216
- class="diagram-stage"
217
- id={renderedAnchorId === legacyAnchorId ? undefined : legacyAnchorId}
245
+ class="diagram-canvas"
246
+ style={`--diagram-print-ratio: ${layout.width} / ${layout.height}; --diagram-width: ${layout.width}; --diagram-height: ${layout.height}; width: ${layout.width}px; height: ${layout.height}px;`}
218
247
  >
219
- <div
220
- id={viewportId}
221
- class="diagram-scroll"
222
- tabindex="0"
223
- role="group"
224
- aria-labelledby={`${id}-caption`}
248
+ <svg
249
+ viewBox={`0 0 ${layout.width} ${layout.height}`}
250
+ width="100%"
251
+ height="100%"
225
252
  >
226
- <div
227
- class="diagram-canvas"
228
- style={`--diagram-print-ratio: ${layout.width} / ${layout.height}; width: ${layout.width}px; height: ${layout.height}px;`}
229
- >
230
- <svg
231
- viewBox={`0 0 ${layout.width} ${layout.height}`}
232
- width="100%"
233
- height="100%"
253
+ <defs>
254
+ <marker
255
+ id={markerId}
256
+ viewBox="0 0 10 10"
257
+ refX="9"
258
+ refY="5"
259
+ markerWidth="7"
260
+ markerHeight="7"
261
+ orient="auto-start-reverse"
234
262
  >
235
- <defs>
236
- <marker
237
- id={markerId}
238
- viewBox="0 0 10 10"
239
- refX="9"
240
- refY="5"
241
- markerWidth="7"
242
- markerHeight="7"
243
- orient="auto-start-reverse"
244
- >
245
- <path
246
- d="M 0 0 L 10 5 L 0 10 z"
247
- class="diagram-arrowhead"
248
- />
249
- </marker>
250
- </defs>
251
-
252
- {
253
- layout.edges.map((edge) => (
254
- <path
255
- d={pathFrom(edge.points)}
256
- class="diagram-edge"
257
- marker-end={`url(#${markerId})`}
263
+ <path
264
+ d="M 0 0 L 10 5 L 0 10 z"
265
+ class="diagram-arrowhead"
266
+ />
267
+ </marker>
268
+ </defs>
269
+
270
+ {
271
+ layout.edges.map((edge) => (
272
+ <path
273
+ d={pathFrom(edge.points)}
274
+ class="diagram-edge"
275
+ marker-end={`url(#${markerId})`}
276
+ />
277
+ ))
278
+ }
279
+
280
+ {
281
+ layout.nodes.map((position) => {
282
+ const node = nodeById.get(position.id)!;
283
+ // Position each node with x/y attributes, never a
284
+ // `transform` on the group: WebKit (every iOS browser)
285
+ // paints a `foreignObject` descendant that gets its own
286
+ // render layer — every KaTeX `position: relative` span —
287
+ // without the ancestor transform, so formulas pile up at
288
+ // the SVG origin while their boxes stay put.
289
+ return (
290
+ <g class="diagram-node">
291
+ <rect
292
+ x={position.x}
293
+ y={position.y}
294
+ width={position.width}
295
+ height={position.height}
296
+ rx="8"
297
+ ry="8"
258
298
  />
259
- ))
260
- }
261
-
262
- {
263
- layout.nodes.map((position) => {
264
- const node = nodeById.get(position.id)!;
265
- return (
266
- <g
267
- transform={`translate(${position.x}, ${position.y})`}
268
- class="diagram-node"
299
+ <foreignObject
300
+ x={position.x}
301
+ y={position.y}
302
+ width={position.width}
303
+ height={position.height}
304
+ >
305
+ <div
306
+ xmlns="http://www.w3.org/1999/xhtml"
307
+ class="diagram-node-content"
269
308
  >
270
- <rect
271
- width={position.width}
272
- height={position.height}
273
- rx="8"
274
- ry="8"
275
- />
276
- <foreignObject
277
- width={position.width}
278
- height={position.height}
279
- >
280
- <div
281
- xmlns="http://www.w3.org/1999/xhtml"
282
- class="diagram-node-content"
283
- >
284
- {
285
- node.text && (
286
- <span class="diagram-node-text">{node.text}</span>
287
- )
288
- }
289
- {
290
- node.mathNodes && (
291
- <span class="diagram-node-formula notation-math">
292
- {node.mathNodes.map((mathNode) => (
293
- <SafeMathNode node={mathNode} />
294
- ))}
295
- </span>
296
- )
297
- }
298
- </div>
299
- </foreignObject>
300
- </g>
301
- );
302
- })
303
- }
304
- </svg>
305
- </div>
306
- </div>
309
+ {
310
+ node.text && (
311
+ <span class="diagram-node-text">{node.text}</span>
312
+ )
313
+ }
314
+ {
315
+ node.mathNodes && (
316
+ <span class="diagram-node-formula notation-math">
317
+ {node.mathNodes.map((mathNode) => (
318
+ <SafeMathNode node={mathNode} />
319
+ ))}
320
+ </span>
321
+ )
322
+ }
323
+ </div>
324
+ </foreignObject>
325
+ </g>
326
+ );
327
+ })
328
+ }
329
+ </svg>
307
330
  </div>
308
331
  </div>
309
- </details>
310
-
311
- <a
312
- class="diagram__title-anchor"
313
- href={`#${renderedAnchorId}`}
314
- aria-label={`${number ? `Diagram ${number}: ` : ''}${label}`}
315
- >
316
- <svg
317
- aria-hidden="true"
318
- width="16"
319
- height="16"
320
- viewBox="0 0 24 24"
321
- fill="currentColor"
322
- ><path
323
- d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"
324
- ></path></svg
325
- >
326
- </a>
327
-
328
- <div class="diagram-toolbar" data-diagram-toolbar hidden>
329
- <button
330
- type="button"
331
- class="diagram-expand-button"
332
- data-diagram-expand
333
- aria-controls={viewportId}
334
- aria-expanded="false"
335
- title="Expand diagram horizontally"
336
- >
337
- <svg aria-hidden="true" viewBox="0 0 24 24">
338
- <path class="diagram-expand-icon" d="M3 12h18M8 7l-5 5 5 5M16 7l5 5-5 5" />
339
- <path
340
- class="diagram-shrink-icon"
341
- d="M3 12h7M7 9l3 3-3 3M21 12h-7M17 9l-3 3 3 3"
342
- />
343
- </svg>
344
- <span class="sr-only">Expand {label}</span>
345
- </button>
346
332
  </div>
347
333
  </div>
334
+
335
+ <FloatCaption
336
+ kind="Diagram"
337
+ prefix="diagram"
338
+ caption={caption}
339
+ short={short}
340
+ anchorId={renderedAnchorId}
341
+ number={number}
342
+ textId={captionTextId}
343
+ >
344
+ <button
345
+ type="button"
346
+ class="float-tool diagram__widen"
347
+ data-diagram-expand
348
+ aria-controls={viewportId}
349
+ aria-expanded="false"
350
+ aria-label={`Expand ${number ? `Diagram ${number}` : 'this diagram'} horizontally`}
351
+ title="Expand diagram horizontally"
352
+ hidden
353
+ >
354
+ <svg aria-hidden="true" viewBox="0 0 24 24">
355
+ <path class="float-tool__widen" d="M3 12h18M8 7l-5 5 5 5M16 7l5 5-5 5" />
356
+ <path class="float-tool__narrow" d="M3 12h7M7 9l3 3-3 3M21 12h-7M17 9l-3 3 3 3" />
357
+ </svg>
358
+ </button>
359
+ </FloatCaption>
348
360
  </figure>
349
361
 
350
362
  <style>
@@ -356,112 +368,10 @@ function pathFrom(points: readonly { x: number; y: number }[]): string {
356
368
  contain: inline-size;
357
369
  }
358
370
 
359
- .diagram__titlebar {
360
- position: relative;
361
- }
362
-
363
- .diagram__details {
364
- border-inline-start: 0;
365
- padding-inline-start: 0;
366
- }
367
-
368
- .diagram__summary {
369
- position: relative;
370
- min-height: 1.75rem;
371
- padding-inline-end: 4rem;
372
- font-weight: 600;
373
- cursor: pointer;
374
- }
375
-
376
- .diagram__summary::before {
377
- position: absolute;
378
- top: 50%;
379
- left: -1.35em;
380
- margin: 0;
381
- transform: translateY(-50%);
382
- opacity: 0;
383
- transition:
384
- opacity 150ms ease 700ms,
385
- transform 150ms ease;
386
- }
387
-
388
- .diagram__details[data-state='open'] > .diagram__summary::before {
389
- transform: translateY(-50%) rotateZ(90deg);
390
- }
391
-
392
- .diagram__details[data-state='closed'] > .diagram__summary::before {
393
- opacity: 1;
394
- transition: none;
395
- }
396
-
397
- .diagram__summary:hover::before,
398
- .diagram__summary:focus-visible::before {
399
- opacity: 1;
400
- transition: opacity 100ms ease;
401
- }
402
-
371
+ /* The caption line, its links and its widen button are styled once for
372
+ every float, in `global.css` (`.float-caption`, `.float-tool`). */
403
373
  .diagram__body {
404
374
  overflow: hidden;
405
- transition: max-height 220ms ease;
406
- }
407
-
408
- .diagram__number {
409
- color: var(--sl-color-text-accent);
410
- }
411
-
412
- .diagram__caption-text::before {
413
- content: ' — ';
414
- }
415
-
416
- .diagram__summary > .diagram__caption-text:first-child::before {
417
- content: none;
418
- }
419
-
420
- .diagram__title-anchor {
421
- position: absolute;
422
- top: 0.1rem;
423
- inset-inline-end: 0;
424
- display: inline-flex;
425
- width: 1.5rem;
426
- height: 1.5rem;
427
- align-items: center;
428
- justify-content: center;
429
- color: var(--sl-color-gray-3);
430
- text-decoration: none;
431
- opacity: 1;
432
- }
433
-
434
- .diagram[data-diagram-overflow='true'] .diagram__title-anchor {
435
- inset-inline-end: 2rem;
436
- }
437
-
438
- .diagram__title-anchor svg {
439
- width: 0.9rem;
440
- height: 0.9rem;
441
- }
442
-
443
- .diagram__title-anchor:hover,
444
- .diagram__title-anchor:focus-visible {
445
- color: var(--sl-color-text-accent);
446
- }
447
-
448
- .diagram__title-anchor:focus-visible {
449
- outline: 2px solid var(--sl-color-accent);
450
- outline-offset: 0.1rem;
451
- }
452
-
453
- @media (hover: hover) {
454
- .diagram__title-anchor {
455
- opacity: 0;
456
- }
457
-
458
- .diagram__details:has(> .diagram__summary:hover) ~ .diagram__title-anchor,
459
- .diagram__details:has(> .diagram__summary:focus-visible)
460
- ~ .diagram__title-anchor,
461
- .diagram__title-anchor:hover,
462
- .diagram__title-anchor:focus-visible {
463
- opacity: 1;
464
- }
465
375
  }
466
376
 
467
377
  .diagram-stage {
@@ -470,56 +380,6 @@ function pathFrom(points: readonly { x: number; y: number }[]): string {
470
380
  overflow: hidden;
471
381
  }
472
382
 
473
- .diagram-toolbar {
474
- position: absolute;
475
- z-index: 2;
476
- top: -0.25rem;
477
- inset-inline-end: 0;
478
- }
479
-
480
- .diagram-toolbar[hidden] {
481
- display: none;
482
- }
483
-
484
- .diagram-expand-button {
485
- display: inline-flex;
486
- width: 1.75rem;
487
- height: 1.75rem;
488
- margin: 0;
489
- align-items: center;
490
- justify-content: center;
491
- color: var(--sl-color-gray-2);
492
- border: 0;
493
- border-radius: 0.25rem;
494
- background: transparent;
495
- cursor: pointer;
496
- }
497
-
498
- .diagram-expand-button svg {
499
- width: 1.1rem;
500
- height: 1.1rem;
501
- fill: none;
502
- stroke: currentColor;
503
- stroke-width: 1.8;
504
- stroke-linecap: round;
505
- stroke-linejoin: round;
506
- }
507
-
508
- .diagram-expand-button[aria-expanded='false'] .diagram-shrink-icon,
509
- .diagram-expand-button[aria-expanded='true'] .diagram-expand-icon {
510
- display: none;
511
- }
512
-
513
- .diagram-expand-button:hover {
514
- color: var(--sl-color-text);
515
- background: var(--sl-color-accent-low);
516
- }
517
-
518
- .diagram-expand-button:focus-visible {
519
- outline: 2px solid var(--sl-color-accent);
520
- outline-offset: 1px;
521
- }
522
-
523
383
  /* A wide graph scrolls inside its own box rather than shrinking to
524
384
  illegibility — the same tradeoff `.notation-formula-math` makes for a
525
385
  wide equation. `tabindex` on the scroller is the accessibility half of
@@ -595,29 +455,14 @@ function pathFrom(points: readonly { x: number; y: number }[]): string {
595
455
  fill: var(--sl-color-gray-4);
596
456
  }
597
457
 
458
+ /* A diagram prints its whole graph (`global.css` resets the scroll box
459
+ for print). */
598
460
  @media print {
599
- .diagram-toolbar,
600
- .diagram__title-anchor,
601
- .diagram__summary::before {
602
- display: none;
603
- }
604
-
605
- .diagram__number {
606
- color: inherit;
607
- }
608
-
609
461
  .diagram__body {
610
462
  display: block !important;
611
463
  max-height: none !important;
612
464
  overflow: visible !important;
613
465
  }
614
466
  }
615
-
616
- @media (prefers-reduced-motion: reduce) {
617
- .diagram__summary::before,
618
- .diagram__body {
619
- transition: none;
620
- }
621
- }
622
467
  }
623
468
  </style>
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The course's collapse defaults at build time — `manifest.disclosure`, the
3
+ * `disclosure` block of the course settings with the engine defaults applied
4
+ * (`normalizeDisclosure` in `../../compiler/course-config`). A component
5
+ * reads its kind's first-visit state here instead of hardcoding one, and a
6
+ * per-instance prop (`defaultOpen`) still overrides it.
7
+ *
8
+ * Read once per build: every float on every page asks, and the manifest does
9
+ * not change under a running build (a content edit in dev restarts Astro).
10
+ */
11
+ import {
12
+ DISCLOSURE_DEFAULTS,
13
+ type NormalizedDisclosure,
14
+ } from '../../compiler/course-config';
15
+ import { loadManifest } from '../../compiler/manifest';
16
+
17
+ let cached: Promise<NormalizedDisclosure> | undefined;
18
+
19
+ export function courseDisclosure(): Promise<NormalizedDisclosure> {
20
+ cached ??= loadManifest().then(
21
+ (manifest) => manifest.disclosure ?? DISCLOSURE_DEFAULTS,
22
+ );
23
+ return cached;
24
+ }