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
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Progressive behavior for `<Diagram>`:
|
|
4
4
|
*
|
|
5
|
-
* -
|
|
6
|
-
*
|
|
5
|
+
* - the widen button ↔ at the end of the diagram's caption line (a
|
|
6
|
+
* `.float-tool`, like a table's), shown only while the canvas overflows;
|
|
7
7
|
* - an in-page expanded surface that moves the real figure (never a clone) to
|
|
8
8
|
* `<body>`, overlays its placeholder, and follows that placeholder while the
|
|
9
9
|
* page scrolls — the same spatial model as `TableOverflowExpander.astro`.
|
|
@@ -16,15 +16,9 @@
|
|
|
16
16
|
---
|
|
17
17
|
|
|
18
18
|
<script>
|
|
19
|
-
import {
|
|
20
|
-
savePersistedOpenState,
|
|
21
|
-
wirePersistedDetails,
|
|
22
|
-
} from '../disclosure/persisted-disclosure';
|
|
23
|
-
import { claimActiveSurface, releaseActiveSurface } from '../hover-panel';
|
|
19
|
+
import { closeTransientCards } from '../card-manager';
|
|
24
20
|
|
|
25
|
-
const GROUP = 'diagram-overflow';
|
|
26
21
|
const EDGE_PX = 16;
|
|
27
|
-
const ANIMATION_MS = 220;
|
|
28
22
|
|
|
29
23
|
interface Expansion {
|
|
30
24
|
readonly figure: HTMLElement;
|
|
@@ -56,8 +50,8 @@
|
|
|
56
50
|
|
|
57
51
|
function captionFor(figure: HTMLElement): string {
|
|
58
52
|
return (
|
|
59
|
-
figure.querySelector<HTMLElement>('
|
|
60
|
-
|
|
53
|
+
figure.querySelector<HTMLElement>(':scope > figcaption[data-float-name]')?.dataset
|
|
54
|
+
.floatName ?? 'this diagram'
|
|
61
55
|
);
|
|
62
56
|
}
|
|
63
57
|
|
|
@@ -74,8 +68,6 @@
|
|
|
74
68
|
: `Expand ${captionFor(figure)} horizontally`,
|
|
75
69
|
);
|
|
76
70
|
button.title = expanded ? 'Shrink diagram' : 'Expand diagram horizontally';
|
|
77
|
-
const srText = button.querySelector<HTMLElement>('.sr-only');
|
|
78
|
-
if (srText) srText.textContent = expanded ? 'Shrink diagram' : 'Expand diagram';
|
|
79
71
|
}
|
|
80
72
|
|
|
81
73
|
function updateScrollFocusability(scroll: HTMLElement, figure: HTMLElement): void {
|
|
@@ -90,15 +82,12 @@
|
|
|
90
82
|
|
|
91
83
|
function evaluateOverflow(
|
|
92
84
|
figure: HTMLElement,
|
|
93
|
-
details: HTMLDetailsElement,
|
|
94
|
-
toolbar: HTMLElement,
|
|
95
85
|
button: HTMLButtonElement,
|
|
96
86
|
scroll: HTMLElement,
|
|
97
87
|
): void {
|
|
98
88
|
if (active?.figure === figure) return;
|
|
99
|
-
const overflows =
|
|
89
|
+
const overflows = scroll.scrollWidth > scroll.clientWidth + 1;
|
|
100
90
|
figure.dataset.diagramOverflow = String(overflows);
|
|
101
|
-
toolbar.hidden = !overflows;
|
|
102
91
|
button.hidden = !overflows;
|
|
103
92
|
updateScrollFocusability(scroll, figure);
|
|
104
93
|
}
|
|
@@ -130,8 +119,8 @@
|
|
|
130
119
|
// restored.
|
|
131
120
|
if (active && active.figure !== figure) return;
|
|
132
121
|
|
|
133
|
-
|
|
134
|
-
|
|
122
|
+
// A hovered card would linger over the expanded diagram; pinned ones stay.
|
|
123
|
+
closeTransientCards();
|
|
135
124
|
|
|
136
125
|
const neededWidth = scroll.scrollWidth;
|
|
137
126
|
const rect = figure.getBoundingClientRect();
|
|
@@ -183,13 +172,13 @@
|
|
|
183
172
|
80,
|
|
184
173
|
);
|
|
185
174
|
figure.style.maxHeight = `${maxHeight}px`;
|
|
186
|
-
const
|
|
175
|
+
const captionHeight =
|
|
187
176
|
figure
|
|
188
|
-
.querySelector<HTMLElement>('
|
|
177
|
+
.querySelector<HTMLElement>(':scope > figcaption')
|
|
189
178
|
?.getBoundingClientRect().height ?? 0;
|
|
190
179
|
figure.style.setProperty(
|
|
191
180
|
'--diagram-expanded-body-height',
|
|
192
|
-
`${Math.max(maxHeight -
|
|
181
|
+
`${Math.max(maxHeight - captionHeight, 40)}px`,
|
|
193
182
|
);
|
|
194
183
|
updateScrollFocusability(scroll, figure);
|
|
195
184
|
};
|
|
@@ -212,126 +201,22 @@
|
|
|
212
201
|
window.addEventListener('resize', reposition, { passive: true });
|
|
213
202
|
}
|
|
214
203
|
|
|
215
|
-
function wireDisclosure(
|
|
216
|
-
figure: HTMLElement,
|
|
217
|
-
details: HTMLDetailsElement,
|
|
218
|
-
body: HTMLElement,
|
|
219
|
-
evaluate: () => void,
|
|
220
|
-
): void {
|
|
221
|
-
const summary = details.querySelector<HTMLElement>(':scope > summary');
|
|
222
|
-
if (!summary) return;
|
|
223
|
-
const defaultOpen = details.open;
|
|
224
|
-
const remember = details.dataset.rememberState === 'true';
|
|
225
|
-
if (remember) {
|
|
226
|
-
wirePersistedDetails(details, `diagram:${figure.id}`, defaultOpen);
|
|
227
|
-
}
|
|
228
|
-
details.dataset.state = details.open ? 'open' : 'closed';
|
|
229
|
-
|
|
230
|
-
// An external/bookmarked deep link reveals the complete diagram. Later
|
|
231
|
-
// in-page jumps retain the reader's persisted minimized state.
|
|
232
|
-
if (location.hash === `#${figure.id}`) {
|
|
233
|
-
details.open = true;
|
|
234
|
-
details.dataset.state = 'open';
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
|
|
238
|
-
let finishTimer: number | null = null;
|
|
239
|
-
let transitionEnd: ((event: TransitionEvent) => void) | null = null;
|
|
240
|
-
|
|
241
|
-
const cancelAnimation = () => {
|
|
242
|
-
if (finishTimer !== null) window.clearTimeout(finishTimer);
|
|
243
|
-
finishTimer = null;
|
|
244
|
-
if (transitionEnd) body.removeEventListener('transitionend', transitionEnd);
|
|
245
|
-
transitionEnd = null;
|
|
246
|
-
body.style.removeProperty('transition');
|
|
247
|
-
body.style.removeProperty('overflow');
|
|
248
|
-
};
|
|
249
|
-
|
|
250
|
-
const setOpen = (open: boolean) => {
|
|
251
|
-
cancelAnimation();
|
|
252
|
-
const current = body.getBoundingClientRect().height;
|
|
253
|
-
if (open) details.open = true;
|
|
254
|
-
details.dataset.state = open ? 'open' : 'closed';
|
|
255
|
-
if (remember) savePersistedOpenState(`diagram:${figure.id}`, open);
|
|
256
|
-
|
|
257
|
-
if (reduceMotion.matches) {
|
|
258
|
-
details.open = open;
|
|
259
|
-
body.style.maxHeight = open ? '' : '0px';
|
|
260
|
-
if (active?.figure === figure) {
|
|
261
|
-
active.placeholder.style.height = `${figure.getBoundingClientRect().height}px`;
|
|
262
|
-
}
|
|
263
|
-
evaluate();
|
|
264
|
-
active?.reposition();
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
body.style.transition = 'none';
|
|
269
|
-
body.style.maxHeight = `${current}px`;
|
|
270
|
-
body.style.overflow = 'hidden';
|
|
271
|
-
void body.offsetHeight;
|
|
272
|
-
body.style.removeProperty('transition');
|
|
273
|
-
|
|
274
|
-
const expandedLimit = Number.parseFloat(
|
|
275
|
-
getComputedStyle(figure).getPropertyValue(
|
|
276
|
-
'--diagram-expanded-body-height',
|
|
277
|
-
),
|
|
278
|
-
);
|
|
279
|
-
const target = open
|
|
280
|
-
? Math.min(
|
|
281
|
-
body.scrollHeight,
|
|
282
|
-
Number.isFinite(expandedLimit) ? expandedLimit : Number.POSITIVE_INFINITY,
|
|
283
|
-
)
|
|
284
|
-
: 0;
|
|
285
|
-
const finish = () => {
|
|
286
|
-
cancelAnimation();
|
|
287
|
-
details.open = open;
|
|
288
|
-
body.style.maxHeight = open ? '' : '0px';
|
|
289
|
-
if (active?.figure === figure) {
|
|
290
|
-
active.placeholder.style.height = `${figure.getBoundingClientRect().height}px`;
|
|
291
|
-
}
|
|
292
|
-
evaluate();
|
|
293
|
-
active?.reposition();
|
|
294
|
-
};
|
|
295
|
-
transitionEnd = (event) => {
|
|
296
|
-
if (event.target === body && event.propertyName === 'max-height') finish();
|
|
297
|
-
};
|
|
298
|
-
body.addEventListener('transitionend', transitionEnd);
|
|
299
|
-
finishTimer = window.setTimeout(finish, ANIMATION_MS + 60);
|
|
300
|
-
requestAnimationFrame(() => {
|
|
301
|
-
body.style.maxHeight = `${target}px`;
|
|
302
|
-
});
|
|
303
|
-
};
|
|
304
|
-
|
|
305
|
-
summary.addEventListener('click', (event) => {
|
|
306
|
-
event.preventDefault();
|
|
307
|
-
setOpen(details.dataset.state !== 'open');
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
|
|
311
204
|
function setup(): void {
|
|
312
205
|
for (const figure of document.querySelectorAll<HTMLElement>(
|
|
313
206
|
'.diagram[data-diagram-expandable]',
|
|
314
207
|
)) {
|
|
315
208
|
if (figure.dataset.diagramExpandReady === 'true') continue;
|
|
316
|
-
const details = figure.querySelector<HTMLDetailsElement>(
|
|
317
|
-
'[data-diagram-details]',
|
|
318
|
-
);
|
|
319
|
-
const body = figure.querySelector<HTMLElement>('[data-diagram-body]');
|
|
320
|
-
const toolbar = figure.querySelector<HTMLElement>('[data-diagram-toolbar]');
|
|
321
209
|
const button = figure.querySelector<HTMLButtonElement>('[data-diagram-expand]');
|
|
322
210
|
const scroll = figure.querySelector<HTMLElement>('.diagram-scroll');
|
|
323
|
-
if (!
|
|
211
|
+
if (!button || !scroll) continue;
|
|
324
212
|
figure.dataset.diagramExpandReady = 'true';
|
|
325
213
|
|
|
326
|
-
const evaluate = () =>
|
|
327
|
-
evaluateOverflow(figure, details, toolbar, button, scroll);
|
|
214
|
+
const evaluate = () => evaluateOverflow(figure, button, scroll);
|
|
328
215
|
button.addEventListener('click', () => {
|
|
329
216
|
if (active?.figure === figure) collapseExpansion();
|
|
330
217
|
else expand(figure, button, scroll, evaluate);
|
|
331
218
|
});
|
|
332
219
|
new ResizeObserver(evaluate).observe(scroll);
|
|
333
|
-
details.addEventListener('toggle', evaluate);
|
|
334
|
-
wireDisclosure(figure, details, body, evaluate);
|
|
335
220
|
updateExpandButton(figure, button, false);
|
|
336
221
|
evaluate();
|
|
337
222
|
}
|
|
@@ -359,16 +244,8 @@
|
|
|
359
244
|
box-shadow: var(--sl-shadow-lg);
|
|
360
245
|
}
|
|
361
246
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
max-height: inherit;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
.diagram-is-expanded .diagram__details {
|
|
368
|
-
display: flex;
|
|
369
|
-
flex-direction: column;
|
|
370
|
-
}
|
|
371
|
-
|
|
247
|
+
/* The caption line stays below the graph; the body takes the height the
|
|
248
|
+
caption leaves (`--diagram-expanded-body-height`) and scrolls. */
|
|
372
249
|
.diagram-is-expanded .diagram__body {
|
|
373
250
|
min-height: 0;
|
|
374
251
|
max-height: var(--diagram-expanded-body-height);
|
|
@@ -10,6 +10,24 @@
|
|
|
10
10
|
* sticky-header `scroll-margin`.
|
|
11
11
|
*
|
|
12
12
|
* This script only adds what CSS and anchors cannot:
|
|
13
|
+
* - the chain icon copies the section's link: one click puts the page URL
|
|
14
|
+
* with the heading's named anchor (never its number) on the clipboard and
|
|
15
|
+
* says so beside the icon — "Link copied · § 1.1.2 · Velocity over time",
|
|
16
|
+
* the number read off the heading's `data-sec-number`
|
|
17
|
+
* (docs/plans/titles-and-containers.md, Q2/Q3). On a narrow screen, where
|
|
18
|
+
* headings show no number, this is where the number surfaces. The link is
|
|
19
|
+
* a named one (docs/plans/figures-and-short-captions.md, decision 9.3):
|
|
20
|
+
* beside the URL (`text/plain`), the clipboard holds an `<a>` reading
|
|
21
|
+
* "§ 1.1.2 Velocity over time" (`text/html`) where the browser has
|
|
22
|
+
* `ClipboardItem`, so a paste into a document keeps the name. The click
|
|
23
|
+
* still navigates, so the hash and the highlight below follow as before;
|
|
24
|
+
* a clipboard that is missing or refuses leaves exactly that navigation.
|
|
25
|
+
* On hover the icon says "Copy link", a title this script sets, since
|
|
26
|
+
* only with it does a click copy. A float's chain icon in its caption
|
|
27
|
+
* line (`a[data-copy-link]`, `FloatCaption.astro`) copies the same way,
|
|
28
|
+
* named by its number and short caption — "Link copied · Figure 2.2.1 ·
|
|
29
|
+
* Projectile trajectory explorer", pasted as "Figure 2.2.1, Projectile
|
|
30
|
+
* trajectory explorer"; its landing is `KeyedFloatEnhancer`'s;
|
|
13
31
|
* - a smooth, header-aware scroll on an in-page jump;
|
|
14
32
|
* - a brief highlight of the section body (the heading's following siblings up
|
|
15
33
|
* to the next heading of equal or higher rank), so a deep link lands the
|
|
@@ -38,6 +56,7 @@
|
|
|
38
56
|
<script>
|
|
39
57
|
import { HEADINGS, sectionParts } from './section-parts';
|
|
40
58
|
import { animatedScrollIntoView } from '../scroll-animation';
|
|
59
|
+
import { floatOf } from '../float-name';
|
|
41
60
|
|
|
42
61
|
(() => {
|
|
43
62
|
const root = document.querySelector('.sl-markdown-content');
|
|
@@ -90,14 +109,187 @@
|
|
|
90
109
|
}
|
|
91
110
|
};
|
|
92
111
|
|
|
112
|
+
// The link a reader can share: the page's canonical URL when it declares
|
|
113
|
+
// one (Starlight does once the site has a `site` URL), else this page
|
|
114
|
+
// without its query — and the heading's named anchor, never a number.
|
|
115
|
+
const shareableUrl = (id: string): string => {
|
|
116
|
+
const canonical = document.querySelector<HTMLLinkElement>(
|
|
117
|
+
'link[rel="canonical"]',
|
|
118
|
+
)?.href;
|
|
119
|
+
const url = new URL(canonical || location.href);
|
|
120
|
+
if (!canonical) url.search = '';
|
|
121
|
+
url.hash = id;
|
|
122
|
+
return url.href;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// Screen readers hear the confirmation from one polite live region; the
|
|
126
|
+
// bubble beside the icon is for the eye only.
|
|
127
|
+
const status = document.createElement('div');
|
|
128
|
+
status.className = 'sr-only';
|
|
129
|
+
status.setAttribute('role', 'status');
|
|
130
|
+
document.body.append(status);
|
|
131
|
+
|
|
132
|
+
const COPIED_MS = 1800;
|
|
133
|
+
let bubble: HTMLElement | null = null;
|
|
134
|
+
let bubbleTimer = 0;
|
|
135
|
+
|
|
136
|
+
// What a link is called: its kind and number ("§ 1.1.2", "Figure 2.2.1")
|
|
137
|
+
// and its title — a heading's text, a check's or example set's topic, a
|
|
138
|
+
// float's short caption. Either can be missing.
|
|
139
|
+
interface LinkName {
|
|
140
|
+
readonly label: string;
|
|
141
|
+
readonly title: string;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const headingName = (heading: HTMLElement): LinkName => {
|
|
145
|
+
const { secNumber, checkNumber, exNumber, memoNumber } = heading.dataset;
|
|
146
|
+
const label = secNumber
|
|
147
|
+
? `§ ${secNumber}`
|
|
148
|
+
: checkNumber
|
|
149
|
+
? `Knowledge check ${checkNumber}`
|
|
150
|
+
: exNumber
|
|
151
|
+
? `Example ${exNumber}`
|
|
152
|
+
: memoNumber
|
|
153
|
+
? `Memo ${memoNumber}`
|
|
154
|
+
: '';
|
|
155
|
+
// A memo goes by its short caption where it has one, as its marker's
|
|
156
|
+
// tooltip does; a check's or example set's heading also draws its kind
|
|
157
|
+
// and number, so only its topic counts.
|
|
158
|
+
const short = memoNumber
|
|
159
|
+
? heading.closest<HTMLElement>('[data-memo-short]')?.dataset.memoShort
|
|
160
|
+
: undefined;
|
|
161
|
+
const titled = heading.querySelector('.block-heading__title') ?? heading;
|
|
162
|
+
return {
|
|
163
|
+
label,
|
|
164
|
+
title: short || (titled.textContent?.replace(/\s+/g, ' ').trim() ?? ''),
|
|
165
|
+
};
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const floatName = (link: HTMLElement): LinkName => {
|
|
169
|
+
const root = link.closest('figure');
|
|
170
|
+
return {
|
|
171
|
+
label: link.dataset.copyLink ?? '',
|
|
172
|
+
title: (root && floatOf(root)?.short) || '',
|
|
173
|
+
};
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const confirmation = ({ label, title }: LinkName): string =>
|
|
177
|
+
['Link copied', label, title].filter(Boolean).join(' · ');
|
|
178
|
+
|
|
179
|
+
// "§ 1.1.2 Velocity over time", "Figure 2.2.1, Projectile trajectory
|
|
180
|
+
// explorer": the text a pasted link reads.
|
|
181
|
+
const linkText = ({ label, title }: LinkName): string =>
|
|
182
|
+
!title ? label : !label ? title : label.startsWith('§') ? `${label} ${title}` : `${label}, ${title}`;
|
|
183
|
+
|
|
184
|
+
// `below`: the viewport y the bubble hangs from — under the icon, or
|
|
185
|
+
// under a heading's whole title when that wraps lower.
|
|
186
|
+
const confirmCopied = (link: HTMLElement, message: string, below: number) => {
|
|
187
|
+
status.textContent = message;
|
|
188
|
+
|
|
189
|
+
bubble?.remove();
|
|
190
|
+
window.clearTimeout(bubbleTimer);
|
|
191
|
+
const shown = document.createElement('span');
|
|
192
|
+
shown.className = 'sl-anchor-copied';
|
|
193
|
+
shown.setAttribute('aria-hidden', 'true');
|
|
194
|
+
shown.textContent = message;
|
|
195
|
+
document.body.append(shown);
|
|
196
|
+
// Under the title — a long one wraps below its icon — with its end edge
|
|
197
|
+
// on the icon's, and never past either side of the text column: a short
|
|
198
|
+
// heading's icon sits near the start.
|
|
199
|
+
const icon = link.getBoundingClientRect();
|
|
200
|
+
const column = root.getBoundingClientRect();
|
|
201
|
+
const width = shown.offsetWidth;
|
|
202
|
+
const left = Math.max(
|
|
203
|
+
column.left,
|
|
204
|
+
Math.min(icon.right - width, column.right - width),
|
|
205
|
+
);
|
|
206
|
+
// Page coordinates, so the bubble travels with the heading while the
|
|
207
|
+
// click's own navigation scrolls it into place.
|
|
208
|
+
shown.style.left = `${Math.round(left + window.scrollX)}px`;
|
|
209
|
+
shown.style.top = `${Math.round(below + window.scrollY + 4)}px`;
|
|
210
|
+
bubble = shown;
|
|
211
|
+
bubbleTimer = window.setTimeout(() => {
|
|
212
|
+
shown.remove();
|
|
213
|
+
if (bubble === shown) bubble = null;
|
|
214
|
+
}, COPIED_MS);
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// The URL as plain text, and — where the browser can hold two flavours —
|
|
218
|
+
// a named link beside it, for a paste into a document.
|
|
219
|
+
const copyLink = (id: string, name: LinkName, confirm: () => void) => {
|
|
220
|
+
const clipboard = navigator.clipboard;
|
|
221
|
+
if (!clipboard?.writeText) return;
|
|
222
|
+
const url = shareableUrl(id);
|
|
223
|
+
const text = linkText(name);
|
|
224
|
+
let written: Promise<void>;
|
|
225
|
+
if (text && typeof ClipboardItem === 'function' && typeof clipboard.write === 'function') {
|
|
226
|
+
const named = document.createElement('a');
|
|
227
|
+
named.href = url;
|
|
228
|
+
named.textContent = text;
|
|
229
|
+
written = clipboard.write([
|
|
230
|
+
new ClipboardItem({
|
|
231
|
+
'text/plain': new Blob([url], { type: 'text/plain' }),
|
|
232
|
+
'text/html': new Blob([named.outerHTML], { type: 'text/html' }),
|
|
233
|
+
}),
|
|
234
|
+
]);
|
|
235
|
+
} else {
|
|
236
|
+
written = clipboard.writeText(url);
|
|
237
|
+
}
|
|
238
|
+
written.then(confirm, () => {
|
|
239
|
+
/* Refused: the navigation still happens, as it always has. */
|
|
240
|
+
});
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
// A heading's chain icon says on hover that a click copies, as a float's
|
|
244
|
+
// does in its own markup (`FloatCaption.astro`). The markup cannot say it
|
|
245
|
+
// here: without this script, or without a clipboard, the icon only
|
|
246
|
+
// navigates. So the title goes on exactly the icons the click handler
|
|
247
|
+
// below copies from — every heading's, including the ones
|
|
248
|
+
// `BlockHeading.astro` and `Memo.astro` draw.
|
|
249
|
+
if (typeof navigator.clipboard?.writeText === 'function') {
|
|
250
|
+
for (const link of root.querySelectorAll<HTMLElement>('a.sl-anchor-link')) {
|
|
251
|
+
const href = link.getAttribute('href') || '';
|
|
252
|
+
if (href.startsWith('#') && headingForId(href.slice(1))) {
|
|
253
|
+
link.title = 'Copy link';
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
93
258
|
root.addEventListener('click', (event) => {
|
|
94
259
|
const origin = event.target instanceof Element ? event.target : null;
|
|
95
|
-
|
|
260
|
+
|
|
261
|
+
// A float's chain icon: copy, confirm, and let the click navigate.
|
|
262
|
+
const floatLink = origin?.closest<HTMLElement>('a[data-copy-link]');
|
|
263
|
+
const floatHref = floatLink?.getAttribute('href') || '';
|
|
264
|
+
if (floatLink && floatHref.startsWith('#')) {
|
|
265
|
+
const name = floatName(floatLink);
|
|
266
|
+
copyLink(floatHref.slice(1), name, () =>
|
|
267
|
+
confirmCopied(
|
|
268
|
+
floatLink,
|
|
269
|
+
confirmation(name),
|
|
270
|
+
floatLink.getBoundingClientRect().bottom,
|
|
271
|
+
),
|
|
272
|
+
);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const link = origin?.closest<HTMLElement>('a.sl-anchor-link');
|
|
96
277
|
if (!link) return;
|
|
97
278
|
const href = link.getAttribute('href') || '';
|
|
98
279
|
if (!href.startsWith('#')) return;
|
|
99
280
|
const heading = headingForId(href.slice(1));
|
|
100
281
|
if (!heading) return;
|
|
282
|
+
const name = headingName(heading);
|
|
283
|
+
copyLink(heading.id, name, () =>
|
|
284
|
+
confirmCopied(
|
|
285
|
+
link,
|
|
286
|
+
confirmation(name),
|
|
287
|
+
Math.max(
|
|
288
|
+
link.getBoundingClientRect().bottom,
|
|
289
|
+
heading.getBoundingClientRect().bottom,
|
|
290
|
+
),
|
|
291
|
+
),
|
|
292
|
+
);
|
|
101
293
|
// Only the already-on-this-anchor click needs handling here — see
|
|
102
294
|
// `KeyedFloatEnhancer.astro`. Every other click is left to the browser's
|
|
103
295
|
// own fragment navigation, picked up below via `hashchange`.
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* activated again — the one case a plain anchor click does nothing for,
|
|
21
21
|
* since the browser won't navigate to a fragment that's already current;
|
|
22
22
|
* - moving keyboard focus onto the float so screen-reader users land on it;
|
|
23
|
+
* - making a display equation that overflows its column keyboard-scrollable;
|
|
23
24
|
* - for an equation only, marking its number permanently visible
|
|
24
25
|
* (`.eq-number-revealed`, styled in `global.css`) once the reader has
|
|
25
26
|
* actually arrived at it, so it doesn't fade back down once the
|
|
@@ -36,9 +37,31 @@
|
|
|
36
37
|
*
|
|
37
38
|
* Everything here is gated on `prefers-reduced-motion` and degrades to the
|
|
38
39
|
* native anchor behaviour if it throws.
|
|
40
|
+
*
|
|
41
|
+
* **Number aliases** (docs/plans/titles-and-containers.md, Q4). With the
|
|
42
|
+
* course's `numbering.aliases` on (the `aliases` prop, from the manifest), a
|
|
43
|
+
* fragment a reader types in number form — `#sec-1.1.2`, `#fig-2.2.1`,
|
|
44
|
+
* `#eq-2.2.4`, `#tab-…`, `#dia-…`, `#check-…`, `#ex-…` — is rewritten to the named
|
|
45
|
+
* anchor of the element that prints that number here. The map is the page
|
|
46
|
+
* itself: every numbered element already carries `data-‹kind›-number` beside
|
|
47
|
+
* its `id`, so an alias resolves exactly when the reader can see its number
|
|
48
|
+
* on this page, and never disagrees with it. Inbound only: nothing the site
|
|
49
|
+
* shows or copies is a number form, and an alias is only as stable as the
|
|
50
|
+
* content version the About panel names. This script runs before
|
|
51
|
+
* `CollapsibleSections` and `HeadingAnchorEnhancer` (see `LessonFooter`), so
|
|
52
|
+
* on arrival the rewrite is in place before either reads the hash — an alias
|
|
53
|
+
* lands exactly like a named deep link.
|
|
39
54
|
*/
|
|
55
|
+
interface Props {
|
|
56
|
+
/** `manifest.numbering.aliases`: resolve number-form fragments. */
|
|
57
|
+
aliases?: boolean;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const { aliases = false } = Astro.props;
|
|
40
61
|
---
|
|
41
62
|
|
|
63
|
+
{aliases && <div hidden data-explico-number-aliases />}
|
|
64
|
+
|
|
42
65
|
<script>
|
|
43
66
|
import { animatedScrollIntoView } from '../scroll-animation';
|
|
44
67
|
|
|
@@ -46,6 +69,21 @@
|
|
|
46
69
|
const root = document.querySelector('.sl-markdown-content');
|
|
47
70
|
if (!root) return;
|
|
48
71
|
|
|
72
|
+
// `#sec-1.1.2` → the element printing that number, if this page has one.
|
|
73
|
+
const ALIAS = /^#(sec|eq|tab|fig|dia|check|ex)-(\d+(?:\.\d+)+)$/;
|
|
74
|
+
const aliasesOn = document.querySelector('[data-explico-number-aliases]') !== null;
|
|
75
|
+
const aliasTarget = (hash: string): HTMLElement | null => {
|
|
76
|
+
const match = aliasesOn ? ALIAS.exec(hash) : null;
|
|
77
|
+
if (!match) return null;
|
|
78
|
+
return root.querySelector<HTMLElement>(
|
|
79
|
+
`[id][data-${match[1]}-number="${match[2]}"]`,
|
|
80
|
+
);
|
|
81
|
+
};
|
|
82
|
+
// On arrival: rewrite without an event, so every enhancer's own
|
|
83
|
+
// first-hash handling sees the named anchor.
|
|
84
|
+
const arrived = aliasTarget(location.hash);
|
|
85
|
+
if (arrived) history.replaceState(history.state, '', `#${arrived.id}`);
|
|
86
|
+
|
|
49
87
|
interface Kind {
|
|
50
88
|
readonly prefix: string;
|
|
51
89
|
readonly triggerSelector: string;
|
|
@@ -60,21 +98,23 @@
|
|
|
60
98
|
numberSelector: '.keyed-equation__number',
|
|
61
99
|
reveal: true,
|
|
62
100
|
},
|
|
101
|
+
// A float's caption line links to it twice: its number, and the chain
|
|
102
|
+
// icon beside it (which also copies the link, `HeadingAnchorEnhancer`).
|
|
63
103
|
{
|
|
64
104
|
prefix: '#tab-',
|
|
65
|
-
triggerSelector: 'a.table-ref, a.keyed-table__number',
|
|
105
|
+
triggerSelector: 'a.table-ref, a.keyed-table__number, a.float-caption__anchor',
|
|
66
106
|
numberSelector: '.keyed-table__number',
|
|
67
107
|
reveal: false,
|
|
68
108
|
},
|
|
69
109
|
{
|
|
70
110
|
prefix: '#fig-',
|
|
71
|
-
triggerSelector: 'a.figure-ref, a.keyed-figure__number',
|
|
111
|
+
triggerSelector: 'a.figure-ref, a.keyed-figure__number, a.float-caption__anchor',
|
|
72
112
|
numberSelector: '.keyed-figure__number',
|
|
73
113
|
reveal: false,
|
|
74
114
|
},
|
|
75
115
|
{
|
|
76
116
|
prefix: '#dia-',
|
|
77
|
-
triggerSelector: 'a.diagram-ref, a.
|
|
117
|
+
triggerSelector: 'a.diagram-ref, a.diagram__number, a.float-caption__anchor',
|
|
78
118
|
numberSelector: '.diagram__number',
|
|
79
119
|
reveal: false,
|
|
80
120
|
},
|
|
@@ -178,12 +218,14 @@
|
|
|
178
218
|
|
|
179
219
|
// A tooltip for the gesture above, and only for the part of the equation
|
|
180
220
|
// it actually applies to — an explained symbol has its own hover card
|
|
181
|
-
// (`NotationLayer.astro`) and needs no tooltip layered on top of it.
|
|
221
|
+
// (`NotationLayer.astro`) and needs no tooltip layered on top of it. It
|
|
222
|
+
// names the equation by its `\caption{}` short caption when it has one —
|
|
223
|
+
// "Copy LaTeX — Range formula", the same title as its copy button.
|
|
182
224
|
// `mouseover`/`mouseout` (not `mouseenter`/`mouseleave`) because they
|
|
183
225
|
// bubble, which delegation on `root` needs.
|
|
184
226
|
root.addEventListener('mouseover', (event) => {
|
|
185
227
|
const origin = event.target instanceof Element ? event.target : null;
|
|
186
|
-
if (!origin || origin.getAttribute('title')
|
|
228
|
+
if (!origin || origin.getAttribute('title')?.startsWith('Copy LaTeX')) return;
|
|
187
229
|
if (
|
|
188
230
|
origin.closest(
|
|
189
231
|
'[data-notation-key], [data-eq-copy], a.keyed-equation__number',
|
|
@@ -191,18 +233,67 @@
|
|
|
191
233
|
) {
|
|
192
234
|
return;
|
|
193
235
|
}
|
|
194
|
-
|
|
195
|
-
|
|
236
|
+
const equation = origin.closest<HTMLElement>('.keyed-equation, .inline-equation');
|
|
237
|
+
if (!equation) return;
|
|
238
|
+
const short = equation.dataset.eqShort;
|
|
239
|
+
origin.setAttribute('title', short ? `Copy LaTeX — ${short}` : 'Copy LaTeX');
|
|
240
|
+
origin.setAttribute('data-copy-hint', '');
|
|
196
241
|
});
|
|
197
242
|
|
|
198
243
|
root.addEventListener('mouseout', (event) => {
|
|
199
244
|
const origin = event.target instanceof Element ? event.target : null;
|
|
200
|
-
|
|
245
|
+
// Only a title this script added — the copy button's own static
|
|
246
|
+
// `title` must survive the pointer leaving it (or crossing onto its icon).
|
|
247
|
+
if (origin?.hasAttribute('data-copy-hint')) {
|
|
201
248
|
origin.removeAttribute('title');
|
|
249
|
+
origin.removeAttribute('data-copy-hint');
|
|
202
250
|
}
|
|
203
251
|
});
|
|
204
252
|
|
|
205
|
-
|
|
253
|
+
// A display equation wider than the column scrolls sideways
|
|
254
|
+
// (`.katex-display { overflow-x: auto }` in `global.css`) — on a phone,
|
|
255
|
+
// with a numbered equation's controls beside it, that is common. A
|
|
256
|
+
// scrollable region must be keyboard-reachable (WCAG 2.1.1, axe's
|
|
257
|
+
// `scrollable-region-focusable`), so, the same way
|
|
258
|
+
// `TableOverflowExpander.astro` and `DiagramEnhancer.astro` treat their
|
|
259
|
+
// scrollers, it gets a `tabindex` and a name only while it overflows. A
|
|
260
|
+
// `ResizeObserver` catches every case that changes that: first layout, a
|
|
261
|
+
// viewport change, and a collapsed section opening (no layout box while
|
|
262
|
+
// closed, so no overflow either).
|
|
263
|
+
const updateEquationScroll = (display: HTMLElement) => {
|
|
264
|
+
if (display.scrollWidth > display.clientWidth + 1) {
|
|
265
|
+
const number = display
|
|
266
|
+
.closest<HTMLElement>('.keyed-equation')
|
|
267
|
+
?.dataset.eqNumber;
|
|
268
|
+
display.tabIndex = 0;
|
|
269
|
+
display.setAttribute('role', 'group');
|
|
270
|
+
display.setAttribute(
|
|
271
|
+
'aria-label',
|
|
272
|
+
number ? `Scrollable equation (${number})` : 'Scrollable equation',
|
|
273
|
+
);
|
|
274
|
+
} else {
|
|
275
|
+
display.removeAttribute('tabindex');
|
|
276
|
+
display.removeAttribute('role');
|
|
277
|
+
display.removeAttribute('aria-label');
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
const equationResize = new ResizeObserver((entries) => {
|
|
281
|
+
for (const entry of entries) updateEquationScroll(entry.target as HTMLElement);
|
|
282
|
+
});
|
|
283
|
+
for (const display of root.querySelectorAll<HTMLElement>('.katex-display')) {
|
|
284
|
+
equationResize.observe(display);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
window.addEventListener('hashchange', (event) => {
|
|
288
|
+
// A number form typed into the address bar of a loaded page: navigate
|
|
289
|
+
// on to the named anchor. The listeners registered after this one
|
|
290
|
+
// (sections, headings) see only that second navigation, not this one.
|
|
291
|
+
const aliased = aliasTarget(location.hash);
|
|
292
|
+
if (aliased) {
|
|
293
|
+
event.stopImmediatePropagation();
|
|
294
|
+
location.replace(`#${aliased.id}`);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
206
297
|
const kind = kindForHash(location.hash);
|
|
207
298
|
if (!kind) return;
|
|
208
299
|
activate(document.getElementById(location.hash.slice(1)), true, kind);
|