explico 0.7.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 +1409 -1
- package/README.md +60 -15
- package/dist/compiler/astro.d.ts +6 -1
- package/dist/compiler/cli.d.ts +54 -3
- package/dist/compiler/cli.js +80 -6
- package/dist/compiler/collections.d.ts +35 -2
- package/dist/compiler/collections.js +84 -8
- 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 +14 -1
- package/dist/compiler/logging.js +6 -2
- 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 +21 -11
- package/dist/reference/delimiter-size.js +15 -39
- 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 +69 -34
- package/dist/reference/equations.mjs +90 -44
- 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 +3 -2
- 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 +47 -23
- 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 +44 -6
- package/dist/reference/gate-math.js +63 -10
- 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 +32 -11
- package/dist/reference/gate-prose-notation.js +30 -8
- package/dist/reference/katex-delimiters.d.mts +15 -0
- package/dist/reference/katex-delimiters.mjs +138 -0
- package/dist/reference/katex-options.mjs +4 -0
- 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 +588 -39
- 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 +5 -5
- 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 +24 -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 +1324 -2
- package/docs/authoring/notation.md +616 -344
- 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/docs/katex-bracket-scaling.md +191 -0
- package/package.json +3 -2
- package/src/compiler/cli.ts +139 -6
- package/src/compiler/collections.ts +144 -10
- 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 +21 -4
- 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 +24 -14
- package/src/components/notation/NotationLayer.astro +169 -162
- package/src/components/notation/NotationMath.astro +10 -1
- package/src/components/notation/render-notation-math.ts +111 -10
- package/src/components/reference/FloatRefLayer.astro +485 -423
- package/src/components/starlight/AboutPanel.astro +22 -6
- 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 +40 -61
- package/src/reference/diagrams-validate.ts +17 -1
- package/src/reference/equations-validate.ts +10 -5
- package/src/reference/equations.mjs +90 -44
- 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 +3 -2
- package/src/reference/gate-course-rules.ts +134 -0
- package/src/reference/gate-heading-length.ts +68 -37
- package/src/reference/gate-heading-structure.ts +193 -0
- package/src/reference/gate-math-punctuation.ts +381 -0
- package/src/reference/gate-math.ts +119 -12
- package/src/reference/gate-part-pages.ts +217 -0
- package/src/reference/gate-prose-notation.ts +74 -13
- package/src/reference/katex-delimiters.mjs +138 -0
- package/src/reference/katex-options.mjs +4 -0
- 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 +588 -39
- 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 +17 -32
- 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 +24 -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
|
@@ -12,6 +12,15 @@
|
|
|
12
12
|
* It is a plain `<details>` disclosure: the browser gives keyboard toggle and
|
|
13
13
|
* `aria-expanded` for free; the inline script only adds Escape-to-close and
|
|
14
14
|
* outside-click-to-close.
|
|
15
|
+
*
|
|
16
|
+
* The panel also *publishes* the identity it displays: the `data-knowledge-*`
|
|
17
|
+
* and `data-engine-version` attributes carry the same values as the rendered
|
|
18
|
+
* text. A build check that wants to confirm the shipped page really carries the
|
|
19
|
+
* configured version would otherwise have to match the panel's prose and its
|
|
20
|
+
* `version · date` join, which pins a course's test to one version string and
|
|
21
|
+
* makes the check fail on every bump; a bare version substring is not even
|
|
22
|
+
* scoped to the panel, so the engine's own version elsewhere on the page can
|
|
23
|
+
* satisfy it. These attributes are exact, scoped, and stable across a bump.
|
|
15
24
|
*/
|
|
16
25
|
import { loadManifest } from '../../compiler/manifest';
|
|
17
26
|
|
|
@@ -32,7 +41,13 @@ const knowledgeLine = knowledge.date
|
|
|
32
41
|
: knowledge.version;
|
|
33
42
|
---
|
|
34
43
|
|
|
35
|
-
<details
|
|
44
|
+
<details
|
|
45
|
+
class="about"
|
|
46
|
+
data-about-panel
|
|
47
|
+
data-knowledge-version={knowledge.version}
|
|
48
|
+
data-knowledge-date={knowledge.date}
|
|
49
|
+
data-engine-version={engine.version}
|
|
50
|
+
>
|
|
36
51
|
<summary
|
|
37
52
|
class="about-toggle"
|
|
38
53
|
aria-label={`About ${material.name}`}
|
|
@@ -47,7 +62,7 @@ const knowledgeLine = knowledge.date
|
|
|
47
62
|
<dl class="about-facts">
|
|
48
63
|
<div>
|
|
49
64
|
<dt>Knowledge version</dt>
|
|
50
|
-
<dd>{knowledgeLine}</dd>
|
|
65
|
+
<dd data-knowledge-line>{knowledgeLine}</dd>
|
|
51
66
|
</div>
|
|
52
67
|
<div>
|
|
53
68
|
<dt>Engine</dt>
|
|
@@ -164,11 +179,12 @@ const knowledgeLine = knowledge.date
|
|
|
164
179
|
max-height: min(70vh, 34rem);
|
|
165
180
|
overflow-y: auto;
|
|
166
181
|
padding: 1rem;
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
border
|
|
182
|
+
/* Course logistics: the meta tier's surface, border and radius
|
|
183
|
+
(`surfaces.css`), plus the overlay shadow every floating card has. */
|
|
184
|
+
border: var(--ex-edge-border);
|
|
185
|
+
border-radius: var(--ex-radius-meta);
|
|
170
186
|
color: var(--sl-color-text);
|
|
171
|
-
background: var(--
|
|
187
|
+
background: var(--ex-surface-tint-neutral);
|
|
172
188
|
box-shadow: var(--sl-shadow-lg);
|
|
173
189
|
font-size: var(--sl-text-xs);
|
|
174
190
|
line-height: 1.5;
|
|
@@ -32,10 +32,15 @@
|
|
|
32
32
|
* together, matching how `rehype-heading-anchors` emits them) into
|
|
33
33
|
* `<summary>` breaks axe's `no-focusable-content` check — a `<summary>` must
|
|
34
34
|
* have no focusable descendants, full stop, no matter how its click handling
|
|
35
|
-
* is patched up.
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
35
|
+
* is patched up. So the anchor stays a sibling of `<summary>` rather than a
|
|
36
|
+
* child — the same reason a float's caption line keeps its links outside any
|
|
37
|
+
* `<summary>` — reusing the existing `.sl-heading-wrapper` div as that
|
|
38
|
+
* sibling-holding container.
|
|
39
|
+
*
|
|
40
|
+
* A `<Memo>` and a `<Fold>` are folded here too: each bar is an `<h6>` in a
|
|
41
|
+
* heading wrapper of its own (`Memo.astro`, `Fold.astro`), with its default
|
|
42
|
+
* and its `rememberState` on the wrapper (`data-default-open`,
|
|
43
|
+
* `data-remember-state="false"` to keep the default on every visit).
|
|
39
44
|
*
|
|
40
45
|
* Headings are processed deepest-rank first (h6 before h2, etc.) so that by
|
|
41
46
|
* the time a shallower heading's `sectionParts()` walk runs, any nested
|
|
@@ -69,6 +74,34 @@
|
|
|
69
74
|
* feel; the peek here needs to be the real, in-place content, sized and
|
|
70
75
|
* positioned relative to its own section, animatable into a real expand.
|
|
71
76
|
*
|
|
77
|
+
* **A closed section must look closed on every screen.** The chevron alone
|
|
78
|
+
* isn't enough: on wide screens it sits in the gutter left of the title, and
|
|
79
|
+
* on a phone there is no gutter — it used to render off-screen, leaving a
|
|
80
|
+
* collapsed section indistinguishable from a plain heading. So `<summary>`
|
|
81
|
+
* also carries two `aria-hidden` spans after the heading:
|
|
82
|
+
* `.sl-section-hint` — "(tap to expand)" / "(click to expand)", narrow or
|
|
83
|
+
* touch screens only, where `global.css` also moves the chevron into the
|
|
84
|
+
* title line — and `.sl-section-stub`, a "Hidden here: 2 paragraphs, 1
|
|
85
|
+
* equation" line (`./section-summary.ts`) on every screen. Both show only
|
|
86
|
+
* while the section isn't `open`, and both are part of the tap target. The
|
|
87
|
+
* heading brings its outline number along (`data-sec-number`, printed by
|
|
88
|
+
* `global.css`): a narrow screen hides it on an open section but shows it on a
|
|
89
|
+
* closed one, which is where it says what is folded away.
|
|
90
|
+
*
|
|
91
|
+
* **Print follows the screen** (`../disclosure/print-as-shown.ts`): an open
|
|
92
|
+
* section prints in full, a closed one as its title plus a "(content
|
|
93
|
+
* collapsed — full text: …)" note with the section's permalink.
|
|
94
|
+
*
|
|
95
|
+
* **Carded sections** (docs/plans/titles-and-containers.md Part 2): a
|
|
96
|
+
* wrapper with `data-card="accent"|"meta"` — a `[card]` / `[card=meta]`
|
|
97
|
+
* heading marker, or a knowledge check (`AssessmentSet.astro`) — has its whole
|
|
98
|
+
* section, heading and body, moved into one `div.ex-section-card` carrying
|
|
99
|
+
* that tier's class from `surfaces.css`. Same boundary, same deepest-first
|
|
100
|
+
* order: a card nested in a section reads as one sibling holding its heading,
|
|
101
|
+
* so the parent's walk still sees it. A section that is both collapsible and
|
|
102
|
+
* carded is collapsed first, then its titlebar — which by then holds the
|
|
103
|
+
* whole section — goes into the card.
|
|
104
|
+
*
|
|
72
105
|
* Wired only on lesson pages (see `LessonFooter.astro`), same gating as
|
|
73
106
|
* `HeadingAnchorEnhancer.astro`/`KeyedFloatEnhancer.astro` — and placed
|
|
74
107
|
* *before* `HeadingAnchorEnhancer` in that file so this component's own
|
|
@@ -80,10 +113,14 @@
|
|
|
80
113
|
|
|
81
114
|
<script>
|
|
82
115
|
import { HEADINGS, sectionParts } from './section-parts';
|
|
116
|
+
import { collectHiddenContent, hiddenContentParts } from './section-summary';
|
|
83
117
|
import {
|
|
84
118
|
wirePersistedDetails,
|
|
85
119
|
savePersistedOpenState,
|
|
86
120
|
} from '../disclosure/persisted-disclosure';
|
|
121
|
+
import { installPrintAsShownFallback } from '../disclosure/print-as-shown';
|
|
122
|
+
import { CARD_CONSTANTS } from '../card-manager';
|
|
123
|
+
import { startTrip, type Trip } from '../card-travel';
|
|
87
124
|
|
|
88
125
|
(() => {
|
|
89
126
|
const root = document.querySelector('.sl-markdown-content');
|
|
@@ -91,6 +128,20 @@
|
|
|
91
128
|
|
|
92
129
|
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
|
|
93
130
|
|
|
131
|
+
// Every section peek the pointer is travelling from, fed by one listener.
|
|
132
|
+
const peekTrips = new Set<Trip>();
|
|
133
|
+
document.addEventListener(
|
|
134
|
+
'pointermove',
|
|
135
|
+
(event) => {
|
|
136
|
+
if (event.pointerType === 'touch') return;
|
|
137
|
+
for (const trip of [...peekTrips]) {
|
|
138
|
+
trip.move({ x: event.clientX, y: event.clientY });
|
|
139
|
+
if (!trip.active) peekTrips.delete(trip);
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
{ passive: true },
|
|
143
|
+
);
|
|
144
|
+
|
|
94
145
|
type State = 'closed' | 'peeking' | 'open';
|
|
95
146
|
|
|
96
147
|
// A collapsed section's peek caps at 10em, measured in the body's own
|
|
@@ -176,20 +227,40 @@
|
|
|
176
227
|
});
|
|
177
228
|
};
|
|
178
229
|
|
|
179
|
-
|
|
230
|
+
const CARD_TIERS = new Set(['accent', 'meta']);
|
|
231
|
+
|
|
232
|
+
const cardSection = (heading: HTMLElement, tier: string) => {
|
|
233
|
+
const parts = sectionParts(heading);
|
|
234
|
+
const card = document.createElement('div');
|
|
235
|
+
card.className = `ex-section-card ex-tier-${tier}`;
|
|
236
|
+
parts[0].before(card);
|
|
237
|
+
card.append(...parts);
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const sectionHeadings = Array.from(
|
|
180
241
|
root.querySelectorAll<HTMLElement>(HEADINGS),
|
|
181
242
|
)
|
|
182
|
-
.filter(
|
|
183
|
-
(heading)
|
|
243
|
+
.filter((heading) => {
|
|
244
|
+
const wrapper = heading.closest<HTMLElement>('.sl-heading-wrapper');
|
|
245
|
+
return (
|
|
184
246
|
!!heading.id &&
|
|
185
|
-
|
|
186
|
-
.
|
|
187
|
-
|
|
247
|
+
(wrapper?.dataset.collapsible === 'true' ||
|
|
248
|
+
CARD_TIERS.has(wrapper?.dataset.card ?? ''))
|
|
249
|
+
);
|
|
250
|
+
})
|
|
188
251
|
// Deepest rank first — see the module comment above.
|
|
189
|
-
.sort((a, b) => Number(b.tagName[1]) - Number(a.tagName[1]))
|
|
252
|
+
.sort((a, b) => Number(b.tagName[1]) - Number(a.tagName[1]));
|
|
253
|
+
|
|
254
|
+
for (const heading of sectionHeadings) {
|
|
255
|
+
const wrapper = heading.closest<HTMLElement>('.sl-heading-wrapper')!;
|
|
256
|
+
if (wrapper.dataset.collapsible === 'true') makeCollapsible(heading, wrapper);
|
|
257
|
+
const tier = wrapper.dataset.card ?? '';
|
|
258
|
+
if (CARD_TIERS.has(tier)) cardSection(heading, tier);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function makeCollapsible(heading: HTMLElement, wrapper: HTMLElement) {
|
|
190
262
|
const parts = sectionParts(heading);
|
|
191
263
|
const bodyParts = parts.slice(1);
|
|
192
|
-
const wrapper = heading.closest<HTMLElement>('.sl-heading-wrapper')!;
|
|
193
264
|
|
|
194
265
|
const details = document.createElement('details');
|
|
195
266
|
details.className = 'sl-section-collapsible';
|
|
@@ -208,6 +279,43 @@
|
|
|
208
279
|
':scope > .sl-anchor-link',
|
|
209
280
|
);
|
|
210
281
|
summary.append(heading);
|
|
282
|
+
|
|
283
|
+
// What a *closed* section shows beyond its title (styles in
|
|
284
|
+
// `global.css`; see the module comment): a "(tap to expand)" hint on
|
|
285
|
+
// narrow/touch screens and a "Hidden here: …" line on every screen.
|
|
286
|
+
// Both are `aria-hidden` — the native `<details>` already announces
|
|
287
|
+
// "collapsed", and the heading alone stays the summary's name.
|
|
288
|
+
const hint = document.createElement('span');
|
|
289
|
+
hint.className = 'sl-section-hint';
|
|
290
|
+
hint.setAttribute('aria-hidden', 'true');
|
|
291
|
+
summary.append(hint);
|
|
292
|
+
// A `<Fold>` whose bar names its floats (`data-bar-names-floats`)
|
|
293
|
+
// counts them here rather than naming them twice.
|
|
294
|
+
const { counts, named } = collectHiddenContent(body, HEADINGS, {
|
|
295
|
+
nameFloats: wrapper.dataset.barNamesFloats === undefined,
|
|
296
|
+
});
|
|
297
|
+
const hiddenParts = hiddenContentParts(counts, named);
|
|
298
|
+
if (hiddenParts) {
|
|
299
|
+
// Outer span collapses as a grid row; the inner one clips the text.
|
|
300
|
+
const stub = document.createElement('span');
|
|
301
|
+
stub.className = 'sl-section-stub';
|
|
302
|
+
stub.setAttribute('aria-hidden', 'true');
|
|
303
|
+
const stubText = document.createElement('span');
|
|
304
|
+
stubText.append('Hidden here: ');
|
|
305
|
+
hiddenParts.forEach((part, index) => {
|
|
306
|
+
if (index > 0) stubText.append(', ');
|
|
307
|
+
if (typeof part === 'string') {
|
|
308
|
+
stubText.append(part);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
const number = document.createElement('b');
|
|
312
|
+
number.textContent = part.number;
|
|
313
|
+
stubText.append(number, ` ${part.short}`);
|
|
314
|
+
});
|
|
315
|
+
stub.append(stubText);
|
|
316
|
+
summary.append(stub);
|
|
317
|
+
}
|
|
318
|
+
|
|
211
319
|
details.append(summary, body);
|
|
212
320
|
|
|
213
321
|
// The anchor link stays put as `details`'s sibling inside `wrapper`
|
|
@@ -223,10 +331,17 @@
|
|
|
223
331
|
// mis-computing a nested `<table>`'s intrinsic (auto-layout) width
|
|
224
332
|
// when a `<details>` is built pre-opened and inserted already-open,
|
|
225
333
|
// versus being opened only after attachment.
|
|
334
|
+
// `data-remember-state="false"` (a `<Memo rememberState={false}>`)
|
|
335
|
+
// keeps the default on every visit; everything else remembers.
|
|
226
336
|
const defaultOpen = wrapper.dataset.defaultOpen !== 'false';
|
|
227
|
-
|
|
228
|
-
details.dataset.state = (details.open ? 'open' : 'closed') satisfies State;
|
|
337
|
+
const remember = wrapper.dataset.rememberState !== 'false';
|
|
229
338
|
const persistKey = `section:${heading.id}`;
|
|
339
|
+
if (remember) wirePersistedDetails(details, persistKey, defaultOpen);
|
|
340
|
+
else details.open = defaultOpen;
|
|
341
|
+
details.dataset.state = (details.open ? 'open' : 'closed') satisfies State;
|
|
342
|
+
const save = (open: boolean) => {
|
|
343
|
+
if (remember) savePersistedOpenState(persistKey, open);
|
|
344
|
+
};
|
|
230
345
|
|
|
231
346
|
// A real (non-peek) commit: the only place a choice gets persisted —
|
|
232
347
|
// via `savePersistedOpenState`, called *synchronously* with the
|
|
@@ -247,7 +362,7 @@
|
|
|
247
362
|
const goingOpen = next === 'open';
|
|
248
363
|
if (goingOpen) details.open = true; // render before animating/measuring
|
|
249
364
|
details.dataset.state = next satisfies State;
|
|
250
|
-
|
|
365
|
+
save(goingOpen);
|
|
251
366
|
growTo(body, goingOpen ? body.scrollHeight : 0, () => {
|
|
252
367
|
details.open = goingOpen;
|
|
253
368
|
if (goingOpen) body.style.maxHeight = '';
|
|
@@ -269,7 +384,7 @@
|
|
|
269
384
|
details.dataset.state = 'open' satisfies State; // …then re-enter flow at that same height
|
|
270
385
|
void body.offsetHeight; // reflow again before animating the grow
|
|
271
386
|
body.style.transition = '';
|
|
272
|
-
|
|
387
|
+
save(true);
|
|
273
388
|
growTo(body, body.scrollHeight, () => {
|
|
274
389
|
details.open = true;
|
|
275
390
|
body.style.maxHeight = '';
|
|
@@ -291,33 +406,60 @@
|
|
|
291
406
|
});
|
|
292
407
|
};
|
|
293
408
|
|
|
294
|
-
//
|
|
295
|
-
//
|
|
296
|
-
//
|
|
297
|
-
//
|
|
298
|
-
//
|
|
299
|
-
//
|
|
300
|
-
//
|
|
301
|
-
//
|
|
302
|
-
//
|
|
303
|
-
//
|
|
304
|
-
//
|
|
305
|
-
//
|
|
306
|
-
|
|
409
|
+
// The peek closes when the reader leaves both the title and the peeked
|
|
410
|
+
// body — not the moment the pointer steps off them, but on the
|
|
411
|
+
// explanation cards' terms (rules C2 and C12 of
|
|
412
|
+
// docs/plans/hover-card-consistency.md, `../card-travel.ts`): heading
|
|
413
|
+
// back toward the body keeps it, anything else closes it after the
|
|
414
|
+
// cards' grace. `:focus-visible`, not `:focus-within` — the same
|
|
415
|
+
// reasoning as `hover-preview.ts`: clicking `summary` to commit a peek
|
|
416
|
+
// (or to collapse an open section) leaves it focused as an ordinary
|
|
417
|
+
// side effect of the click, and that focus lingers (`:focus-within`
|
|
418
|
+
// stays true) long after the pointer has moved elsewhere — which, before
|
|
419
|
+
// this used `:focus-visible` instead, meant a peek entered right after
|
|
420
|
+
// such a click could never close on mouse-leave at all.
|
|
421
|
+
// `:focus-visible` is only true for keyboard/keyboard-equivalent focus,
|
|
422
|
+
// so a mouse click doesn't count, but a reader who actually tabbed here
|
|
423
|
+
// still keeps their peek open.
|
|
424
|
+
let graceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
425
|
+
let ownTrip: Trip | null = null;
|
|
426
|
+
const exitUnlessWanted = () => {
|
|
427
|
+
if (!details.matches(':hover') && !summary.matches(':focus-visible')) {
|
|
428
|
+
exitPeek();
|
|
429
|
+
}
|
|
430
|
+
};
|
|
307
431
|
const cancelLeave = () => {
|
|
308
|
-
if (
|
|
309
|
-
|
|
310
|
-
|
|
432
|
+
if (ownTrip) {
|
|
433
|
+
ownTrip.end();
|
|
434
|
+
peekTrips.delete(ownTrip);
|
|
435
|
+
ownTrip = null;
|
|
311
436
|
}
|
|
437
|
+
if (graceTimer !== null) clearTimeout(graceTimer);
|
|
438
|
+
graceTimer = null;
|
|
312
439
|
};
|
|
313
440
|
const scheduleLeave = () => {
|
|
314
441
|
cancelLeave();
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
442
|
+
graceTimer = setTimeout(() => {
|
|
443
|
+
graceTimer = null;
|
|
444
|
+
exitUnlessWanted();
|
|
445
|
+
}, CARD_CONSTANTS.travelGraceMs);
|
|
446
|
+
};
|
|
447
|
+
const travelFrom = (apex: { x: number; y: number }) => {
|
|
448
|
+
cancelLeave();
|
|
449
|
+
const trip = startTrip(apex, {
|
|
450
|
+
graceMs: CARD_CONSTANTS.travelGraceMs,
|
|
451
|
+
restMs: CARD_CONSTANTS.travelRestMs,
|
|
452
|
+
tripMaxMs: CARD_CONSTANTS.travelTripMaxMs,
|
|
453
|
+
cardBox: () => body.getBoundingClientRect(),
|
|
454
|
+
triggerBox: () => summary.getBoundingClientRect(),
|
|
455
|
+
onExit: () => {
|
|
456
|
+
if (ownTrip === trip) cancelLeave();
|
|
457
|
+
exitUnlessWanted();
|
|
458
|
+
},
|
|
320
459
|
});
|
|
460
|
+
if (!trip.active) return;
|
|
461
|
+
ownTrip = trip;
|
|
462
|
+
peekTrips.add(trip);
|
|
321
463
|
};
|
|
322
464
|
|
|
323
465
|
summary.addEventListener('pointerenter', (event) => {
|
|
@@ -326,9 +468,16 @@
|
|
|
326
468
|
enterPeek();
|
|
327
469
|
});
|
|
328
470
|
summary.addEventListener('focus', enterPeek);
|
|
471
|
+
details.addEventListener('pointerenter', (event) => {
|
|
472
|
+
if (event.pointerType !== 'touch') cancelLeave();
|
|
473
|
+
});
|
|
329
474
|
details.addEventListener('pointerleave', (event) => {
|
|
330
475
|
if (event.pointerType === 'touch') return;
|
|
331
|
-
|
|
476
|
+
if (details.dataset.state === 'peeking') {
|
|
477
|
+
travelFrom({ x: event.clientX, y: event.clientY });
|
|
478
|
+
} else {
|
|
479
|
+
scheduleLeave();
|
|
480
|
+
}
|
|
332
481
|
});
|
|
333
482
|
details.addEventListener('focusout', scheduleLeave);
|
|
334
483
|
|
|
@@ -342,6 +491,20 @@
|
|
|
342
491
|
else if (details.dataset.state === 'open') commitOpenOrClosed('closed');
|
|
343
492
|
else commitOpenOrClosed('open');
|
|
344
493
|
});
|
|
494
|
+
|
|
495
|
+
// The browser opens a closed `<details>` by itself to show a match of
|
|
496
|
+
// find-in-page or a fragment inside it. Every open this state machine
|
|
497
|
+
// makes sets `data-state` in the same task, so an open that finds it
|
|
498
|
+
// still `closed` is the browser's: follow it, as a real expand.
|
|
499
|
+
details.addEventListener('toggle', () => {
|
|
500
|
+
if (!details.open || details.dataset.state !== 'closed') return;
|
|
501
|
+
if (details.hasAttribute('data-explico-temporary-open')) return;
|
|
502
|
+
supersede.get(body)?.();
|
|
503
|
+
supersede.delete(body);
|
|
504
|
+
details.dataset.state = 'open' satisfies State;
|
|
505
|
+
body.style.removeProperty('max-height');
|
|
506
|
+
save(true);
|
|
507
|
+
});
|
|
345
508
|
}
|
|
346
509
|
|
|
347
510
|
// A closed native `<details>` doesn't just hide its non-`<summary>`
|
|
@@ -402,60 +565,11 @@
|
|
|
402
565
|
expandAncestors(currentHashId(), false);
|
|
403
566
|
});
|
|
404
567
|
|
|
405
|
-
// Print
|
|
406
|
-
//
|
|
407
|
-
//
|
|
408
|
-
//
|
|
409
|
-
//
|
|
410
|
-
|
|
411
|
-
// force every section's real `open`/`data-state` for print in JS and
|
|
412
|
-
// restore afterward. `beforeprint`/`afterprint` cover an actual print
|
|
413
|
-
// dialog; `matchMedia('print')`'s `change` event additionally covers
|
|
414
|
-
// tools (headless browsers, some print-preview UIs) that flip the media
|
|
415
|
-
// type without firing those events.
|
|
416
|
-
let savedState: { open: boolean; state: string }[] | null = null;
|
|
417
|
-
|
|
418
|
-
const expandAllForPrint = () => {
|
|
419
|
-
if (document.querySelector('[data-explico-print-controller]')) return;
|
|
420
|
-
if (savedState) return; // already expanded for a print in progress
|
|
421
|
-
const all = Array.from(
|
|
422
|
-
document.querySelectorAll<HTMLDetailsElement>(
|
|
423
|
-
'.sl-section-collapsible',
|
|
424
|
-
),
|
|
425
|
-
);
|
|
426
|
-
savedState = all.map((d) => ({
|
|
427
|
-
open: d.open,
|
|
428
|
-
state: d.dataset.state ?? 'closed',
|
|
429
|
-
}));
|
|
430
|
-
for (const d of all) {
|
|
431
|
-
d.open = true;
|
|
432
|
-
d.dataset.state = 'open' satisfies State;
|
|
433
|
-
(d.querySelector<HTMLElement>(':scope > .sl-section-body') ??
|
|
434
|
-
undefined)?.style.removeProperty('max-height');
|
|
435
|
-
}
|
|
436
|
-
};
|
|
437
|
-
|
|
438
|
-
const restoreAfterPrint = () => {
|
|
439
|
-
if (!savedState) return;
|
|
440
|
-
const all = document.querySelectorAll<HTMLDetailsElement>(
|
|
441
|
-
'.sl-section-collapsible',
|
|
442
|
-
);
|
|
443
|
-
all.forEach((d, i) => {
|
|
444
|
-
const saved = savedState?.[i];
|
|
445
|
-
if (!saved) return;
|
|
446
|
-
d.open = saved.open;
|
|
447
|
-
d.dataset.state = saved.state;
|
|
448
|
-
d.querySelector<HTMLElement>(':scope > .sl-section-body')?.style
|
|
449
|
-
.removeProperty('max-height');
|
|
450
|
-
});
|
|
451
|
-
savedState = null;
|
|
452
|
-
};
|
|
453
|
-
|
|
454
|
-
window.addEventListener('beforeprint', expandAllForPrint);
|
|
455
|
-
window.addEventListener('afterprint', restoreAfterPrint);
|
|
456
|
-
window.matchMedia('print').addEventListener('change', (event) => {
|
|
457
|
-
if (event.matches) expandAllForPrint();
|
|
458
|
-
else restoreAfterPrint();
|
|
459
|
-
});
|
|
568
|
+
// Print follows the screen: a section the reader left open prints in
|
|
569
|
+
// full, a closed (or merely peeking) one prints its title plus a
|
|
570
|
+
// "(content collapsed …)" note. `LessonExportActions.astro` does this
|
|
571
|
+
// when the course has the print controller; this covers the browser's
|
|
572
|
+
// own print on a course without it.
|
|
573
|
+
installPrintAsShownFallback();
|
|
460
574
|
})();
|
|
461
575
|
</script>
|