explico 0.4.0 → 0.5.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 +273 -1
- package/README.md +103 -8
- package/dist/compiler/astro.d.ts +9 -1
- package/dist/compiler/astro.js +182 -3
- package/dist/compiler/cli.d.ts +13 -0
- package/dist/compiler/cli.js +28 -4
- package/dist/compiler/collections.d.ts +3 -2
- package/dist/compiler/collections.js +28 -1
- package/dist/compiler/course-config.d.ts +58 -1
- package/dist/compiler/course-config.js +28 -1
- package/dist/compiler/exports/standalone-html.d.ts +34 -0
- package/dist/compiler/exports/standalone-html.js +803 -0
- package/dist/compiler/manifest.d.ts +90 -6
- package/dist/compiler/manifest.js +150 -17
- package/dist/compiler/resolution-report.d.ts +4 -0
- package/dist/reference/base-abbreviations.d.mts +30 -0
- package/dist/reference/base-abbreviations.mjs +60 -0
- package/dist/reference/base-operators.d.mts +15 -0
- package/dist/reference/base-operators.mjs +40 -0
- package/dist/reference/base-roman-labels.d.mts +35 -0
- package/dist/reference/base-roman-labels.mjs +43 -0
- package/dist/reference/consistency.d.ts +8 -2
- package/dist/reference/consistency.js +50 -3
- package/dist/reference/diagrams-validate.d.ts +21 -0
- package/dist/reference/diagrams-validate.js +13 -0
- package/dist/reference/equations.d.mts +10 -15
- package/dist/reference/equations.mjs +67 -14
- package/dist/reference/gate-abbreviations.d.ts +17 -0
- package/dist/reference/gate-abbreviations.js +64 -0
- package/dist/reference/gate-math.d.ts +28 -5
- package/dist/reference/gate-math.js +5 -2
- package/dist/reference/gate-prose-notation.d.ts +60 -0
- package/dist/reference/gate-prose-notation.js +125 -0
- package/dist/reference/keyed-float-validate.d.ts +5 -5
- package/dist/reference/keyed-jsx-floats.d.mts +7 -12
- package/dist/reference/keyed-jsx-floats.mjs +50 -25
- package/dist/reference/knowledge-checks-validate.d.ts +35 -0
- package/dist/reference/knowledge-checks-validate.js +78 -0
- package/dist/reference/knowledge-checks.d.mts +25 -0
- package/dist/reference/knowledge-checks.mjs +45 -0
- package/dist/reference/lab-math-scope.d.ts +9 -1
- package/dist/reference/lab-math-scope.js +32 -4
- package/dist/reference/math-glyphs.d.mts +30 -0
- package/dist/reference/math-glyphs.mjs +367 -0
- package/dist/reference/mdx-attributes.d.mts +13 -0
- package/dist/reference/mdx-attributes.mjs +38 -0
- package/dist/reference/mixed-content.d.ts +17 -0
- package/dist/reference/mixed-content.js +29 -0
- package/dist/reference/page-notation.d.ts +52 -0
- package/dist/reference/page-notation.js +171 -0
- package/dist/reference/raw-floats-validate.d.ts +18 -0
- package/dist/reference/raw-floats-validate.js +53 -0
- package/dist/reference/raw-floats.d.mts +18 -0
- package/dist/reference/raw-floats.mjs +129 -0
- package/dist/reference/rehype-heading-anchors.d.mts +1 -1
- package/dist/reference/rehype-heading-anchors.mjs +107 -1
- package/dist/reference/rehype-table-header-labels.d.mts +26 -0
- package/dist/reference/rehype-table-header-labels.mjs +152 -0
- package/dist/reference/remark-abbreviations.d.mts +7 -0
- package/dist/reference/remark-abbreviations.mjs +174 -0
- package/dist/reference/remark-memo.d.mts +16 -0
- package/dist/reference/remark-memo.mjs +339 -0
- package/dist/reference/remark-notation.d.mts +57 -9
- package/dist/reference/remark-notation.mjs +354 -42
- package/dist/reference/remark-short-note.d.mts +4 -0
- package/dist/reference/remark-short-note.mjs +237 -0
- package/dist/reference/types.d.ts +16 -1
- package/dist/reference/units-vocab.js +4 -0
- package/docs/VERSIONING.md +33 -4
- package/docs/authoring/README.md +8 -0
- package/docs/authoring/agents.md +17 -2
- package/docs/authoring/components.md +135 -0
- package/docs/authoring/migrating.md +92 -7
- package/docs/authoring/notation.md +560 -1
- package/docs/authoring/structure.md +120 -8
- package/docs/authoring/visualizations.md +36 -3
- package/package.json +5 -2
- package/src/compiler/astro.ts +208 -3
- package/src/compiler/cli.ts +29 -2
- package/src/compiler/collections.ts +45 -4
- package/src/compiler/course-config.ts +92 -0
- package/src/compiler/exports/standalone-html.ts +963 -0
- package/src/compiler/manifest.ts +268 -22
- package/src/components/HoverPanel.astro +45 -4
- package/src/components/abbreviation/AbbreviationGlossary.astro +70 -0
- package/src/components/abbreviation/AbbreviationLayer.astro +127 -0
- package/src/components/assessments/AssessmentRunner.tsx +52 -17
- package/src/components/assessments/AssessmentSet.astro +98 -14
- package/src/components/assessments/RenderedContent.tsx +55 -0
- package/src/components/assessments/assessment-randomization.ts +16 -0
- package/src/components/diagram/Diagram.astro +444 -83
- package/src/components/disclosure/hover-preview.ts +202 -0
- package/src/components/disclosure/persisted-disclosure.ts +66 -0
- package/src/components/examples/CompactExamples.astro +2 -0
- package/src/components/figure/Figure.astro +279 -10
- package/src/components/memo/Memo.astro +453 -0
- package/src/components/memo/ShortNote.astro +223 -0
- package/src/components/notation/NotationFormula.astro +18 -1
- package/src/components/notation/NotationLayer.astro +105 -8
- package/src/components/notation/render-notation-math.ts +39 -2
- package/src/components/notation/safe-math-to-react.ts +50 -0
- package/src/components/reference/FloatRefLayer.astro +233 -15
- package/src/components/starlight/CollapsibleSections.astro +461 -0
- package/src/components/starlight/DiagramEnhancer.astro +385 -0
- package/src/components/starlight/HeadingAnchorEnhancer.astro +5 -17
- package/src/components/starlight/KeyedFloatEnhancer.astro +81 -3
- package/src/components/starlight/LayoutHeader.astro +50 -0
- package/src/components/starlight/LayoutSocialIcons.astro +41 -2
- package/src/components/starlight/LessonExportActions.astro +436 -0
- package/src/components/starlight/LessonFooter.astro +27 -2
- package/src/components/starlight/section-parts.ts +31 -0
- package/src/components/table/Table.astro +293 -7
- package/src/components/table/TableOverflowExpander.astro +489 -0
- package/src/content-config.ts +20 -0
- package/src/reference/base-abbreviations.mjs +60 -0
- package/src/reference/base-operators.mjs +40 -0
- package/src/reference/base-roman-labels.mjs +43 -0
- package/src/reference/consistency.ts +73 -2
- package/src/reference/diagrams-validate.ts +40 -0
- package/src/reference/equations.mjs +67 -14
- package/src/reference/gate-abbreviations.ts +128 -0
- package/src/reference/gate-math.ts +27 -7
- package/src/reference/gate-prose-notation.ts +224 -0
- package/src/reference/keyed-float-validate.ts +5 -5
- package/src/reference/keyed-jsx-floats.mjs +50 -25
- package/src/reference/knowledge-checks-validate.ts +130 -0
- package/src/reference/knowledge-checks.mjs +45 -0
- package/src/reference/lab-math-scope.ts +45 -3
- package/src/reference/math-glyphs.mjs +367 -0
- package/src/reference/mdx-attributes.mjs +38 -0
- package/src/reference/mixed-content.ts +69 -0
- package/src/reference/page-notation.ts +252 -0
- package/src/reference/raw-floats-validate.ts +103 -0
- package/src/reference/raw-floats.mjs +129 -0
- package/src/reference/rehype-heading-anchors.mjs +107 -1
- package/src/reference/rehype-table-header-labels.mjs +152 -0
- package/src/reference/remark-abbreviations.mjs +174 -0
- package/src/reference/remark-memo.mjs +339 -0
- package/src/reference/remark-notation.mjs +354 -42
- package/src/reference/remark-short-note.mjs +237 -0
- package/src/reference/types.ts +16 -0
- package/src/reference/units-vocab.ts +4 -0
- package/src/styles/global.css +996 -50
|
@@ -15,8 +15,15 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import NotationMath from './NotationMath.astro';
|
|
17
17
|
import SafeMathNode from './SafeMathNode.astro';
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
renderNotationFormula,
|
|
20
|
+
stripNotationMarkers,
|
|
21
|
+
} from './render-notation-math';
|
|
19
22
|
import type { GlyphScopeEntry, ResolvedGloss } from '../../reference/gloss';
|
|
23
|
+
import {
|
|
24
|
+
notationKeysInMathNodes,
|
|
25
|
+
registerPageNotationDependencies,
|
|
26
|
+
} from '../../reference/page-notation';
|
|
20
27
|
|
|
21
28
|
export interface Props {
|
|
22
29
|
formula: string;
|
|
@@ -36,12 +43,18 @@ const {
|
|
|
36
43
|
entryLabel,
|
|
37
44
|
} = Astro.props;
|
|
38
45
|
const { nodes } = renderNotationFormula(formula, scope);
|
|
46
|
+
registerPageNotationDependencies(
|
|
47
|
+
Astro.locals,
|
|
48
|
+
notationKeysInMathNodes(nodes),
|
|
49
|
+
`NotationFormula formula "${formula}"`,
|
|
50
|
+
);
|
|
39
51
|
---
|
|
40
52
|
|
|
41
53
|
<div class="notation-formula-block">
|
|
42
54
|
<div
|
|
43
55
|
class="notation-formula-math"
|
|
44
56
|
data-notation-formula
|
|
57
|
+
data-latex={stripNotationMarkers(formula)}
|
|
45
58
|
role="group"
|
|
46
59
|
aria-label={entryLabel ? `${entryLabel} formula` : 'Formula'}
|
|
47
60
|
tabindex="0"
|
|
@@ -88,6 +101,10 @@ const { nodes } = renderNotationFormula(formula, scope);
|
|
|
88
101
|
container is the fix; this is its focus ring. */
|
|
89
102
|
.notation-formula-math {
|
|
90
103
|
overflow-x: auto;
|
|
104
|
+
/* `overflow-x: auto` alone makes the block axis compute to `auto` too
|
|
105
|
+
(CSS Overflow §3) — equations must never scroll vertically on their
|
|
106
|
+
own, only sideways. */
|
|
107
|
+
overflow-y: hidden;
|
|
91
108
|
padding-block: 0.15rem;
|
|
92
109
|
color: var(--sl-color-text);
|
|
93
110
|
}
|
|
@@ -46,6 +46,7 @@ export interface Props {
|
|
|
46
46
|
|
|
47
47
|
import NotationMath from './NotationMath.astro';
|
|
48
48
|
import NotationFormula from './NotationFormula.astro';
|
|
49
|
+
import { stripNotationMarkers } from './render-notation-math';
|
|
49
50
|
import type { GlyphScopeEntry, ResolvedGloss } from '../../reference/gloss';
|
|
50
51
|
|
|
51
52
|
const {
|
|
@@ -169,7 +170,11 @@ for (const gloss of glossItems) {
|
|
|
169
170
|
entryLabel={definition.label}
|
|
170
171
|
/>
|
|
171
172
|
) : (
|
|
172
|
-
<span
|
|
173
|
+
<span
|
|
174
|
+
class="notation-formula"
|
|
175
|
+
data-notation-formula
|
|
176
|
+
data-latex={stripNotationMarkers(definition.formula)}
|
|
177
|
+
>
|
|
173
178
|
<NotationMath latex={definition.formula} />
|
|
174
179
|
</span>
|
|
175
180
|
))}
|
|
@@ -244,6 +249,14 @@ for (const gloss of glossItems) {
|
|
|
244
249
|
</header>
|
|
245
250
|
<p data-slot-summary hidden />
|
|
246
251
|
<div data-slot-formula hidden />
|
|
252
|
+
<button
|
|
253
|
+
class="notation-popup-copy"
|
|
254
|
+
type="button"
|
|
255
|
+
data-popup-copy
|
|
256
|
+
hidden
|
|
257
|
+
>
|
|
258
|
+
Copy LaTeX
|
|
259
|
+
</button>
|
|
247
260
|
<p class="notation-units" data-slot-units hidden />
|
|
248
261
|
<a data-slot-link hidden>
|
|
249
262
|
Open the full definition
|
|
@@ -369,7 +382,18 @@ for (const gloss of glossItems) {
|
|
|
369
382
|
const units = fragment.querySelector<HTMLElement>('[data-slot-units]');
|
|
370
383
|
const link =
|
|
371
384
|
fragment.querySelector<HTMLAnchorElement>('[data-slot-link]');
|
|
372
|
-
|
|
385
|
+
const copyButton = fragment.querySelector<HTMLButtonElement>(
|
|
386
|
+
'[data-popup-copy]',
|
|
387
|
+
);
|
|
388
|
+
if (
|
|
389
|
+
!el ||
|
|
390
|
+
!symbolHost ||
|
|
391
|
+
!title ||
|
|
392
|
+
!summary ||
|
|
393
|
+
!formulaHost ||
|
|
394
|
+
!units ||
|
|
395
|
+
!copyButton
|
|
396
|
+
) {
|
|
373
397
|
return null;
|
|
374
398
|
}
|
|
375
399
|
|
|
@@ -395,6 +419,35 @@ for (const gloss of glossItems) {
|
|
|
395
419
|
...(sourceFormula ? [sourceFormula.cloneNode(true)] : []),
|
|
396
420
|
);
|
|
397
421
|
formulaHost.hidden = sourceFormula === null;
|
|
422
|
+
|
|
423
|
+
// The formula's own `data-latex` carries the plain, authored LaTeX —
|
|
424
|
+
// not the `\explain{key}{…}`-wrapped source KaTeX actually rendered —
|
|
425
|
+
// so what a reader copies is what they'd write themselves, not an
|
|
426
|
+
// internal marker macro. See `render-notation-math.ts:renderNotationFormula`.
|
|
427
|
+
const formulaLatex = sourceFormula?.dataset.latex ?? '';
|
|
428
|
+
copyButton.hidden = formulaLatex.length === 0;
|
|
429
|
+
if (formulaLatex.length > 0) {
|
|
430
|
+
copyButton.addEventListener('click', (event) => {
|
|
431
|
+
// Otherwise this bubbles to the document click handler in
|
|
432
|
+
// `popup-stack.ts`, which treats any click inside a popup that
|
|
433
|
+
// isn't a trigger or a link as "close it" — that would remove the
|
|
434
|
+
// card before the "Copied!" flash below ever paints.
|
|
435
|
+
event.stopPropagation();
|
|
436
|
+
if (!navigator.clipboard) return;
|
|
437
|
+
navigator.clipboard.writeText(formulaLatex).then(
|
|
438
|
+
() => {
|
|
439
|
+
copyButton.dataset.copied = 'true';
|
|
440
|
+
window.setTimeout(() => {
|
|
441
|
+
delete copyButton.dataset.copied;
|
|
442
|
+
}, 1200);
|
|
443
|
+
},
|
|
444
|
+
() => {
|
|
445
|
+
/* Clipboard blocked: nothing more we can do here. */
|
|
446
|
+
},
|
|
447
|
+
);
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
|
|
398
451
|
setOptionalText(
|
|
399
452
|
units,
|
|
400
453
|
entry
|
|
@@ -573,6 +626,10 @@ for (const gloss of glossItems) {
|
|
|
573
626
|
|
|
574
627
|
.notation-formula {
|
|
575
628
|
overflow-x: auto;
|
|
629
|
+
/* `overflow-x: auto` alone makes the block axis compute to `auto` too
|
|
630
|
+
(CSS Overflow §3) — equations must never scroll vertically on their
|
|
631
|
+
own, only sideways. */
|
|
632
|
+
overflow-y: hidden;
|
|
576
633
|
padding-block: 0.15rem;
|
|
577
634
|
color: var(--sl-color-text);
|
|
578
635
|
}
|
|
@@ -612,15 +669,19 @@ for (const gloss of glossItems) {
|
|
|
612
669
|
margin: 0;
|
|
613
670
|
width: min(23rem, calc(100vw - 1.5rem));
|
|
614
671
|
overflow: auto;
|
|
615
|
-
padding:
|
|
616
|
-
padding-inline
|
|
672
|
+
padding-block: 0.75rem;
|
|
673
|
+
padding-inline: 1rem 2.4rem;
|
|
617
674
|
border: 1px solid var(--sl-color-gray-5);
|
|
618
675
|
border-top: 0.25rem solid var(--sl-color-accent);
|
|
619
676
|
border-radius: 0.65rem;
|
|
620
677
|
color: var(--sl-color-text);
|
|
621
678
|
background: var(--sl-color-bg);
|
|
622
679
|
box-shadow: var(--sl-shadow-lg);
|
|
623
|
-
font-size
|
|
680
|
+
/* Relative to the surrounding prose's own font-size, not a fixed
|
|
681
|
+
Starlight step — the card is mounted on `<body>`, alongside the
|
|
682
|
+
article rather than nested inside it, so `%` still resolves against
|
|
683
|
+
the same base the article's paragraphs use. */
|
|
684
|
+
font-size: 90%;
|
|
624
685
|
}
|
|
625
686
|
|
|
626
687
|
.notation-popup-close {
|
|
@@ -677,15 +738,51 @@ for (const gloss of glossItems) {
|
|
|
677
738
|
}
|
|
678
739
|
|
|
679
740
|
.notation-panel-body p {
|
|
680
|
-
margin-block: 0.
|
|
741
|
+
margin-block: 0.5rem;
|
|
681
742
|
color: var(--sl-color-gray-2);
|
|
682
|
-
font-size: var(--sl-text-sm);
|
|
683
743
|
line-height: 1.5;
|
|
684
744
|
}
|
|
685
745
|
|
|
746
|
+
.notation-popup-copy {
|
|
747
|
+
position: relative;
|
|
748
|
+
margin-block: 0.4rem 0.6rem;
|
|
749
|
+
padding: 0.2rem 0.55rem;
|
|
750
|
+
border: 1px solid var(--sl-color-gray-5);
|
|
751
|
+
border-radius: 0.35rem;
|
|
752
|
+
color: var(--sl-color-text-accent);
|
|
753
|
+
background: var(--sl-color-bg);
|
|
754
|
+
font: inherit;
|
|
755
|
+
font-size: var(--sl-text-xs);
|
|
756
|
+
cursor: pointer;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
.notation-popup-copy:hover {
|
|
760
|
+
border-color: var(--sl-color-accent);
|
|
761
|
+
background: var(--sl-color-accent-low);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
.notation-popup-copy[data-copied]::after {
|
|
765
|
+
content: 'Copied!';
|
|
766
|
+
position: absolute;
|
|
767
|
+
inset-block-end: calc(100% + 0.35rem);
|
|
768
|
+
inset-inline-start: 50%;
|
|
769
|
+
translate: -50% 0;
|
|
770
|
+
padding: 0.15rem 0.45rem;
|
|
771
|
+
border-radius: 0.35rem;
|
|
772
|
+
background: var(--sl-color-accent-low);
|
|
773
|
+
color: var(--sl-color-text-accent);
|
|
774
|
+
font-size: var(--sl-text-xs);
|
|
775
|
+
white-space: nowrap;
|
|
776
|
+
pointer-events: none;
|
|
777
|
+
}
|
|
778
|
+
|
|
686
779
|
.notation-panel-body [data-slot-formula] {
|
|
687
780
|
overflow-x: auto;
|
|
688
|
-
|
|
781
|
+
/* See `.notation-formula` above: pairs with `overflow-x` to stop the
|
|
782
|
+
auto-both-axes quirk from giving the formula its own vertical
|
|
783
|
+
scrollbar. */
|
|
784
|
+
overflow-y: hidden;
|
|
785
|
+
margin-block: 0.5rem;
|
|
689
786
|
color: var(--sl-color-text);
|
|
690
787
|
}
|
|
691
788
|
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import rehypeKatex from 'rehype-katex';
|
|
2
2
|
import { createNotationKatexOptions } from '../../reference/katex-options.mjs';
|
|
3
3
|
import { NOTATION_KEY_PATTERN } from '../../reference/remark-notation.mjs';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
GlyphResolutionError,
|
|
6
|
+
resolveMathGlyphs,
|
|
7
|
+
stripNotationMarkers,
|
|
8
|
+
} from '../../reference/math-glyphs.mjs';
|
|
5
9
|
import type { GlyphScopeEntry } from '../../reference/gloss';
|
|
6
10
|
|
|
7
11
|
interface HastText {
|
|
@@ -32,6 +36,19 @@ export type SafeMathNode =
|
|
|
32
36
|
children: readonly SafeMathNode[];
|
|
33
37
|
};
|
|
34
38
|
|
|
39
|
+
/**
|
|
40
|
+
* A string that mixes prose and math, already split and rendered — text runs
|
|
41
|
+
* verbatim, a math run as the same safe, structured tree `SafeMathNode` is.
|
|
42
|
+
* Produced by `AssessmentSet.astro` from `manifest.assessmentContent`
|
|
43
|
+
* (`ContentSegment[]`, unrendered LaTeX) via {@link renderNotationMath}, and
|
|
44
|
+
* consumed by `AssessmentRunner`'s React-side recursive renderer
|
|
45
|
+
* (`RenderedContent.tsx`) — the React equivalent of `SafeMathNode.astro`,
|
|
46
|
+
* since a React island can't render an `.astro` component.
|
|
47
|
+
*/
|
|
48
|
+
export type MixedContentNode =
|
|
49
|
+
| { type: 'text'; value: string }
|
|
50
|
+
| { type: 'math'; nodes: readonly SafeMathNode[] };
|
|
51
|
+
|
|
35
52
|
const allowedTags = new Set([
|
|
36
53
|
'annotation',
|
|
37
54
|
'math',
|
|
@@ -203,6 +220,14 @@ function sanitizeNode(node: HastNode): SafeMathNode {
|
|
|
203
220
|
});
|
|
204
221
|
}
|
|
205
222
|
|
|
223
|
+
// Re-exported so existing imports of `stripNotationMarkers` from this module
|
|
224
|
+
// (the notation popup card's "Copy LaTeX" button) keep working. The
|
|
225
|
+
// implementation lives in `math-glyphs.mjs` because `remark-notation.mjs`
|
|
226
|
+
// (a numbered display equation's own copy button, at build time) needs it
|
|
227
|
+
// too, and importing it from here would make that a circular dependency —
|
|
228
|
+
// this module already imports from `remark-notation.mjs`.
|
|
229
|
+
export { stripNotationMarkers };
|
|
230
|
+
|
|
206
231
|
/**
|
|
207
232
|
* Render repository-authored LaTeX through the same build-time KaTeX adapter
|
|
208
233
|
* as lesson math, then expose a constrained structured tree to Astro. No HTML
|
|
@@ -274,7 +299,19 @@ export function renderNotationFormula(
|
|
|
274
299
|
const result = resolveMathGlyphs(formula, [...scope]);
|
|
275
300
|
bound = result.latex;
|
|
276
301
|
unresolved = result.unresolved.map(({ token }: { token: string }) => token);
|
|
277
|
-
} catch {
|
|
302
|
+
} catch (error) {
|
|
303
|
+
// N13 (a nonlinear function with no parenthesized argument) is a hard,
|
|
304
|
+
// no-exceptions rule like N9 — never degrade it to inert math, so both
|
|
305
|
+
// `Diagram` (which turns `degraded` into a build error anyway, but with
|
|
306
|
+
// a misleading "notation scope" message) and `NotationFormula` (which
|
|
307
|
+
// otherwise keeps a degraded formula's inert-but-correct fallback) fail
|
|
308
|
+
// loudly with the actual fix instead.
|
|
309
|
+
if (
|
|
310
|
+
error instanceof GlyphResolutionError &&
|
|
311
|
+
error.code === 'unparenthesized-function'
|
|
312
|
+
) {
|
|
313
|
+
throw error;
|
|
314
|
+
}
|
|
278
315
|
bound = formula;
|
|
279
316
|
degraded = true;
|
|
280
317
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapt a `SafeMathNode.properties` object — HTML attribute names and a raw
|
|
3
|
+
* CSS-text `style` string, exactly what `SafeMathNode.astro` spreads onto a
|
|
4
|
+
* dynamic element — into React's prop shape, for the React-side renderer
|
|
5
|
+
* (`RenderedContent.tsx`) `SafeMathNode.astro` doesn't need: React requires
|
|
6
|
+
* `className` (not `class`) and an object for `style` (not a CSS-text
|
|
7
|
+
* string) — "The `style` prop expects a mapping from style properties to
|
|
8
|
+
* values, not a string" is a hard React error, not a warning, so this isn't
|
|
9
|
+
* optional. Split into its own `.ts` module (rather than living inline in
|
|
10
|
+
* the `.tsx` component) so the parsing logic is unit-testable without a DOM.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** kebab-case CSS property -> camelCase, e.g. `margin-right` -> `marginRight`. */
|
|
14
|
+
export function camelCaseCssProperty(name: string): string {
|
|
15
|
+
return name.replace(/-([a-z])/g, (_, letter: string) => letter.toUpperCase());
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Parse a CSS-text `style` string into a React style object. `safeStyle`'s
|
|
20
|
+
* own grammar in `render-notation-math.ts`
|
|
21
|
+
* (`/^(?:[a-z-]+:[ A-Za-z0-9+.,()\-/%]*;)*$/`) guarantees every declaration
|
|
22
|
+
* is `key: value;` with no nested colons/semicolons — a straight split is
|
|
23
|
+
* exact for that input; this is not a general CSS parser.
|
|
24
|
+
*/
|
|
25
|
+
export function parseStyle(style: string): Record<string, string> {
|
|
26
|
+
const result: Record<string, string> = {};
|
|
27
|
+
for (const declaration of style.split(';')) {
|
|
28
|
+
if (declaration.trim() === '') continue;
|
|
29
|
+
const [property, value] = declaration.split(':');
|
|
30
|
+
if (property === undefined || value === undefined) continue;
|
|
31
|
+
result[camelCaseCssProperty(property.trim())] = value.trim();
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* `class` -> `className`, a `style` string -> a style object, everything
|
|
38
|
+
* else (`aria-hidden`, `data-notation-key`, the MathML attribute names)
|
|
39
|
+
* passed through unchanged — they're spelled the same in HTML and React.
|
|
40
|
+
*/
|
|
41
|
+
export function toReactProps(
|
|
42
|
+
properties: Readonly<Record<string, string>>,
|
|
43
|
+
): Record<string, unknown> {
|
|
44
|
+
const { class: className, style, ...rest } = properties;
|
|
45
|
+
return {
|
|
46
|
+
...rest,
|
|
47
|
+
...(className !== undefined ? { className } : {}),
|
|
48
|
+
...(style !== undefined ? { style: parseStyle(style) } : {}),
|
|
49
|
+
};
|
|
50
|
+
}
|