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
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The "Hidden here: …" line a *closed* collapsible section shows under its
|
|
3
|
+
* title (see `CollapsibleSections.astro`), so a reader can tell there is
|
|
4
|
+
* content behind the title — and roughly how much — without opening it.
|
|
5
|
+
*
|
|
6
|
+
* A numbered float with a short caption is named, number first, before the
|
|
7
|
+
* counts of everything else: "Hidden here: **Figure 2.1.1** Projectile
|
|
8
|
+
* trajectory explorer, 2 paragraphs" (docs/plans/figures-and-short-captions.md,
|
|
9
|
+
* decision 9.2); a float without one stays in the counts ("1 table").
|
|
10
|
+
*
|
|
11
|
+
* Split in two so the wording is unit-testable without a DOM:
|
|
12
|
+
* `collectHiddenContent` walks a section body once (browser only), and
|
|
13
|
+
* `hiddenContentParts` / `describeHiddenContent` turn what it found into the
|
|
14
|
+
* reader-facing line.
|
|
15
|
+
*/
|
|
16
|
+
import { FLOAT_KIND_WORDS, floatOf, type NamedFloat } from '../float-name';
|
|
17
|
+
|
|
18
|
+
export interface HiddenContentCounts {
|
|
19
|
+
readonly subsections: number;
|
|
20
|
+
readonly paragraphs: number;
|
|
21
|
+
readonly equations: number;
|
|
22
|
+
readonly figures: number;
|
|
23
|
+
readonly tables: number;
|
|
24
|
+
readonly diagrams: number;
|
|
25
|
+
readonly lists: number;
|
|
26
|
+
readonly codeBlocks: number;
|
|
27
|
+
readonly memos: number;
|
|
28
|
+
readonly examples: number;
|
|
29
|
+
readonly questions: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Reading order of the summary line: structure first, then prose, then the
|
|
33
|
+
// numbered floats, then everything else.
|
|
34
|
+
const LABELS: readonly (readonly [keyof HiddenContentCounts, string, string])[] = [
|
|
35
|
+
['subsections', 'subsection', 'subsections'],
|
|
36
|
+
['paragraphs', 'paragraph', 'paragraphs'],
|
|
37
|
+
['equations', 'equation', 'equations'],
|
|
38
|
+
['figures', 'figure', 'figures'],
|
|
39
|
+
['tables', 'table', 'tables'],
|
|
40
|
+
['diagrams', 'diagram', 'diagrams'],
|
|
41
|
+
['lists', 'list', 'lists'],
|
|
42
|
+
['codeBlocks', 'code block', 'code blocks'],
|
|
43
|
+
['memos', 'memo', 'memos'],
|
|
44
|
+
['examples', 'worked example', 'worked examples'],
|
|
45
|
+
['questions', 'question', 'questions'],
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
/** One item of the line: a named float — its number set in bold — or a count. */
|
|
49
|
+
export type HiddenPart = string | { readonly number: string; readonly short: string };
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The line's items: every named float, in page order, then the counts; or
|
|
53
|
+
* `null` when nothing countable is inside (the caller then shows no line at
|
|
54
|
+
* all rather than a vague one). `named` holds only floats with a short
|
|
55
|
+
* caption, already left out of `counts`.
|
|
56
|
+
*/
|
|
57
|
+
export function hiddenContentParts(
|
|
58
|
+
counts: HiddenContentCounts,
|
|
59
|
+
named: readonly NamedFloat[] = [],
|
|
60
|
+
): HiddenPart[] | null {
|
|
61
|
+
const parts: HiddenPart[] = [
|
|
62
|
+
...named.flatMap((float) =>
|
|
63
|
+
float.short
|
|
64
|
+
? [{ number: `${FLOAT_KIND_WORDS[float.kind]} ${float.number}`, short: float.short }]
|
|
65
|
+
: [],
|
|
66
|
+
),
|
|
67
|
+
...LABELS.filter(([key]) => counts[key] > 0).map(
|
|
68
|
+
([key, singular, plural]) =>
|
|
69
|
+
`${counts[key]} ${counts[key] === 1 ? singular : plural}`,
|
|
70
|
+
),
|
|
71
|
+
];
|
|
72
|
+
return parts.length > 0 ? parts : null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* `"Hidden here: Figure 2.1.1 Projectile trajectory explorer, 2 paragraphs,
|
|
77
|
+
* 1 equation"` — the line as text — or `null` with nothing to say.
|
|
78
|
+
*/
|
|
79
|
+
export function describeHiddenContent(
|
|
80
|
+
counts: HiddenContentCounts,
|
|
81
|
+
named: readonly NamedFloat[] = [],
|
|
82
|
+
): string | null {
|
|
83
|
+
const parts = hiddenContentParts(counts, named);
|
|
84
|
+
if (!parts) return null;
|
|
85
|
+
const text = parts.map((part) =>
|
|
86
|
+
typeof part === 'string' ? part : `${part.number} ${part.short}`,
|
|
87
|
+
);
|
|
88
|
+
return `Hidden here: ${text.join(', ')}`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Components counted as one item each. Anything *inside* one of them (a
|
|
93
|
+
* figure's caption paragraph, a memo's equation, a question's option list,
|
|
94
|
+
* a worked example's working) belongs to that component and is not counted
|
|
95
|
+
* again on its own.
|
|
96
|
+
*/
|
|
97
|
+
const CONTAINERS =
|
|
98
|
+
'.keyed-figure, .keyed-table, .diagram, .memo-block, .compact-examples, .compact-example, .assessment-shell, .short-note';
|
|
99
|
+
|
|
100
|
+
/** Whether any ancestor of `element` strictly between it and `root` matches `selector`. */
|
|
101
|
+
function hasAncestorWithin(element: Element, root: Element, selector: string): boolean {
|
|
102
|
+
for (
|
|
103
|
+
let node = element.parentElement;
|
|
104
|
+
node && node !== root;
|
|
105
|
+
node = node.parentElement
|
|
106
|
+
) {
|
|
107
|
+
if (node.matches(selector)) return true;
|
|
108
|
+
}
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface HiddenContent {
|
|
113
|
+
readonly counts: HiddenContentCounts;
|
|
114
|
+
/** The numbered floats with a short caption, in page order; not in `counts`. */
|
|
115
|
+
readonly named: readonly NamedFloat[];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* What a section body holds, at every depth — a nested subsection's content
|
|
120
|
+
* is hidden along with its parent's, so it counts too. `headings` is the
|
|
121
|
+
* heading selector (`HEADINGS` from `./section-parts`). With `nameFloats:
|
|
122
|
+
* false` — a `<Fold>` whose bar already names its floats — every float stays
|
|
123
|
+
* a count.
|
|
124
|
+
*/
|
|
125
|
+
export function collectHiddenContent(
|
|
126
|
+
body: Element,
|
|
127
|
+
headings: string,
|
|
128
|
+
{ nameFloats = true }: { readonly nameFloats?: boolean } = {},
|
|
129
|
+
): HiddenContent {
|
|
130
|
+
const outside = (selector: string, alsoExclude = '') =>
|
|
131
|
+
Array.from(body.querySelectorAll(selector)).filter(
|
|
132
|
+
(element) =>
|
|
133
|
+
!hasAncestorWithin(element, body, CONTAINERS) &&
|
|
134
|
+
!(alsoExclude && hasAncestorWithin(element, body, alsoExclude)),
|
|
135
|
+
);
|
|
136
|
+
const itemsIn = (container: string, item: string) =>
|
|
137
|
+
outside(container).reduce(
|
|
138
|
+
(total, element) => total + element.querySelectorAll(item).length,
|
|
139
|
+
0,
|
|
140
|
+
);
|
|
141
|
+
const named = nameFloats
|
|
142
|
+
? outside('.keyed-figure, .keyed-table, .diagram').flatMap((element) => {
|
|
143
|
+
const float = floatOf(element);
|
|
144
|
+
return float?.short ? [float] : [];
|
|
145
|
+
})
|
|
146
|
+
: [];
|
|
147
|
+
const unnamed = (selector: string) =>
|
|
148
|
+
outside(selector).filter(
|
|
149
|
+
(element) => !named.some((float) => float.anchorId === element.id),
|
|
150
|
+
).length;
|
|
151
|
+
|
|
152
|
+
const counts: HiddenContentCounts = {
|
|
153
|
+
// Only Markdown section headings — each sits in its own
|
|
154
|
+
// `.sl-heading-wrapper` — not a heading a component draws for itself
|
|
155
|
+
// (a lab's title, an assessment runner's, a `<Fold>`'s bar, which is out
|
|
156
|
+
// of the accessibility tree's headings with `role="none"`).
|
|
157
|
+
subsections: outside(headings).filter(
|
|
158
|
+
(heading) =>
|
|
159
|
+
hasAncestorWithin(heading, body, '.sl-heading-wrapper') &&
|
|
160
|
+
heading.getAttribute('role') !== 'none',
|
|
161
|
+
).length,
|
|
162
|
+
// A loose list's `<li><p>` is part of the list, not a paragraph of its own.
|
|
163
|
+
paragraphs: outside('p', 'ul, ol').length,
|
|
164
|
+
equations: outside('.katex-display').length,
|
|
165
|
+
figures: unnamed('.keyed-figure'),
|
|
166
|
+
// Keyed tables, plus any raw Markdown table (warned about, but possible).
|
|
167
|
+
tables: unnamed('.keyed-table') + outside('table').length,
|
|
168
|
+
diagrams: unnamed('.diagram'),
|
|
169
|
+
lists: outside('ul, ol', 'ul, ol').length,
|
|
170
|
+
codeBlocks: outside('pre').length,
|
|
171
|
+
memos: outside('.memo-block').length,
|
|
172
|
+
// A set of worked examples counts its examples; folded itself (the set
|
|
173
|
+
// is a section of its own, `CompactExamples.astro`), its body holds them
|
|
174
|
+
// directly.
|
|
175
|
+
examples:
|
|
176
|
+
itemsIn('.compact-examples', '.compact-example') +
|
|
177
|
+
outside('.compact-example').length,
|
|
178
|
+
questions: itemsIn('.assessment-shell', '.assessment-item'),
|
|
179
|
+
};
|
|
180
|
+
return { counts, named };
|
|
181
|
+
}
|
|
@@ -13,23 +13,40 @@
|
|
|
13
13
|
* `[[tab-<id>]]` (same page) or `[[lesson-slug#tab-<id>]]` (cross page); a
|
|
14
14
|
* table without `id` is still numbered and deep-linkable, just not
|
|
15
15
|
* referenceable by name — the same rule an unkeyed equation follows.
|
|
16
|
+
*
|
|
17
|
+
* Named by the caption pair every float takes
|
|
18
|
+
* (docs/plans/titles-and-containers.md Part 3): the long `caption`, in the
|
|
19
|
+
* shared caption line (`global.css` `.float-caption`) above the table, and an
|
|
20
|
+
* optional `short` caption that heads the `[[tab-…]]` preview card. Minimized,
|
|
21
|
+
* a table keeps that caption line and its header row (decision Q6).
|
|
16
22
|
*/
|
|
23
|
+
import { courseDisclosure } from '../disclosure/course-disclosure';
|
|
24
|
+
import { captionTitle } from '../float-name';
|
|
25
|
+
|
|
17
26
|
export interface Props {
|
|
18
27
|
/** Stable key, e.g. `id="kinematics-summary"`. Omit for a positional-only table. */
|
|
19
28
|
id?: string;
|
|
20
|
-
/**
|
|
29
|
+
/** The long caption, shown above the table (the LaTeX convention — the reverse of a figure caption, which goes below) and in the hover-preview card. */
|
|
21
30
|
caption: string;
|
|
31
|
+
/** Optional short caption: heads the `[[tab-…]]` preview card; rule S1 measures it. */
|
|
32
|
+
short?: string;
|
|
22
33
|
/** Computed by `remark-notation.mjs` — never author this directly. */
|
|
23
34
|
number?: string;
|
|
24
35
|
/** Computed by `remark-notation.mjs` — never author this directly. */
|
|
25
36
|
anchorId?: string;
|
|
26
37
|
}
|
|
27
38
|
|
|
28
|
-
const { id, caption, number, anchorId } = Astro.props;
|
|
39
|
+
const { id, caption, short, number, anchorId } = Astro.props;
|
|
40
|
+
// A table longer than this starts truncated (the course's
|
|
41
|
+
// `disclosure.tables.truncateAfter`, default 6).
|
|
42
|
+
const { truncateAfter } = (await courseDisclosure()).tables;
|
|
29
43
|
|
|
30
44
|
if (typeof caption !== 'string' || !caption.trim()) {
|
|
31
45
|
throw new Error('Table requires a non-empty `caption` prop.');
|
|
32
46
|
}
|
|
47
|
+
if (short !== undefined && (typeof short !== 'string' || !short.trim())) {
|
|
48
|
+
throw new Error('Table `short` must be a non-empty string when given.');
|
|
49
|
+
}
|
|
33
50
|
if (!number || !anchorId) {
|
|
34
51
|
throw new Error(
|
|
35
52
|
'Table is missing its computed `number`/`anchorId` — this page must run remark-notation with a `tables` cross-page table configured (see astro.config.mjs).',
|
|
@@ -42,14 +59,31 @@ if (!number || !anchorId) {
|
|
|
42
59
|
id={anchorId}
|
|
43
60
|
data-tab-number={number}
|
|
44
61
|
data-tab-key={id}
|
|
62
|
+
data-tab-short={short}
|
|
63
|
+
data-truncate-after={truncateAfter}
|
|
45
64
|
>
|
|
46
|
-
<figcaption class="keyed-table__caption">
|
|
65
|
+
<figcaption class="keyed-table__caption float-caption">
|
|
47
66
|
<a
|
|
48
|
-
class="keyed-table__number"
|
|
67
|
+
class="keyed-table__number float-caption__number"
|
|
49
68
|
href={`#${anchorId}`}
|
|
50
69
|
aria-label={`Table ${number}`}>Table {number}</a
|
|
51
70
|
>
|
|
52
|
-
<
|
|
71
|
+
<a
|
|
72
|
+
class="float-caption__anchor"
|
|
73
|
+
href={`#${anchorId}`}
|
|
74
|
+
aria-label={`Copy the link to Table ${number}`}
|
|
75
|
+
title="Copy link"
|
|
76
|
+
data-copy-link={`Table ${number}`}
|
|
77
|
+
>
|
|
78
|
+
<svg aria-hidden="true" width="16" height="16" viewBox="0 0 24 24" fill="currentColor"
|
|
79
|
+
><path
|
|
80
|
+
d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"
|
|
81
|
+
></path></svg
|
|
82
|
+
>
|
|
83
|
+
</a>
|
|
84
|
+
{short && <b class="float-caption__title">{captionTitle(short)}</b>}{short && ' '}<span
|
|
85
|
+
class="keyed-table__caption-text float-caption__text">{caption}</span
|
|
86
|
+
>
|
|
53
87
|
</figcaption>
|
|
54
88
|
<div class="keyed-table__body">
|
|
55
89
|
<slot />
|
|
@@ -76,45 +110,33 @@ if (!number || !anchorId) {
|
|
|
76
110
|
margin-inline: auto;
|
|
77
111
|
}
|
|
78
112
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/* Added by the row-pagination script below for every table (it always
|
|
87
|
-
gets a minimize-to-header-row toggle, regardless of row count) —
|
|
88
|
-
reserves room so caption text never runs under the absolutely-positioned
|
|
89
|
-
toggle button in the corner. */
|
|
90
|
-
.keyed-table__caption--paginated {
|
|
91
|
-
padding-inline-end: 1.75em;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/* The toggle button and everything else this rule block styles are
|
|
95
|
-
created at runtime (see the `<script>` below), so they never carry this
|
|
96
|
-
component's `data-astro-cid-*` scoping attribute — `:global()` is
|
|
97
|
-
required for the same reason `.keyed-table__body :global(table)` above
|
|
98
|
-
needs it. Reuses `.table-overflow-expand-btn` from
|
|
99
|
-
`TableOverflowExpander.astro` (Part 4) for its base look
|
|
100
|
-
(border/hover/focus/print-hidden), just repositioned into the caption's
|
|
101
|
-
top-right corner instead of a flex-row side column. */
|
|
102
|
-
:global(.keyed-table__pagination-toggle) {
|
|
103
|
-
position: absolute;
|
|
104
|
-
top: 0;
|
|
105
|
-
inset-inline-end: 0;
|
|
106
|
-
border: 1px solid var(--sl-color-gray-5);
|
|
107
|
-
border-radius: 0.3rem;
|
|
108
|
-
}
|
|
109
|
-
|
|
113
|
+
/* Everything this rule block styles is created at runtime (see the
|
|
114
|
+
`<script>` below), so it never carries this component's
|
|
115
|
+
`data-astro-cid-*` scoping attribute — `:global()` is required for the
|
|
116
|
+
same reason `.keyed-table__body :global(table)` above needs it. The fold
|
|
117
|
+
toggle is a `.float-tool` in the caption's button row (`global.css`). */
|
|
110
118
|
:global(.keyed-table__more-row td) {
|
|
111
119
|
padding-block: 0.4rem;
|
|
120
|
+
padding-inline: 0;
|
|
112
121
|
color: var(--sl-color-gray-3);
|
|
113
122
|
font-size: var(--sl-text-sm);
|
|
114
123
|
font-style: italic;
|
|
115
124
|
text-align: center;
|
|
116
125
|
}
|
|
117
126
|
|
|
127
|
+
/* Centred on the part of the table on screen: the cell spans every column,
|
|
128
|
+
so a wide table scrolled or clipped sideways would otherwise centre the
|
|
129
|
+
label on columns the reader cannot see. The label sticks to the scroll
|
|
130
|
+
clip's start edge and is as wide as the clip (`100cqi`, the clip being a
|
|
131
|
+
size container), or as the row when the whole table fits. */
|
|
132
|
+
:global(.keyed-table__more-label) {
|
|
133
|
+
position: sticky;
|
|
134
|
+
inset-inline-start: 0;
|
|
135
|
+
display: block;
|
|
136
|
+
width: 100cqi;
|
|
137
|
+
max-width: 100%;
|
|
138
|
+
}
|
|
139
|
+
|
|
118
140
|
/* Only the live table's own more-row gets this class — a hover-card
|
|
119
141
|
preview clone (`FloatRefLayer.astro`) strips it so the same row reads
|
|
120
142
|
as an inert truncation marker there instead of a dead-looking link. */
|
|
@@ -163,32 +185,7 @@ if (!number || !anchorId) {
|
|
|
163
185
|
margin-inline-start: 1.25rem;
|
|
164
186
|
}
|
|
165
187
|
|
|
166
|
-
.keyed-table__number {
|
|
167
|
-
font-weight: 600;
|
|
168
|
-
font-variant-numeric: tabular-nums;
|
|
169
|
-
color: var(--sl-color-text-accent);
|
|
170
|
-
text-decoration: none;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
.keyed-table__number:hover,
|
|
174
|
-
.keyed-table__number:focus-visible {
|
|
175
|
-
text-decoration: underline;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
.keyed-table__number:focus-visible {
|
|
179
|
-
outline: 2px solid var(--sl-color-accent);
|
|
180
|
-
outline-offset: 0.15rem;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
.keyed-table__caption-text::before {
|
|
184
|
-
content: ' — ';
|
|
185
|
-
}
|
|
186
|
-
|
|
187
188
|
@media print {
|
|
188
|
-
.keyed-table__number {
|
|
189
|
-
color: inherit;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
189
|
:global(.keyed-table__pagination-links),
|
|
193
190
|
:global(.keyed-table__more-row) {
|
|
194
191
|
display: none !important;
|
|
@@ -211,7 +208,7 @@ if (!number || !anchorId) {
|
|
|
211
208
|
* `truncated` (first/last few rows, the default once a table is long
|
|
212
209
|
* enough to bother) -> `expanded` (every row). Every table gets the
|
|
213
210
|
* minimize-to-header-row toggle in its caption, regardless of row count —
|
|
214
|
-
* a short table (`totalRows <=
|
|
211
|
+
* a short table (`totalRows <= truncateAfter`) just never grows the
|
|
215
212
|
* "… N more rows …" truncation row or the "Show less"/"Minimize" links
|
|
216
213
|
* below it, so it only ever has two reachable states, `minimized` and
|
|
217
214
|
* `expanded`. No persisted state here, same as `TableOverflowExpander.astro`'s
|
|
@@ -231,8 +228,6 @@ if (!number || !anchorId) {
|
|
|
231
228
|
|
|
232
229
|
type PaginationState = 'minimized' | 'truncated' | 'expanded';
|
|
233
230
|
|
|
234
|
-
const KEEP_EACH_END = 3;
|
|
235
|
-
const PAGINATE_ABOVE_ROWS = 6;
|
|
236
231
|
|
|
237
232
|
// Mirrors `TableOverflowExpander.astro`'s own `captionFor` exactly, for
|
|
238
233
|
// the same aria-label wording across both tables' controls.
|
|
@@ -245,6 +240,25 @@ if (!number || !anchorId) {
|
|
|
245
240
|
return text || 'this table';
|
|
246
241
|
}
|
|
247
242
|
|
|
243
|
+
// The fold toggle's chevron: ⌃ folds the rows away, ⌄ brings them back.
|
|
244
|
+
const FOLD_ICON = '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 15l6-6 6 6"/></svg>';
|
|
245
|
+
const UNFOLD_ICON = '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 9l6 6 6-6"/></svg>';
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* The caption's button row (`global.css` `.float-tools`), shared with
|
|
249
|
+
* `TableOverflowExpander.astro`'s widen button: whichever script runs
|
|
250
|
+
* first makes it. The widen button goes first, the fold toggle last.
|
|
251
|
+
*/
|
|
252
|
+
function floatTools(caption: HTMLElement): HTMLElement {
|
|
253
|
+
let tools = caption.querySelector<HTMLElement>(':scope > .float-tools');
|
|
254
|
+
if (!tools) {
|
|
255
|
+
tools = document.createElement('span');
|
|
256
|
+
tools.className = 'float-tools';
|
|
257
|
+
caption.append(tools);
|
|
258
|
+
}
|
|
259
|
+
return tools;
|
|
260
|
+
}
|
|
261
|
+
|
|
248
262
|
function wireTable(figure: HTMLElement) {
|
|
249
263
|
figure.dataset.paginationWired = 'true';
|
|
250
264
|
|
|
@@ -260,18 +274,20 @@ if (!number || !anchorId) {
|
|
|
260
274
|
if (totalRows === 0) return;
|
|
261
275
|
|
|
262
276
|
// Long enough to also need truncation, on top of the minimize toggle
|
|
263
|
-
// every table gets below
|
|
264
|
-
|
|
277
|
+
// every table gets below: more rows than the course's
|
|
278
|
+
// `disclosure.tables.truncateAfter`. A truncated table shows up to that
|
|
279
|
+
// many rows, half from each end.
|
|
280
|
+
const truncateAfter = Number(figure.dataset.truncateAfter) || 6;
|
|
281
|
+
const keepEachEnd = Math.floor(truncateAfter / 2);
|
|
282
|
+
const paginated = totalRows > truncateAfter;
|
|
265
283
|
|
|
266
284
|
const caption = figure.querySelector<HTMLElement>(
|
|
267
285
|
':scope > .keyed-table__caption',
|
|
268
286
|
);
|
|
269
|
-
caption?.classList.add('keyed-table__caption--paginated');
|
|
270
287
|
const toggle = document.createElement('button');
|
|
271
288
|
toggle.type = 'button';
|
|
272
|
-
toggle.className =
|
|
273
|
-
|
|
274
|
-
caption?.append(toggle);
|
|
289
|
+
toggle.className = 'float-tool keyed-table__pagination-toggle';
|
|
290
|
+
if (caption) floatTools(caption).append(toggle);
|
|
275
291
|
|
|
276
292
|
let moreRow: HTMLTableRowElement | null = null;
|
|
277
293
|
let links: HTMLElement | null = null;
|
|
@@ -279,7 +295,7 @@ if (!number || !anchorId) {
|
|
|
279
295
|
let minimizeAll: HTMLButtonElement | null = null;
|
|
280
296
|
|
|
281
297
|
if (paginated) {
|
|
282
|
-
const hiddenCount = totalRows -
|
|
298
|
+
const hiddenCount = totalRows - keepEachEnd * 2;
|
|
283
299
|
const headerCells = table.tHead?.rows[0]?.cells ?? rows[0].cells;
|
|
284
300
|
const colCount =
|
|
285
301
|
Array.from(headerCells).reduce(
|
|
@@ -302,10 +318,13 @@ if (!number || !anchorId) {
|
|
|
302
318
|
moreRow.setAttribute('aria-label', `Show all ${totalRows} rows`);
|
|
303
319
|
const moreCell = document.createElement('td');
|
|
304
320
|
moreCell.colSpan = colCount;
|
|
305
|
-
|
|
321
|
+
const moreLabel = document.createElement('span');
|
|
322
|
+
moreLabel.className = 'keyed-table__more-label';
|
|
323
|
+
moreLabel.textContent =
|
|
306
324
|
hiddenCount === 1 ? '… 1 more row …' : `… ${hiddenCount} more rows …`;
|
|
325
|
+
moreCell.append(moreLabel);
|
|
307
326
|
moreRow.append(moreCell);
|
|
308
|
-
tbody.insertBefore(moreRow, rows[
|
|
327
|
+
tbody.insertBefore(moreRow, rows[keepEachEnd]);
|
|
309
328
|
|
|
310
329
|
// Centered below the table body, and only ever shown once every row is
|
|
311
330
|
// — "Show less" un-truncates back down, "Minimize to header row"
|
|
@@ -336,13 +355,13 @@ if (!number || !anchorId) {
|
|
|
336
355
|
rows.forEach((row, index) => {
|
|
337
356
|
const inMiddle =
|
|
338
357
|
paginated &&
|
|
339
|
-
index >=
|
|
340
|
-
index < totalRows -
|
|
358
|
+
index >= keepEachEnd &&
|
|
359
|
+
index < totalRows - keepEachEnd;
|
|
341
360
|
row.hidden = minimized || (inMiddle && !expanded);
|
|
342
361
|
});
|
|
343
362
|
if (moreRow) moreRow.hidden = minimized || expanded;
|
|
344
363
|
|
|
345
|
-
toggle.
|
|
364
|
+
toggle.innerHTML = minimized ? UNFOLD_ICON : FOLD_ICON;
|
|
346
365
|
toggle.setAttribute('aria-expanded', String(!minimized));
|
|
347
366
|
toggle.setAttribute(
|
|
348
367
|
'aria-label',
|
|
@@ -95,15 +95,11 @@
|
|
|
95
95
|
* (`visibility: hidden`) outright — there is no reason to keep any part of
|
|
96
96
|
* it rendered once the reader has scrolled past it.
|
|
97
97
|
*
|
|
98
|
-
* Only one table is ever expanded at a time, and expanding one closes
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* those layers, the table doesn't stay registered as the arbiter's active
|
|
104
|
-
* surface while expanded, so a hover card opening *afterward* — over an
|
|
105
|
-
* explainable quantity inside the table itself, say — can't turn around and
|
|
106
|
-
* force the table closed the way claiming the arbiter normally would. The
|
|
98
|
+
* Only one table is ever expanded at a time, and expanding one closes the
|
|
99
|
+
* explanation cards only the pointer was keeping open
|
|
100
|
+
* (`closeTransientCards`, `../card-manager.ts`); pinned cards stay. A card
|
|
101
|
+
* opening *afterward* — over an explainable quantity inside the table
|
|
102
|
+
* itself, say — simply floats above the still-open table. The
|
|
107
103
|
* table closes only through its own explicit gestures — the collapse
|
|
108
104
|
* buttons or Escape — and deliberately *not* an outside click: a reader
|
|
109
105
|
* following a link in the "On this page" rail, or clicking anywhere else to
|
|
@@ -112,13 +108,18 @@
|
|
|
112
108
|
*
|
|
113
109
|
* No persisted state here (unlike Parts 1/3): expand/collapse is transient
|
|
114
110
|
* per-view UI, not a reader preference worth remembering across visits.
|
|
111
|
+
*
|
|
112
|
+
* **Caption button**: a keyed table also gets a widen button ↔ in its
|
|
113
|
+
* caption's button row at the top right (`global.css` `.float-tools`), next
|
|
114
|
+
* to the fold toggle — the same action as the right-edge column, found
|
|
115
|
+
* where a reader looks for a table's controls. It shows whenever that
|
|
116
|
+
* column does, and turns into →← while the table is widened.
|
|
115
117
|
*/
|
|
116
118
|
---
|
|
117
119
|
|
|
118
120
|
<script>
|
|
119
|
-
import {
|
|
121
|
+
import { closeTransientCards } from "../card-manager";
|
|
120
122
|
|
|
121
|
-
const GROUP = "table-overflow";
|
|
122
123
|
const EDGE_PX = 16; // ~1em at the default root font size
|
|
123
124
|
const WIDTH_BUFFER_PX = 2; // rounding slack so the minimal width never re-triggers a scrollbar
|
|
124
125
|
const SIDE_COLUMN_PX = 24; // must match `.table-overflow-expand-btn`'s `width: 1.5rem`
|
|
@@ -136,6 +137,28 @@
|
|
|
136
137
|
|
|
137
138
|
let active: Expansion | null = null;
|
|
138
139
|
|
|
140
|
+
/** Each keyed table's caption widen button, by its body (the container). */
|
|
141
|
+
const captionButtons = new WeakMap<HTMLElement, HTMLButtonElement>();
|
|
142
|
+
|
|
143
|
+
const WIDEN_ICON =
|
|
144
|
+
'<svg viewBox="0 0 24 24" aria-hidden="true">' +
|
|
145
|
+
'<path class="float-tool__widen" d="M3 12h18M8 7l-5 5 5 5M16 7l5 5-5 5"/>' +
|
|
146
|
+
'<path class="float-tool__narrow" d="M3 12h7M7 9l3 3-3 3M21 12h-7M17 9l-3 3 3 3"/></svg>';
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* The caption's button row, shared with `Table.astro`'s fold toggle
|
|
150
|
+
* (which has the same helper): whichever script runs first makes it.
|
|
151
|
+
*/
|
|
152
|
+
function floatTools(caption: HTMLElement): HTMLElement {
|
|
153
|
+
let tools = caption.querySelector<HTMLElement>(":scope > .float-tools");
|
|
154
|
+
if (!tools) {
|
|
155
|
+
tools = document.createElement("span");
|
|
156
|
+
tools.className = "float-tools";
|
|
157
|
+
caption.append(tools);
|
|
158
|
+
}
|
|
159
|
+
return tools;
|
|
160
|
+
}
|
|
161
|
+
|
|
139
162
|
function sidebarLeftBound(): number {
|
|
140
163
|
const sidebar = document.querySelector<HTMLElement>(".sidebar-pane");
|
|
141
164
|
const visible =
|
|
@@ -187,6 +210,12 @@
|
|
|
187
210
|
);
|
|
188
211
|
button.title = expanded ? "Collapse" : "Expand to full width";
|
|
189
212
|
button.setAttribute("aria-expanded", String(expanded));
|
|
213
|
+
const captionButton = captionButtons.get(container);
|
|
214
|
+
if (captionButton) {
|
|
215
|
+
captionButton.setAttribute("aria-label", button.getAttribute("aria-label") ?? "");
|
|
216
|
+
captionButton.title = button.title;
|
|
217
|
+
captionButton.setAttribute("aria-expanded", String(expanded));
|
|
218
|
+
}
|
|
190
219
|
}
|
|
191
220
|
|
|
192
221
|
// A scrollable region must itself be reachable by keyboard (WCAG 2.1.1 —
|
|
@@ -216,6 +245,8 @@
|
|
|
216
245
|
) {
|
|
217
246
|
if (active?.scroll === scroll) return; // never hide the one currently expanded
|
|
218
247
|
button.hidden = scroll.scrollWidth <= scroll.clientWidth + 1;
|
|
248
|
+
const captionButton = captionButtons.get(container);
|
|
249
|
+
if (captionButton) captionButton.hidden = button.hidden;
|
|
219
250
|
updateScrollFocusability(scroll, container);
|
|
220
251
|
}
|
|
221
252
|
|
|
@@ -250,20 +281,11 @@
|
|
|
250
281
|
) {
|
|
251
282
|
if (active) collapse();
|
|
252
283
|
|
|
253
|
-
//
|
|
254
|
-
//
|
|
255
|
-
//
|
|
256
|
-
//
|
|
257
|
-
|
|
258
|
-
// explainable quantity inside it, say) would claim the arbiter in turn
|
|
259
|
-
// and force this table closed the same way a genuinely different
|
|
260
|
-
// overlay replacing it would — which is exactly backwards: the card
|
|
261
|
-
// should simply float above the still-open table (it already
|
|
262
|
-
// out-ranks it on z-index), and the table should close only through
|
|
263
|
-
// its own explicit gestures (the collapse buttons, an outside click, or
|
|
264
|
-
// Escape — all wired below, independent of this arbiter).
|
|
265
|
-
claimActiveSurface(GROUP, () => {});
|
|
266
|
-
releaseActiveSurface(GROUP);
|
|
284
|
+
// A hovered card would linger over the expanded table; pinned ones stay.
|
|
285
|
+
// A card opened afterward over the table's own content floats above it
|
|
286
|
+
// (the card layer out-ranks it on z-index), and the table closes only
|
|
287
|
+
// through its own gestures (the collapse buttons or Escape, wired below).
|
|
288
|
+
closeTransientCards();
|
|
267
289
|
|
|
268
290
|
// Measured *before* anything moves: `scrollWidth` reports the table's
|
|
269
291
|
// full, unclipped content width regardless of how narrow this wrapper
|
|
@@ -438,13 +460,28 @@
|
|
|
438
460
|
button.type = "button";
|
|
439
461
|
button.className = "table-overflow-expand-btn";
|
|
440
462
|
button.hidden = true;
|
|
441
|
-
updateButtonLabel(container, button, false);
|
|
442
463
|
container.append(button);
|
|
443
464
|
|
|
444
|
-
|
|
465
|
+
const caption = container
|
|
466
|
+
.closest<HTMLElement>(".keyed-table")
|
|
467
|
+
?.querySelector<HTMLElement>(":scope > .keyed-table__caption");
|
|
468
|
+
if (caption) {
|
|
469
|
+
const captionButton = document.createElement("button");
|
|
470
|
+
captionButton.type = "button";
|
|
471
|
+
captionButton.className = "float-tool keyed-table__widen-toggle";
|
|
472
|
+
captionButton.hidden = true;
|
|
473
|
+
captionButton.innerHTML = WIDEN_ICON;
|
|
474
|
+
floatTools(caption).prepend(captionButton);
|
|
475
|
+
captionButtons.set(container, captionButton);
|
|
476
|
+
}
|
|
477
|
+
updateButtonLabel(container, button, false);
|
|
478
|
+
|
|
479
|
+
const toggle = () => {
|
|
445
480
|
if (active?.container === container) collapse();
|
|
446
481
|
else expand(container, button, scroll);
|
|
447
|
-
}
|
|
482
|
+
};
|
|
483
|
+
button.addEventListener("click", toggle);
|
|
484
|
+
captionButtons.get(container)?.addEventListener("click", toggle);
|
|
448
485
|
|
|
449
486
|
evaluateOverflow(scroll, button, container);
|
|
450
487
|
|