explico 0.8.0 → 0.11.0
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 +1239 -1
- package/README.md +60 -15
- package/dist/compiler/astro.d.ts +6 -1
- package/dist/compiler/cli.d.ts +46 -2
- package/dist/compiler/cli.js +70 -4
- package/dist/compiler/collections.d.ts +35 -2
- package/dist/compiler/collections.js +68 -5
- package/dist/compiler/course-config.d.ts +3 -194
- package/dist/compiler/course-config.js +78 -5
- package/dist/compiler/exports/standalone-html.d.ts +6 -1
- package/dist/compiler/knowledge-version.js +1 -1
- package/dist/compiler/lesson-derivation.d.ts +27 -10
- package/dist/compiler/lesson-derivation.js +23 -8
- package/dist/compiler/logging.d.ts +4 -1
- package/dist/compiler/logging.js +4 -1
- package/dist/compiler/manifest.d.ts +204 -31
- package/dist/compiler/manifest.js +270 -55
- package/dist/compiler/resolution-report.d.ts +6 -1
- package/dist/course-config-Dsnri9Tk.d.ts +464 -0
- package/dist/reference/base-accents.d.mts +26 -0
- package/dist/reference/base-accents.mjs +31 -0
- package/dist/reference/base-roman-labels.d.mts +2 -2
- package/dist/reference/base-roman-labels.mjs +2 -2
- package/dist/reference/consistency.d.ts +14 -2
- package/dist/reference/consistency.js +108 -4
- package/dist/reference/delimiter-size.d.ts +3 -2
- package/dist/reference/diagrams-validate.d.ts +7 -2
- package/dist/reference/diagrams-validate.js +9 -1
- package/dist/reference/equations-validate.d.ts +8 -5
- package/dist/reference/equations-validate.js +1 -0
- package/dist/reference/equations.d.mts +60 -31
- package/dist/reference/equations.mjs +83 -43
- package/dist/reference/examples-validate.d.ts +31 -0
- package/dist/reference/examples-validate.js +48 -0
- package/dist/reference/figures-validate.d.ts +1 -1
- package/dist/reference/figures-validate.js +4 -1
- package/dist/reference/folds-validate.d.ts +33 -0
- package/dist/reference/folds-validate.js +32 -0
- package/dist/reference/gate-abbreviations.d.ts +3 -1
- package/dist/reference/gate-abbreviations.js +1 -0
- package/dist/reference/gate-course-rules.d.ts +32 -0
- package/dist/reference/gate-course-rules.js +85 -0
- package/dist/reference/gate-heading-length.d.ts +5 -2
- package/dist/reference/gate-heading-length.js +45 -21
- package/dist/reference/gate-heading-structure.d.ts +41 -0
- package/dist/reference/gate-heading-structure.js +113 -0
- package/dist/reference/gate-math-punctuation.d.ts +71 -0
- package/dist/reference/gate-math-punctuation.js +251 -0
- package/dist/reference/gate-math.d.ts +22 -6
- package/dist/reference/gate-math.js +42 -8
- package/dist/reference/gate-part-pages.d.ts +50 -0
- package/dist/reference/gate-part-pages.js +118 -0
- package/dist/reference/gate-prose-notation.d.ts +21 -11
- package/dist/reference/gate-prose-notation.js +25 -7
- package/dist/reference/katex-delimiters.mjs +20 -7
- package/dist/reference/keyed-float-validate.d.ts +27 -10
- package/dist/reference/keyed-float-validate.js +15 -1
- package/dist/reference/keyed-jsx-floats.d.mts +11 -5
- package/dist/reference/keyed-jsx-floats.mjs +73 -20
- package/dist/reference/knowledge-checks-validate.d.ts +1 -1
- package/dist/reference/knowledge-checks.d.mts +3 -1
- package/dist/reference/knowledge-checks.mjs +9 -4
- package/dist/reference/lab-math-scope.d.ts +1 -0
- package/dist/reference/lab-math-slot.d.ts +34 -0
- package/dist/reference/lab-math-slot.js +6 -0
- package/dist/reference/math-glyphs.d.mts +80 -4
- package/dist/reference/math-glyphs.mjs +572 -37
- package/dist/reference/page-notation.d.ts +6 -1
- package/dist/reference/raw-floats-validate.js +1 -1
- package/dist/reference/raw-floats.mjs +1 -1
- package/dist/reference/rehype-heading-anchors.d.mts +14 -0
- package/dist/reference/rehype-heading-anchors.mjs +116 -17
- package/dist/reference/rehype-table-header-labels.d.mts +6 -1
- package/dist/reference/rehype-table-header-labels.mjs +21 -3
- package/dist/reference/remark-asides.d.mts +3 -0
- package/dist/reference/remark-asides.mjs +180 -0
- package/dist/reference/remark-memo.d.mts +3 -1
- package/dist/reference/remark-memo.mjs +42 -11
- package/dist/reference/remark-notation.d.mts +60 -10
- package/dist/reference/remark-notation.mjs +387 -36
- package/dist/reference/render-lab-math.d.ts +4 -26
- package/dist/reference/render-lab-math.js +4 -4
- package/dist/reference/sections-validate.d.ts +27 -0
- package/dist/reference/sections-validate.js +41 -0
- package/dist/reference/sections.d.mts +97 -0
- package/dist/reference/sections.mjs +306 -0
- package/dist/reference/tables-validate.d.ts +1 -1
- package/dist/reference/types.d.ts +17 -1
- package/dist/reference/unit-words.d.ts +37 -0
- package/dist/reference/unit-words.js +170 -0
- package/dist/rules/catalog.d.ts +176 -0
- package/dist/rules/catalog.js +983 -0
- package/dist/rules/course-rules.d.ts +115 -0
- package/dist/rules/course-rules.js +292 -0
- package/dist/rules/course-rules.schema.json +138 -0
- package/dist/rules/in-effect.d.ts +53 -0
- package/dist/rules/in-effect.js +142 -0
- package/dist/rules/rules.schema.json +1072 -0
- package/dist/rules/settings.d.ts +3 -0
- package/dist/rules/settings.js +290 -0
- package/dist/rules/waivers.d.ts +53 -0
- package/dist/rules/waivers.js +132 -0
- package/dist/session/PreferenceStore.d.ts +5 -0
- package/dist/session/PreferenceStore.js +9 -0
- package/docs/VERSIONING.md +2 -2
- package/docs/authoring/README.md +232 -40
- package/docs/authoring/agents.md +55 -9
- package/docs/authoring/components.md +178 -22
- package/docs/authoring/migrating.md +1096 -2
- package/docs/authoring/notation.md +576 -322
- package/docs/authoring/prose.md +551 -0
- package/docs/authoring/retired.md +35 -0
- package/docs/authoring/structure-rules.md +422 -40
- package/docs/authoring/structure.md +445 -62
- package/docs/authoring/versioning.md +20 -28
- package/docs/authoring/visualizations.md +142 -20
- package/package.json +2 -2
- package/src/compiler/cli.ts +116 -6
- package/src/compiler/collections.ts +111 -7
- package/src/compiler/course-config.ts +273 -34
- package/src/compiler/knowledge-version.ts +1 -1
- package/src/compiler/lesson-derivation.ts +54 -15
- package/src/compiler/logging.ts +8 -2
- package/src/compiler/manifest.ts +573 -104
- package/src/components/BlockHeading.astro +113 -0
- package/src/components/CourseContents.astro +221 -0
- package/src/components/CurriculumMap.astro +5 -6
- package/src/components/FloatIndex.astro +200 -0
- package/src/components/HoverPanel.astro +431 -79
- package/src/components/PartBridge.astro +27 -0
- package/src/components/PartOverview.astro +401 -0
- package/src/components/PartScope.astro +27 -0
- package/src/components/abbreviation/AbbreviationLayer.astro +49 -51
- package/src/components/assessments/AssessmentRunner.tsx +8 -5
- package/src/components/assessments/AssessmentSet.astro +23 -26
- package/src/components/block-heading.ts +146 -0
- package/src/components/card-arrow.ts +243 -0
- package/src/components/card-drag.ts +631 -0
- package/src/components/card-jump.ts +45 -0
- package/src/components/card-manager.ts +1589 -0
- package/src/components/card-memory.ts +177 -0
- package/src/components/card-placement.ts +504 -0
- package/src/components/card-side.ts +336 -0
- package/src/components/card-travel.ts +197 -0
- package/src/components/citation/CitationLayer.astro +77 -76
- package/src/components/diagram/Diagram.astro +161 -316
- package/src/components/disclosure/course-disclosure.ts +24 -0
- package/src/components/disclosure/hover-preview.ts +122 -42
- package/src/components/disclosure/persisted-disclosure.ts +3 -3
- package/src/components/disclosure/print-as-shown.ts +252 -0
- package/src/components/examples/CompactExample.astro +3 -1
- package/src/components/examples/CompactExamples.astro +101 -94
- package/src/components/figure/Figure.astro +174 -274
- package/src/components/figure/FloatCaption.astro +87 -0
- package/src/components/figure/figure-state.ts +58 -0
- package/src/components/figure/figure-tools.ts +87 -0
- package/src/components/float-name.ts +101 -0
- package/src/components/fold/Fold.astro +125 -0
- package/src/components/fold/fold-id.ts +40 -0
- package/src/components/memo/Memo.astro +162 -231
- package/src/components/memo/ShortNote.astro +6 -6
- package/src/components/notation/NotationFormula.astro +5 -1
- package/src/components/notation/NotationGlossary.astro +3 -4
- package/src/components/notation/NotationLayer.astro +169 -162
- package/src/components/notation/NotationMath.astro +10 -1
- package/src/components/reference/FloatRefLayer.astro +485 -423
- package/src/components/starlight/AboutPanel.astro +5 -4
- package/src/components/starlight/CollapsibleSections.astro +207 -93
- package/src/components/starlight/DiagramEnhancer.astro +15 -138
- package/src/components/starlight/HeadingAnchorEnhancer.astro +193 -1
- package/src/components/starlight/KeyedFloatEnhancer.astro +100 -9
- package/src/components/starlight/LayoutHeader.astro +298 -19
- package/src/components/starlight/LayoutPageSidebar.astro +99 -12
- package/src/components/starlight/LayoutPageTitle.astro +120 -0
- package/src/components/starlight/LayoutSidebar.astro +229 -11
- package/src/components/starlight/LessonExportActions.astro +53 -72
- package/src/components/starlight/LessonFooter.astro +26 -4
- package/src/components/starlight/section-parts.ts +15 -2
- package/src/components/starlight/section-summary.ts +181 -0
- package/src/components/table/Table.astro +95 -76
- package/src/components/table/TableOverflowExpander.astro +65 -28
- package/src/content-config.ts +40 -4
- package/src/reference/base-accents.mjs +31 -0
- package/src/reference/base-roman-labels.mjs +2 -2
- package/src/reference/consistency.ts +176 -5
- package/src/reference/delimiter-size.ts +3 -2
- package/src/reference/diagrams-validate.ts +17 -1
- package/src/reference/equations-validate.ts +10 -5
- package/src/reference/equations.mjs +83 -43
- package/src/reference/examples-validate.ts +97 -0
- package/src/reference/figures-validate.ts +6 -1
- package/src/reference/folds-validate.ts +65 -0
- package/src/reference/gate-abbreviations.ts +1 -0
- package/src/reference/gate-course-rules.ts +134 -0
- package/src/reference/gate-heading-length.ts +66 -35
- package/src/reference/gate-heading-structure.ts +193 -0
- package/src/reference/gate-math-punctuation.ts +381 -0
- package/src/reference/gate-math.ts +69 -10
- package/src/reference/gate-part-pages.ts +217 -0
- package/src/reference/gate-prose-notation.ts +59 -12
- package/src/reference/katex-delimiters.mjs +20 -7
- package/src/reference/keyed-float-validate.ts +45 -11
- package/src/reference/keyed-jsx-floats.mjs +73 -20
- package/src/reference/knowledge-checks-validate.ts +1 -1
- package/src/reference/knowledge-checks.mjs +9 -4
- package/src/reference/lab-math-slot.ts +36 -0
- package/src/reference/math-glyphs.mjs +572 -37
- package/src/reference/raw-floats-validate.ts +1 -1
- package/src/reference/raw-floats.mjs +1 -1
- package/src/reference/rehype-heading-anchors.mjs +116 -17
- package/src/reference/rehype-table-header-labels.mjs +21 -3
- package/src/reference/remark-asides.mjs +180 -0
- package/src/reference/remark-memo.mjs +42 -11
- package/src/reference/remark-notation.mjs +387 -36
- package/src/reference/render-lab-math.ts +16 -31
- package/src/reference/sections-validate.ts +81 -0
- package/src/reference/sections.mjs +306 -0
- package/src/reference/tables-validate.ts +2 -1
- package/src/reference/types.ts +17 -0
- package/src/reference/unit-words.ts +233 -0
- package/src/rules/catalog.ts +1298 -0
- package/src/rules/course-rules.ts +456 -0
- package/src/rules/in-effect.ts +235 -0
- package/src/rules/settings.ts +448 -0
- package/src/rules/waivers.ts +249 -0
- package/src/session/PreferenceStore.ts +15 -0
- package/src/styles/global.css +1471 -163
- package/src/styles/surfaces.css +182 -0
- package/src/components/hover-panel.ts +0 -339
- package/src/components/popup-stack.ts +0 -345
|
@@ -6,65 +6,53 @@
|
|
|
6
6
|
* mirrors exactly (separate counter, same `[[fig-<id>]]` / cross-page
|
|
7
7
|
* reference grammar, `number`/`anchorId` computed by `remark-notation.mjs`).
|
|
8
8
|
*
|
|
9
|
+
* **One caption pair** (docs/plans/titles-and-containers.md Part 3): a figure
|
|
10
|
+
* is named by `caption`, the long caption shown below it with its number,
|
|
11
|
+
* and an optional `short` caption, the name the `[[fig-…]]` preview card
|
|
12
|
+
* heads with (and rule S1 measures). There is no title bar: a lab inside the
|
|
13
|
+
* figure carries no title or caption of its own either — the figure's
|
|
14
|
+
* caption names the whole.
|
|
15
|
+
*
|
|
9
16
|
* `previewable` (default `true`) controls what the hover-preview card
|
|
10
17
|
* (`FloatRefLayer.astro`) shows for a `[[fig-<id>]]` reference elsewhere in
|
|
11
|
-
* the prose: `true` clones
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* card would be an inert, non-hydrated, id-colliding duplicate, not a
|
|
15
|
-
* working preview.
|
|
16
|
-
*
|
|
17
|
-
* Collapsible + persistent (docs/plans/collapsible-disclosure.md Part 1): the
|
|
18
|
-
* figure body sits in a native `<details>` whose `<summary>` is a persistent
|
|
19
|
-
* title bar showing `title` (a short label, distinct from the longer
|
|
20
|
-
* `caption`) — reading as an OS-window minimize/restore control while
|
|
21
|
-
* staying entirely native `<details>` semantics (`aria-expanded`, keyboard
|
|
22
|
-
* toggle, no bespoke ARIA needed). The rotating chevron itself isn't
|
|
23
|
-
* authored here: `@astrojs/starlight`'s own `markdown.css` already gives
|
|
24
|
-
* every `.sl-markdown-content summary` a disclosure-triangle `::before`
|
|
25
|
-
* marker that rotates 90° on `[open]` — reproducing that ourselves would
|
|
26
|
-
* just draw two.
|
|
18
|
+
* the prose: `true` clones the figure's picture — the default slot, never
|
|
19
|
+
* its tools — into the card, as a still copy; `false` shows the caption and
|
|
20
|
+
* number only.
|
|
27
21
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* hover-peek card — showing it twice would be redundant. `textContent`
|
|
34
|
-
* reads on a `display: none` element still work, so `FloatRefLayer`'s
|
|
35
|
-
* preview card (`captionFor` in `FloatRefLayer.astro`) can read it
|
|
36
|
-
* regardless of the source figure's collapse state.
|
|
22
|
+
* **Laid out as LaTeX lays it out, in every state**
|
|
23
|
+
* (docs/plans/figures-and-short-captions.md, decision 1): the picture, then
|
|
24
|
+
* the caption line (`FloatCaption.astro`). A figure never hides or compacts
|
|
25
|
+
* by itself; to make one fully collapsible, wrap it in a `<Fold>` or put it
|
|
26
|
+
* in a `### … [collapsible]` / `[card]` section.
|
|
37
27
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* `defaultOpen` sets the first-visit state; `rememberState` (default `true`)
|
|
52
|
-
* persists the reader's later choice per-page via
|
|
53
|
-
* `../disclosure/persisted-disclosure`.
|
|
28
|
+
* **A lab is picture → caption → tools** (decisions 3–6, 8). The default
|
|
29
|
+
* slot is the picture (a lab's plot); the named slot `tools` holds its
|
|
30
|
+
* controls — an instruction line, the sliders and fields, the live equation —
|
|
31
|
+
* and renders after the caption line, in a tray inside a `<details>`. Only
|
|
32
|
+
* the tools fold: the fold button ⌃ / ⌄ ends the caption line, and folded
|
|
33
|
+
* tools leave a "Hidden here: 2 sliders, 1 equation" line that unfolds them
|
|
34
|
+
* (`./figure-tools.ts`). Their first state is `defaultOpen`, else the
|
|
35
|
+
* course's `disclosure.figures`; `rememberState` (default `true`) keeps the
|
|
36
|
+
* reader's choice per page. The caption line has a live end the lab writes
|
|
37
|
+
* the case it shows into (`./figure-state.ts`). Print and export show the
|
|
38
|
+
* figure as LaTeX would — picture and caption, live clause included; the
|
|
39
|
+
* tools never print. A figure with no `tools` slot has no button and no tray.
|
|
54
40
|
*/
|
|
41
|
+
import FloatCaption from './FloatCaption.astro';
|
|
42
|
+
import { courseDisclosure } from '../disclosure/course-disclosure';
|
|
55
43
|
|
|
56
44
|
export interface Props {
|
|
57
45
|
/** Stable key, e.g. `id="projectile-trajectory"`. Omit for a positional-only figure. */
|
|
58
46
|
id?: string;
|
|
59
|
-
/**
|
|
60
|
-
title?: string;
|
|
61
|
-
/** Full caption, shown below the figure (with its number) and in the hover-preview card. */
|
|
47
|
+
/** The long caption, shown below the figure with its number, and in the hover-preview card. */
|
|
62
48
|
caption: string;
|
|
63
|
-
/**
|
|
49
|
+
/** Optional short caption: heads the `[[fig-…]]` preview card; rule S1 measures it. */
|
|
50
|
+
short?: string;
|
|
51
|
+
/** `false` to keep the hover-preview card to the caption and number. */
|
|
64
52
|
previewable?: boolean;
|
|
65
|
-
/**
|
|
53
|
+
/** Whether the `tools` start open for a reader with nothing saved yet. Default: the course's `disclosure.figures`. */
|
|
66
54
|
defaultOpen?: boolean;
|
|
67
|
-
/** Persist the reader's open/
|
|
55
|
+
/** Persist the reader's open/folded choice for the `tools` across visits, per page. Default: `true`. */
|
|
68
56
|
rememberState?: boolean;
|
|
69
57
|
/** Computed by `remark-notation.mjs` — never author this directly. */
|
|
70
58
|
number?: string;
|
|
@@ -74,25 +62,36 @@ export interface Props {
|
|
|
74
62
|
|
|
75
63
|
const {
|
|
76
64
|
id,
|
|
77
|
-
title,
|
|
78
65
|
caption,
|
|
66
|
+
short,
|
|
79
67
|
previewable = true,
|
|
80
|
-
defaultOpen
|
|
68
|
+
defaultOpen,
|
|
81
69
|
rememberState = true,
|
|
82
70
|
number,
|
|
83
71
|
anchorId,
|
|
84
72
|
} = Astro.props;
|
|
85
73
|
|
|
74
|
+
if ('title' in Astro.props) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
`<Figure${id ? ` id="${id}"` : ''}> has no \`title\` prop: a figure is named by its \`caption\` (the long caption, shown below it with its number) and an optional \`short\` caption. Move a short title to \`short\`.`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
86
79
|
if (typeof caption !== 'string' || !caption.trim()) {
|
|
87
80
|
throw new Error('Figure requires a non-empty `caption` prop.');
|
|
88
81
|
}
|
|
82
|
+
if (short !== undefined && (typeof short !== 'string' || !short.trim())) {
|
|
83
|
+
throw new Error('Figure `short` must be a non-empty string when given.');
|
|
84
|
+
}
|
|
89
85
|
if (!number || !anchorId) {
|
|
90
86
|
throw new Error(
|
|
91
87
|
'Figure is missing its computed `number`/`anchorId` — this page must run remark-notation with a `figures` cross-page table configured (see astro.config.mjs).',
|
|
92
88
|
);
|
|
93
89
|
}
|
|
94
90
|
|
|
95
|
-
const
|
|
91
|
+
const hasTools = Astro.slots.has('tools');
|
|
92
|
+
const toolsId = `${anchorId}-tools`;
|
|
93
|
+
const toolsOpen = hasTools && (defaultOpen ?? (await courseDisclosure()).figures.defaultOpen);
|
|
94
|
+
const name = `Figure ${number}`;
|
|
96
95
|
---
|
|
97
96
|
|
|
98
97
|
<figure
|
|
@@ -100,46 +99,70 @@ const titleText = typeof title === 'string' && title.trim() ? title : caption;
|
|
|
100
99
|
id={anchorId}
|
|
101
100
|
data-fig-number={number}
|
|
102
101
|
data-fig-key={id}
|
|
102
|
+
data-fig-short={short}
|
|
103
103
|
data-fig-previewable={String(previewable)}
|
|
104
|
+
data-fig-tools={hasTools ? '' : undefined}
|
|
104
105
|
>
|
|
105
|
-
<div class="keyed-
|
|
106
|
-
<
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
106
|
+
<div class="keyed-figure__body">
|
|
107
|
+
<slot />
|
|
108
|
+
</div>
|
|
109
|
+
<FloatCaption
|
|
110
|
+
kind="Figure"
|
|
111
|
+
prefix="keyed-figure"
|
|
112
|
+
caption={caption}
|
|
113
|
+
short={short}
|
|
114
|
+
anchorId={anchorId}
|
|
115
|
+
number={number}
|
|
116
|
+
live
|
|
117
|
+
>
|
|
118
|
+
{
|
|
119
|
+
hasTools && (
|
|
120
|
+
<button
|
|
121
|
+
type="button"
|
|
122
|
+
class="float-tool keyed-figure__tools-toggle"
|
|
123
|
+
data-figure-tools-toggle
|
|
124
|
+
aria-controls={toolsId}
|
|
125
|
+
aria-expanded={String(toolsOpen)}
|
|
126
|
+
aria-label={`${toolsOpen ? 'Hide' : 'Show'} the controls of ${name}`}
|
|
127
|
+
title={toolsOpen ? 'Hide the controls' : 'Show the controls'}
|
|
122
128
|
>
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
></path></svg
|
|
129
|
+
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
130
|
+
<path class="float-tool__fold" d="M6 15l6-6 6 6" />
|
|
131
|
+
<path class="float-tool__unfold" d="M6 9l6 6 6-6" />
|
|
132
|
+
</svg>
|
|
133
|
+
</button>
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
</FloatCaption>
|
|
137
|
+
{
|
|
138
|
+
hasTools && (
|
|
139
|
+
<button
|
|
140
|
+
type="button"
|
|
141
|
+
class="keyed-figure__tools-stub"
|
|
142
|
+
data-figure-tools-stub
|
|
143
|
+
aria-controls={toolsId}
|
|
144
|
+
hidden={toolsOpen}
|
|
140
145
|
>
|
|
141
|
-
|
|
142
|
-
|
|
146
|
+
Hidden here: the controls
|
|
147
|
+
</button>
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
{
|
|
151
|
+
hasTools && (
|
|
152
|
+
<details
|
|
153
|
+
class="keyed-figure__tools-fold"
|
|
154
|
+
id={toolsId}
|
|
155
|
+
data-remember-state={String(rememberState)}
|
|
156
|
+
data-print-omit
|
|
157
|
+
open={toolsOpen}
|
|
158
|
+
>
|
|
159
|
+
<summary class="keyed-figure__tools-summary">Controls of {name}</summary>
|
|
160
|
+
<div class="keyed-figure__tools">
|
|
161
|
+
<slot name="tools" />
|
|
162
|
+
</div>
|
|
163
|
+
</details>
|
|
164
|
+
)
|
|
165
|
+
}
|
|
143
166
|
</figure>
|
|
144
167
|
|
|
145
168
|
<style>
|
|
@@ -148,226 +171,103 @@ const titleText = typeof title === 'string' && title.trim() ? title : caption;
|
|
|
148
171
|
padding: 0;
|
|
149
172
|
}
|
|
150
173
|
|
|
151
|
-
/*
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
language sitewide — overrides Starlight's own default `<details>`
|
|
159
|
-
accent rule (`markdown.css`, a rule down the left side) entirely. */
|
|
160
|
-
.keyed-figure__details {
|
|
161
|
-
border: 1px solid var(--sl-color-gray-5);
|
|
162
|
-
border-block-start: 4px solid var(--sl-color-accent);
|
|
163
|
-
border-radius: 0.2rem;
|
|
164
|
-
background: color-mix(in srgb, var(--sl-color-bg) 94%, var(--sl-color-accent) 6%);
|
|
165
|
-
box-shadow: 0 1rem 2.6rem rgba(0, 0, 0, 0.13);
|
|
166
|
-
padding: clamp(1rem, 3vw, 1.5rem);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
.keyed-figure__titlebar {
|
|
170
|
-
position: relative;
|
|
171
|
-
/* A custom property, not `font-size` itself: `font-size` is inherited
|
|
172
|
-
CSS, and `.keyed-figure__body` (the slotted figure content) is a
|
|
173
|
-
*descendant* of this wrapper too — setting `font-size` here directly
|
|
174
|
-
would inflate the figure's real content, not just the title. Read
|
|
175
|
-
this back explicitly on just the summary and the anchor icon below. */
|
|
176
|
-
--keyed-figure-title-size: var(--sl-text-h3);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
.keyed-figure__summary {
|
|
180
|
-
/* This is a title, not a caption — size it like the headings labs
|
|
181
|
-
already use internally (e.g. LaunchAngleExplorer's own <h3>), not
|
|
182
|
-
like the muted `.keyed-figure__caption-text` below. */
|
|
183
|
-
font-size: var(--keyed-figure-title-size);
|
|
184
|
-
line-height: var(--sl-line-height-headings, 1.2);
|
|
185
|
-
/* Room for the absolutely-positioned anchor icon so title text never
|
|
186
|
-
runs under it. */
|
|
187
|
-
padding-inline-end: 1.5em;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
.keyed-figure__title-text {
|
|
191
|
-
font-weight: 600;
|
|
192
|
-
color: var(--sl-color-white);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
.keyed-figure__title-anchor {
|
|
196
|
-
position: absolute;
|
|
197
|
-
top: 0.15em;
|
|
198
|
-
inset-inline-end: 0;
|
|
199
|
-
display: inline-flex;
|
|
200
|
-
align-items: center;
|
|
201
|
-
justify-content: center;
|
|
202
|
-
font-size: var(--keyed-figure-title-size);
|
|
203
|
-
width: 1em;
|
|
204
|
-
height: 1em;
|
|
205
|
-
color: var(--sl-color-gray-3);
|
|
206
|
-
text-decoration: none;
|
|
207
|
-
/* Prevent double/triple clicks from selecting the icon's a11y text. */
|
|
208
|
-
-webkit-user-select: none;
|
|
209
|
-
user-select: none;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
.keyed-figure__title-anchor svg {
|
|
213
|
-
width: 0.7em;
|
|
214
|
-
height: 0.7em;
|
|
174
|
+
/* A figure is book material: the plain tier (`surfaces.css`), no card —
|
|
175
|
+
no border, tint, shadow or top rule (docs/plans/titles-and-containers.md
|
|
176
|
+
Q8). A figure that should read as set apart goes in a `### … [card]`
|
|
177
|
+
section. The caption line is styled once for every float, in
|
|
178
|
+
`global.css` (`.float-caption`). */
|
|
179
|
+
.keyed-figure__body {
|
|
180
|
+
overflow-x: auto;
|
|
215
181
|
}
|
|
216
182
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
183
|
+
/* A lab's tools: a tray of their own after the caption line — a thin
|
|
184
|
+
border and the meta tier's radius, no tint, so it reads as controls
|
|
185
|
+
beside the figure rather than as part of the picture. */
|
|
186
|
+
.keyed-figure__tools-fold {
|
|
187
|
+
margin-block-start: 0.75rem;
|
|
188
|
+
padding: 0;
|
|
189
|
+
border: 0;
|
|
220
190
|
}
|
|
221
191
|
|
|
222
|
-
.keyed-
|
|
223
|
-
|
|
224
|
-
|
|
192
|
+
.keyed-figure__tools {
|
|
193
|
+
padding: 0.75rem 1rem;
|
|
194
|
+
border: 1px solid var(--sl-color-gray-5);
|
|
195
|
+
border-radius: var(--ex-radius-meta, 0.5rem);
|
|
225
196
|
}
|
|
226
197
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
(`anchor-links.css`). */
|
|
230
|
-
@media (hover: hover) {
|
|
231
|
-
.keyed-figure__title-anchor {
|
|
232
|
-
opacity: 0;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
.keyed-figure__title-anchor:focus-visible,
|
|
236
|
-
.keyed-figure__titlebar:hover .keyed-figure__title-anchor {
|
|
237
|
-
opacity: 1;
|
|
238
|
-
}
|
|
198
|
+
.keyed-figure__tools > :global(:first-child) {
|
|
199
|
+
margin-block-start: 0;
|
|
239
200
|
}
|
|
240
201
|
|
|
241
|
-
|
|
242
|
-
|
|
202
|
+
/* The native summary is for a reader without scripting; with it, the fold
|
|
203
|
+
button at the end of the caption line and the "Hidden here" line do the
|
|
204
|
+
work. */
|
|
205
|
+
.keyed-figure__tools-summary {
|
|
206
|
+
display: none;
|
|
243
207
|
}
|
|
244
208
|
|
|
245
|
-
.
|
|
246
|
-
|
|
209
|
+
/* Folded tools leave a line in the section stub's look (`global.css`
|
|
210
|
+
`.sl-section-stub`), a button that unfolds them. */
|
|
211
|
+
.keyed-figure__tools-stub {
|
|
212
|
+
display: block;
|
|
213
|
+
width: 100%;
|
|
214
|
+
margin: 0.3rem 0 0;
|
|
215
|
+
padding: 0 0 0.45rem;
|
|
216
|
+
border: 0;
|
|
217
|
+
border-block-end: 1px dashed var(--sl-color-gray-5);
|
|
218
|
+
background: none;
|
|
219
|
+
font: inherit;
|
|
247
220
|
font-size: var(--sl-text-sm);
|
|
221
|
+
line-height: 1.5;
|
|
248
222
|
color: var(--sl-color-gray-3);
|
|
223
|
+
text-align: start;
|
|
224
|
+
cursor: pointer;
|
|
249
225
|
}
|
|
250
226
|
|
|
251
|
-
.keyed-
|
|
252
|
-
|
|
253
|
-
font-variant-numeric: tabular-nums;
|
|
254
|
-
color: var(--sl-color-text-accent);
|
|
255
|
-
text-decoration: none;
|
|
227
|
+
.keyed-figure__tools-stub[hidden] {
|
|
228
|
+
display: none;
|
|
256
229
|
}
|
|
257
230
|
|
|
258
|
-
.keyed-
|
|
259
|
-
|
|
260
|
-
text-decoration: underline;
|
|
231
|
+
.keyed-figure__tools-stub:hover {
|
|
232
|
+
color: var(--sl-color-text-accent);
|
|
261
233
|
}
|
|
262
234
|
|
|
263
|
-
.keyed-
|
|
235
|
+
.keyed-figure__tools-stub:focus-visible {
|
|
264
236
|
outline: 2px solid var(--sl-color-accent);
|
|
265
|
-
outline-offset:
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
.keyed-figure__caption-text::before {
|
|
269
|
-
content: ' — ';
|
|
237
|
+
outline-offset: 2px;
|
|
270
238
|
}
|
|
271
239
|
|
|
272
|
-
@media
|
|
273
|
-
.keyed-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
.keyed-figure__title-anchor {
|
|
278
|
-
display: none;
|
|
240
|
+
@media (scripting: none) {
|
|
241
|
+
.keyed-figure__tools-summary {
|
|
242
|
+
display: list-item;
|
|
243
|
+
font-size: var(--sl-text-sm);
|
|
244
|
+
font-weight: 600;
|
|
279
245
|
}
|
|
280
246
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
.keyed-figure__summary::before {
|
|
247
|
+
.keyed-figure__tools-stub,
|
|
248
|
+
:global(.keyed-figure__tools-toggle) {
|
|
284
249
|
display: none !important;
|
|
285
250
|
}
|
|
251
|
+
}
|
|
286
252
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
.keyed-
|
|
292
|
-
|
|
253
|
+
/* Print and export show a lab as its LaTeX figure: the picture and the
|
|
254
|
+
caption, live clause included. The tools never print
|
|
255
|
+
(`../disclosure/print-as-shown.ts` leaves their `<details>` alone). */
|
|
256
|
+
@media print {
|
|
257
|
+
.keyed-figure__tools-fold,
|
|
258
|
+
.keyed-figure__tools-stub {
|
|
259
|
+
display: none !important;
|
|
293
260
|
}
|
|
294
261
|
}
|
|
295
262
|
</style>
|
|
296
263
|
|
|
297
264
|
<script>
|
|
298
|
-
import {
|
|
299
|
-
import { attachHoverPeek, closeHoverPeek } from '../disclosure/hover-preview';
|
|
265
|
+
import { wireFigureTools } from './figure-tools';
|
|
300
266
|
|
|
301
267
|
document
|
|
302
|
-
.querySelectorAll<
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
details.dataset.figureWired = 'true';
|
|
307
|
-
const figure = details.closest<HTMLElement>('.keyed-figure');
|
|
308
|
-
if (!figure || !figure.id) return;
|
|
309
|
-
|
|
310
|
-
// The SSR-rendered `open` attribute already carries the author's
|
|
311
|
-
// `defaultOpen` prop verbatim — read it before anything below mutates
|
|
312
|
-
// `.open`, rather than threading the prop value through a second data
|
|
313
|
-
// attribute.
|
|
314
|
-
const defaultOpen = details.open;
|
|
315
|
-
if (details.dataset.rememberState === 'true') {
|
|
316
|
-
wirePersistedDetails(details, `figure:${figure.id}`, defaultOpen);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
const summary = details.querySelector<HTMLElement>(
|
|
320
|
-
':scope > summary',
|
|
321
|
-
);
|
|
322
|
-
|
|
323
|
-
// Deep-linking to this figure (from a `[[fig-key]]` jump or a shared
|
|
324
|
-
// URL) must reveal it even if a reader had left it collapsed — the
|
|
325
|
-
// figure's id lives on the always-visible <figure>, not on the
|
|
326
|
-
// <details> itself, so the browser's native :target reveal doesn't
|
|
327
|
-
// apply here and this has to be done by hand.
|
|
328
|
-
const openIfTargeted = () => {
|
|
329
|
-
if (location.hash === `#${figure.id}`) details.open = true;
|
|
330
|
-
};
|
|
331
|
-
openIfTargeted();
|
|
332
|
-
window.addEventListener('hashchange', openIfTargeted);
|
|
333
|
-
|
|
334
|
-
if (!summary) return;
|
|
335
|
-
|
|
336
|
-
// The peek is only useful while collapsed — once expanded, the real
|
|
337
|
-
// content is already on screen right below the title bar, and duping
|
|
338
|
-
// it into a floating box would just be noise. `attachHoverPeek` itself
|
|
339
|
-
// doesn't know about "collapsed", so gate it here: a capture-order
|
|
340
|
-
// listener added *before* the wiring call runs first on the same
|
|
341
|
-
// element and can suppress the event before `attachHoverPeek`'s own
|
|
342
|
-
// handler sees it.
|
|
343
|
-
const suppressWhenOpen = (event: Event) => {
|
|
344
|
-
if (details.open) event.stopImmediatePropagation();
|
|
345
|
-
};
|
|
346
|
-
summary.addEventListener('pointerenter', suppressWhenOpen);
|
|
347
|
-
summary.addEventListener('focus', suppressWhenOpen);
|
|
348
|
-
|
|
349
|
-
// Expanding while the peek is still showing (hover, then click to
|
|
350
|
-
// open without the pointer ever leaving) would otherwise leave a now-
|
|
351
|
-
// redundant preview floating over the real content that just
|
|
352
|
-
// appeared underneath it.
|
|
353
|
-
details.addEventListener('toggle', () => {
|
|
354
|
-
if (details.open) closeHoverPeek(summary);
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
// The caption now lives in the <figcaption> after <details>, not
|
|
358
|
-
// inside the summary — query it from the figure, not the summary.
|
|
359
|
-
const captionText =
|
|
360
|
-
figure.querySelector('.keyed-figure__caption-text')?.textContent ??
|
|
361
|
-
'';
|
|
362
|
-
attachHoverPeek(summary, {
|
|
363
|
-
label: `Figure ${figure.dataset.figNumber ?? ''} preview`,
|
|
364
|
-
getContent: () => {
|
|
365
|
-
const wrap = document.createElement('div');
|
|
366
|
-
const strong = document.createElement('strong');
|
|
367
|
-
strong.textContent = `Figure ${figure.dataset.figNumber ?? ''}`;
|
|
368
|
-
wrap.append(strong, document.createTextNode(` — ${captionText}`));
|
|
369
|
-
return wrap;
|
|
370
|
-
},
|
|
371
|
-
});
|
|
268
|
+
.querySelectorAll<HTMLElement>('.keyed-figure[data-fig-tools]:not([data-fig-tools-wired])')
|
|
269
|
+
.forEach((figure) => {
|
|
270
|
+
figure.dataset.figToolsWired = 'true';
|
|
271
|
+
wireFigureTools(figure);
|
|
372
272
|
});
|
|
373
273
|
</script>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* The caption line of a `<Figure>` or a `<Diagram>`
|
|
4
|
+
* (docs/plans/figures-and-short-captions.md, decisions 1 and 4). One
|
|
5
|
+
* anatomy, below the picture, the LaTeX figure convention:
|
|
6
|
+
*
|
|
7
|
+
* Figure 2.2.1 [chain] **‹short›.** ‹caption› ‹live end› [buttons]
|
|
8
|
+
*
|
|
9
|
+
* The number links to the float, the chain icon copies that link
|
|
10
|
+
* (`HeadingAnchorEnhancer.astro` reads `data-copy-link`) and then navigates
|
|
11
|
+
* the same way, and the caption is the float's one long caption. A float
|
|
12
|
+
* with a short caption opens the caption with it as a bold title,
|
|
13
|
+
* `b.float-caption__title` (decision 9.1), outside `.float-caption__text`, so
|
|
14
|
+
* a reader of the long caption (a preview card, a fold's bar) still reads
|
|
15
|
+
* it alone. The line
|
|
16
|
+
* closes the figure and is never the thing that hides: a figure is always
|
|
17
|
+
* its picture and then this line. To fold a float away completely, wrap it
|
|
18
|
+
* in a `<Fold>` or put it in a collapsible section.
|
|
19
|
+
*
|
|
20
|
+
* A host's buttons (a diagram's widen ↔, the fold of a lab's tools) go in
|
|
21
|
+
* the default slot, rendered inside a `.float-tools` span at the end of the
|
|
22
|
+
* line — the boxed row a table's caption has (`global.css`). A float with no
|
|
23
|
+
* buttons has no row, even when the host passes an empty slot.
|
|
24
|
+
*
|
|
25
|
+
* `live` adds the line's live end: an empty
|
|
26
|
+
* `span.float-caption__state[data-figure-state]` after the caption, outside
|
|
27
|
+
* `.float-caption__text`, where a lab states the case its plot shows
|
|
28
|
+
* (`./figure-state.ts`).
|
|
29
|
+
*/
|
|
30
|
+
import { captionTitle } from '../float-name';
|
|
31
|
+
|
|
32
|
+
export interface Props {
|
|
33
|
+
/** The reader-facing kind word: `Figure`, `Diagram`. */
|
|
34
|
+
kind: string;
|
|
35
|
+
/** Class prefix of the host component, e.g. `keyed-figure` → `keyed-figure__number`. */
|
|
36
|
+
prefix: string;
|
|
37
|
+
/** The float's long caption. */
|
|
38
|
+
caption: string;
|
|
39
|
+
/** Its short caption, shown as the line's bold title. */
|
|
40
|
+
short?: string;
|
|
41
|
+
/** Its anchor id (`fig-…`, `dia-…`). */
|
|
42
|
+
anchorId: string;
|
|
43
|
+
/** Its number; an unnumbered float shows none. */
|
|
44
|
+
number?: string;
|
|
45
|
+
/** An id for the caption text, for a control inside the body to be named by. */
|
|
46
|
+
textId?: string;
|
|
47
|
+
/** Give the line a live end a lab writes into (`./figure-state.ts`). */
|
|
48
|
+
live?: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const { kind, prefix, caption, short, anchorId, number, textId, live = false } = Astro.props;
|
|
52
|
+
const name = number ? `${kind} ${number}` : kind.toLowerCase();
|
|
53
|
+
const tools = Astro.slots.has('default') ? (await Astro.slots.render('default')).trim() : '';
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
<figcaption class:list={['float-caption', `${prefix}__caption`]} data-float-name={name}>
|
|
57
|
+
{
|
|
58
|
+
number && (
|
|
59
|
+
<a
|
|
60
|
+
class:list={['float-caption__number', `${prefix}__number`]}
|
|
61
|
+
href={`#${anchorId}`}
|
|
62
|
+
aria-label={name}
|
|
63
|
+
>
|
|
64
|
+
{name}
|
|
65
|
+
</a>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
<a
|
|
69
|
+
class="float-caption__anchor"
|
|
70
|
+
href={`#${anchorId}`}
|
|
71
|
+
aria-label={`Copy the link to ${name}`}
|
|
72
|
+
title="Copy link"
|
|
73
|
+
data-copy-link={name}
|
|
74
|
+
>
|
|
75
|
+
<svg aria-hidden="true" width="16" height="16" viewBox="0 0 24 24" fill="currentColor"
|
|
76
|
+
><path
|
|
77
|
+
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"
|
|
78
|
+
></path></svg
|
|
79
|
+
>
|
|
80
|
+
</a>
|
|
81
|
+
{short && <b class="float-caption__title">{captionTitle(short)}</b>}{short && ' '}<span
|
|
82
|
+
class:list={['float-caption__text', `${prefix}__caption-text`]}
|
|
83
|
+
id={textId}
|
|
84
|
+
>{caption}</span
|
|
85
|
+
>{live && <span class="float-caption__state" data-figure-state />}
|
|
86
|
+
{tools && <span class="float-tools" set:html={tools} />}
|
|
87
|
+
</figcaption>
|