explico 0.8.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (230) hide show
  1. package/CHANGELOG.md +1239 -1
  2. package/README.md +60 -15
  3. package/dist/compiler/astro.d.ts +6 -1
  4. package/dist/compiler/cli.d.ts +46 -2
  5. package/dist/compiler/cli.js +70 -4
  6. package/dist/compiler/collections.d.ts +35 -2
  7. package/dist/compiler/collections.js +68 -5
  8. package/dist/compiler/course-config.d.ts +3 -194
  9. package/dist/compiler/course-config.js +78 -5
  10. package/dist/compiler/exports/standalone-html.d.ts +6 -1
  11. package/dist/compiler/knowledge-version.js +1 -1
  12. package/dist/compiler/lesson-derivation.d.ts +27 -10
  13. package/dist/compiler/lesson-derivation.js +23 -8
  14. package/dist/compiler/logging.d.ts +4 -1
  15. package/dist/compiler/logging.js +4 -1
  16. package/dist/compiler/manifest.d.ts +204 -31
  17. package/dist/compiler/manifest.js +270 -55
  18. package/dist/compiler/resolution-report.d.ts +6 -1
  19. package/dist/course-config-Dsnri9Tk.d.ts +464 -0
  20. package/dist/reference/base-accents.d.mts +26 -0
  21. package/dist/reference/base-accents.mjs +31 -0
  22. package/dist/reference/base-roman-labels.d.mts +2 -2
  23. package/dist/reference/base-roman-labels.mjs +2 -2
  24. package/dist/reference/consistency.d.ts +14 -2
  25. package/dist/reference/consistency.js +108 -4
  26. package/dist/reference/delimiter-size.d.ts +3 -2
  27. package/dist/reference/diagrams-validate.d.ts +7 -2
  28. package/dist/reference/diagrams-validate.js +9 -1
  29. package/dist/reference/equations-validate.d.ts +8 -5
  30. package/dist/reference/equations-validate.js +1 -0
  31. package/dist/reference/equations.d.mts +60 -31
  32. package/dist/reference/equations.mjs +83 -43
  33. package/dist/reference/examples-validate.d.ts +31 -0
  34. package/dist/reference/examples-validate.js +48 -0
  35. package/dist/reference/figures-validate.d.ts +1 -1
  36. package/dist/reference/figures-validate.js +4 -1
  37. package/dist/reference/folds-validate.d.ts +33 -0
  38. package/dist/reference/folds-validate.js +32 -0
  39. package/dist/reference/gate-abbreviations.d.ts +3 -1
  40. package/dist/reference/gate-abbreviations.js +1 -0
  41. package/dist/reference/gate-course-rules.d.ts +32 -0
  42. package/dist/reference/gate-course-rules.js +85 -0
  43. package/dist/reference/gate-heading-length.d.ts +5 -2
  44. package/dist/reference/gate-heading-length.js +45 -21
  45. package/dist/reference/gate-heading-structure.d.ts +41 -0
  46. package/dist/reference/gate-heading-structure.js +113 -0
  47. package/dist/reference/gate-math-punctuation.d.ts +71 -0
  48. package/dist/reference/gate-math-punctuation.js +251 -0
  49. package/dist/reference/gate-math.d.ts +22 -6
  50. package/dist/reference/gate-math.js +42 -8
  51. package/dist/reference/gate-part-pages.d.ts +50 -0
  52. package/dist/reference/gate-part-pages.js +118 -0
  53. package/dist/reference/gate-prose-notation.d.ts +21 -11
  54. package/dist/reference/gate-prose-notation.js +25 -7
  55. package/dist/reference/katex-delimiters.mjs +20 -7
  56. package/dist/reference/keyed-float-validate.d.ts +27 -10
  57. package/dist/reference/keyed-float-validate.js +15 -1
  58. package/dist/reference/keyed-jsx-floats.d.mts +11 -5
  59. package/dist/reference/keyed-jsx-floats.mjs +73 -20
  60. package/dist/reference/knowledge-checks-validate.d.ts +1 -1
  61. package/dist/reference/knowledge-checks.d.mts +3 -1
  62. package/dist/reference/knowledge-checks.mjs +9 -4
  63. package/dist/reference/lab-math-scope.d.ts +1 -0
  64. package/dist/reference/lab-math-slot.d.ts +34 -0
  65. package/dist/reference/lab-math-slot.js +6 -0
  66. package/dist/reference/math-glyphs.d.mts +80 -4
  67. package/dist/reference/math-glyphs.mjs +572 -37
  68. package/dist/reference/page-notation.d.ts +6 -1
  69. package/dist/reference/raw-floats-validate.js +1 -1
  70. package/dist/reference/raw-floats.mjs +1 -1
  71. package/dist/reference/rehype-heading-anchors.d.mts +14 -0
  72. package/dist/reference/rehype-heading-anchors.mjs +116 -17
  73. package/dist/reference/rehype-table-header-labels.d.mts +6 -1
  74. package/dist/reference/rehype-table-header-labels.mjs +21 -3
  75. package/dist/reference/remark-asides.d.mts +3 -0
  76. package/dist/reference/remark-asides.mjs +180 -0
  77. package/dist/reference/remark-memo.d.mts +3 -1
  78. package/dist/reference/remark-memo.mjs +42 -11
  79. package/dist/reference/remark-notation.d.mts +60 -10
  80. package/dist/reference/remark-notation.mjs +387 -36
  81. package/dist/reference/render-lab-math.d.ts +4 -26
  82. package/dist/reference/render-lab-math.js +4 -4
  83. package/dist/reference/sections-validate.d.ts +27 -0
  84. package/dist/reference/sections-validate.js +41 -0
  85. package/dist/reference/sections.d.mts +97 -0
  86. package/dist/reference/sections.mjs +306 -0
  87. package/dist/reference/tables-validate.d.ts +1 -1
  88. package/dist/reference/types.d.ts +17 -1
  89. package/dist/reference/unit-words.d.ts +37 -0
  90. package/dist/reference/unit-words.js +170 -0
  91. package/dist/rules/catalog.d.ts +176 -0
  92. package/dist/rules/catalog.js +983 -0
  93. package/dist/rules/course-rules.d.ts +115 -0
  94. package/dist/rules/course-rules.js +292 -0
  95. package/dist/rules/course-rules.schema.json +138 -0
  96. package/dist/rules/in-effect.d.ts +53 -0
  97. package/dist/rules/in-effect.js +142 -0
  98. package/dist/rules/rules.schema.json +1072 -0
  99. package/dist/rules/settings.d.ts +3 -0
  100. package/dist/rules/settings.js +290 -0
  101. package/dist/rules/waivers.d.ts +53 -0
  102. package/dist/rules/waivers.js +132 -0
  103. package/dist/session/PreferenceStore.d.ts +5 -0
  104. package/dist/session/PreferenceStore.js +9 -0
  105. package/docs/VERSIONING.md +2 -2
  106. package/docs/authoring/README.md +232 -40
  107. package/docs/authoring/agents.md +55 -9
  108. package/docs/authoring/components.md +178 -22
  109. package/docs/authoring/migrating.md +1096 -2
  110. package/docs/authoring/notation.md +576 -322
  111. package/docs/authoring/prose.md +551 -0
  112. package/docs/authoring/retired.md +35 -0
  113. package/docs/authoring/structure-rules.md +422 -40
  114. package/docs/authoring/structure.md +445 -62
  115. package/docs/authoring/versioning.md +20 -28
  116. package/docs/authoring/visualizations.md +142 -20
  117. package/package.json +2 -2
  118. package/src/compiler/cli.ts +116 -6
  119. package/src/compiler/collections.ts +111 -7
  120. package/src/compiler/course-config.ts +273 -34
  121. package/src/compiler/knowledge-version.ts +1 -1
  122. package/src/compiler/lesson-derivation.ts +54 -15
  123. package/src/compiler/logging.ts +8 -2
  124. package/src/compiler/manifest.ts +573 -104
  125. package/src/components/BlockHeading.astro +113 -0
  126. package/src/components/CourseContents.astro +221 -0
  127. package/src/components/CurriculumMap.astro +5 -6
  128. package/src/components/FloatIndex.astro +200 -0
  129. package/src/components/HoverPanel.astro +431 -79
  130. package/src/components/PartBridge.astro +27 -0
  131. package/src/components/PartOverview.astro +401 -0
  132. package/src/components/PartScope.astro +27 -0
  133. package/src/components/abbreviation/AbbreviationLayer.astro +49 -51
  134. package/src/components/assessments/AssessmentRunner.tsx +8 -5
  135. package/src/components/assessments/AssessmentSet.astro +23 -26
  136. package/src/components/block-heading.ts +146 -0
  137. package/src/components/card-arrow.ts +243 -0
  138. package/src/components/card-drag.ts +631 -0
  139. package/src/components/card-jump.ts +45 -0
  140. package/src/components/card-manager.ts +1589 -0
  141. package/src/components/card-memory.ts +177 -0
  142. package/src/components/card-placement.ts +504 -0
  143. package/src/components/card-side.ts +336 -0
  144. package/src/components/card-travel.ts +197 -0
  145. package/src/components/citation/CitationLayer.astro +77 -76
  146. package/src/components/diagram/Diagram.astro +161 -316
  147. package/src/components/disclosure/course-disclosure.ts +24 -0
  148. package/src/components/disclosure/hover-preview.ts +122 -42
  149. package/src/components/disclosure/persisted-disclosure.ts +3 -3
  150. package/src/components/disclosure/print-as-shown.ts +252 -0
  151. package/src/components/examples/CompactExample.astro +3 -1
  152. package/src/components/examples/CompactExamples.astro +101 -94
  153. package/src/components/figure/Figure.astro +174 -274
  154. package/src/components/figure/FloatCaption.astro +87 -0
  155. package/src/components/figure/figure-state.ts +58 -0
  156. package/src/components/figure/figure-tools.ts +87 -0
  157. package/src/components/float-name.ts +101 -0
  158. package/src/components/fold/Fold.astro +125 -0
  159. package/src/components/fold/fold-id.ts +40 -0
  160. package/src/components/memo/Memo.astro +162 -231
  161. package/src/components/memo/ShortNote.astro +6 -6
  162. package/src/components/notation/NotationFormula.astro +5 -1
  163. package/src/components/notation/NotationGlossary.astro +3 -4
  164. package/src/components/notation/NotationLayer.astro +169 -162
  165. package/src/components/notation/NotationMath.astro +10 -1
  166. package/src/components/reference/FloatRefLayer.astro +485 -423
  167. package/src/components/starlight/AboutPanel.astro +5 -4
  168. package/src/components/starlight/CollapsibleSections.astro +207 -93
  169. package/src/components/starlight/DiagramEnhancer.astro +15 -138
  170. package/src/components/starlight/HeadingAnchorEnhancer.astro +193 -1
  171. package/src/components/starlight/KeyedFloatEnhancer.astro +100 -9
  172. package/src/components/starlight/LayoutHeader.astro +298 -19
  173. package/src/components/starlight/LayoutPageSidebar.astro +99 -12
  174. package/src/components/starlight/LayoutPageTitle.astro +120 -0
  175. package/src/components/starlight/LayoutSidebar.astro +229 -11
  176. package/src/components/starlight/LessonExportActions.astro +53 -72
  177. package/src/components/starlight/LessonFooter.astro +26 -4
  178. package/src/components/starlight/section-parts.ts +15 -2
  179. package/src/components/starlight/section-summary.ts +181 -0
  180. package/src/components/table/Table.astro +95 -76
  181. package/src/components/table/TableOverflowExpander.astro +65 -28
  182. package/src/content-config.ts +40 -4
  183. package/src/reference/base-accents.mjs +31 -0
  184. package/src/reference/base-roman-labels.mjs +2 -2
  185. package/src/reference/consistency.ts +176 -5
  186. package/src/reference/delimiter-size.ts +3 -2
  187. package/src/reference/diagrams-validate.ts +17 -1
  188. package/src/reference/equations-validate.ts +10 -5
  189. package/src/reference/equations.mjs +83 -43
  190. package/src/reference/examples-validate.ts +97 -0
  191. package/src/reference/figures-validate.ts +6 -1
  192. package/src/reference/folds-validate.ts +65 -0
  193. package/src/reference/gate-abbreviations.ts +1 -0
  194. package/src/reference/gate-course-rules.ts +134 -0
  195. package/src/reference/gate-heading-length.ts +66 -35
  196. package/src/reference/gate-heading-structure.ts +193 -0
  197. package/src/reference/gate-math-punctuation.ts +381 -0
  198. package/src/reference/gate-math.ts +69 -10
  199. package/src/reference/gate-part-pages.ts +217 -0
  200. package/src/reference/gate-prose-notation.ts +59 -12
  201. package/src/reference/katex-delimiters.mjs +20 -7
  202. package/src/reference/keyed-float-validate.ts +45 -11
  203. package/src/reference/keyed-jsx-floats.mjs +73 -20
  204. package/src/reference/knowledge-checks-validate.ts +1 -1
  205. package/src/reference/knowledge-checks.mjs +9 -4
  206. package/src/reference/lab-math-slot.ts +36 -0
  207. package/src/reference/math-glyphs.mjs +572 -37
  208. package/src/reference/raw-floats-validate.ts +1 -1
  209. package/src/reference/raw-floats.mjs +1 -1
  210. package/src/reference/rehype-heading-anchors.mjs +116 -17
  211. package/src/reference/rehype-table-header-labels.mjs +21 -3
  212. package/src/reference/remark-asides.mjs +180 -0
  213. package/src/reference/remark-memo.mjs +42 -11
  214. package/src/reference/remark-notation.mjs +387 -36
  215. package/src/reference/render-lab-math.ts +16 -31
  216. package/src/reference/sections-validate.ts +81 -0
  217. package/src/reference/sections.mjs +306 -0
  218. package/src/reference/tables-validate.ts +2 -1
  219. package/src/reference/types.ts +17 -0
  220. package/src/reference/unit-words.ts +233 -0
  221. package/src/rules/catalog.ts +1298 -0
  222. package/src/rules/course-rules.ts +456 -0
  223. package/src/rules/in-effect.ts +235 -0
  224. package/src/rules/settings.ts +448 -0
  225. package/src/rules/waivers.ts +249 -0
  226. package/src/session/PreferenceStore.ts +15 -0
  227. package/src/styles/global.css +1471 -163
  228. package/src/styles/surfaces.css +182 -0
  229. package/src/components/hover-panel.ts +0 -339
  230. package/src/components/popup-stack.ts +0 -345
@@ -1,345 +0,0 @@
1
- /**
2
- * Multi-popup engine for the notation layer (see `NotationLayer.astro`).
3
- *
4
- * Unlike the single shared hover panel (`./hover-panel.ts`, used by the
5
- * citation layer, which never nests), notation cards nest: a card's formula
6
- * can itself carry live symbols (D15), and pointing at one is the reader
7
- * exploring outward, not navigating away — replacing the card they were just
8
- * reading with a different one is the wrong response to that. So this module
9
- * keeps a small stack of independent popup elements instead of one shared
10
- * panel: each key gets at most one open popup, opening a symbol found inside
11
- * an existing popup adds another popup beside it, closing a popup also
12
- * closes whatever was opened from inside it (nothing is left pointing at a
13
- * symbol that no longer exists), and every popup positions itself clear of
14
- * its trigger and of every other currently open popup.
15
- *
16
- * Because each key's popup is a real, persistent DOM node — never replaced
17
- * in place — this sidesteps the reentrancy problem an earlier "replace the
18
- * panel's content" design ran into: a `:hover` check made once the dust
19
- * settles is always accurate, there is no synthetic event fired by a node
20
- * removing itself out from under the pointer.
21
- */
22
- import { claimActiveSurface, releaseActiveSurface } from './hover-panel';
23
-
24
- export interface PopupStackConfig {
25
- /** Where popup elements are appended. They are `position: fixed`, so the
26
- * parent's place in the document doesn't matter. */
27
- container: HTMLElement;
28
- /** Exclusivity group against other surfaces (e.g. the citation panel). */
29
- groupId: string;
30
- /**
31
- * Nearest interactive trigger for an event target — must search the live
32
- * document *and* the content of every currently open popup.
33
- */
34
- triggerFrom: (node: EventTarget | null) => HTMLElement | null;
35
- /** Stable key identifying what a trigger points at. */
36
- keyFor: (trigger: HTMLElement) => string | null;
37
- /** A pin toggle outside the popups themselves (e.g. a list button). */
38
- pinControlFrom?: (node: EventTarget | null) => HTMLElement | null;
39
- /**
40
- * Build a fresh, unattached popup element for `key`, opened from `trigger`.
41
- * Return `null` to abort the open (unknown key).
42
- */
43
- createPopup: (key: string, trigger: HTMLElement) => HTMLElement | null;
44
- /** Called after any popup opens, closes, or changes pin state. */
45
- onChange?: (open: ReadonlySet<string>, pinned: ReadonlySet<string>) => void;
46
- }
47
-
48
- interface PopupRecord {
49
- key: string;
50
- el: HTMLElement;
51
- trigger: HTMLElement;
52
- parentKey: string | null;
53
- pinned: boolean;
54
- leaveTimer: number | null;
55
- }
56
-
57
- function closestElement(node: EventTarget | null, selector: string) {
58
- return node instanceof Element ? node.closest<HTMLElement>(selector) : null;
59
- }
60
-
61
- export function createPopupStack(config: PopupStackConfig) {
62
- const { container } = config;
63
- const popups = new Map<string, PopupRecord>();
64
- const order: string[] = []; // open order, oldest first — Escape closes the last.
65
-
66
- const pinControlFrom = (node: EventTarget | null) =>
67
- config.pinControlFrom?.(node) ?? null;
68
-
69
- const notify = () => {
70
- config.onChange?.(
71
- new Set(popups.keys()),
72
- new Set([...popups.values()].filter((p) => p.pinned).map((p) => p.key)),
73
- );
74
- };
75
-
76
- const parentOf = (trigger: HTMLElement): string | null => {
77
- const host = closestElement(trigger, '[data-popup-key]');
78
- const key = host?.getAttribute('data-popup-key') ?? null;
79
- return key && popups.has(key) ? key : null;
80
- };
81
-
82
- function position(record: PopupRecord) {
83
- const edge = 12;
84
- const gap = 10;
85
- const { el, trigger } = record;
86
- const displayEquation = trigger.closest<HTMLElement>('.katex-display');
87
- const safeRegion = displayEquation ?? trigger;
88
- const regionRect = safeRegion.getBoundingClientRect();
89
- const anchorRect = trigger.getBoundingClientRect();
90
-
91
- const headerBottom =
92
- document.querySelector<HTMLElement>('.header')?.getBoundingClientRect()
93
- .bottom ?? 0;
94
- const verticalStart = Math.max(edge, headerBottom + edge);
95
- const verticalEnd = window.innerHeight - edge;
96
- const spaceAbove = regionRect.top - gap - verticalStart;
97
- const spaceBelow = verticalEnd - regionRect.bottom - gap;
98
-
99
- el.style.maxHeight = `${Math.max(spaceAbove, spaceBelow, 0)}px`;
100
- let rect = el.getBoundingClientRect();
101
- const placeBelow = spaceBelow >= rect.height || spaceBelow >= spaceAbove;
102
- el.style.maxHeight = `${Math.max(placeBelow ? spaceBelow : spaceAbove, 0)}px`;
103
- rect = el.getBoundingClientRect();
104
-
105
- let top = placeBelow
106
- ? regionRect.bottom + gap
107
- : regionRect.top - gap - rect.height;
108
- let left = anchorRect.left + anchorRect.width / 2 - rect.width / 2;
109
-
110
- const article = document.querySelector<HTMLElement>('.sl-markdown-content');
111
- const articleRect = article?.getBoundingClientRect();
112
- const horizontalStart = Math.max(edge, articleRect?.left ?? edge);
113
- const horizontalEnd = Math.min(
114
- window.innerWidth - edge,
115
- articleRect?.right ?? window.innerWidth - edge,
116
- );
117
-
118
- top = Math.max(verticalStart, Math.min(top, verticalEnd - rect.height));
119
- left =
120
- horizontalEnd - horizontalStart >= rect.width
121
- ? Math.max(horizontalStart, Math.min(left, horizontalEnd - rect.width))
122
- : Math.max(edge, Math.min(left, window.innerWidth - rect.width - edge));
123
-
124
- // Clear of every other open popup: cascade below whichever one this
125
- // still overlaps, then re-clamp to the viewport. A handful of popups
126
- // open at once is the realistic ceiling here, so a bounded scan is
127
- // plenty — this does not need to be a general bin-packer.
128
- const others = [...popups.values()]
129
- .filter((other) => other.key !== record.key)
130
- .map((other) => other.el.getBoundingClientRect());
131
- for (let attempt = 0; attempt < others.length + 4; attempt++) {
132
- const box = {
133
- top,
134
- left,
135
- right: left + rect.width,
136
- bottom: top + rect.height,
137
- };
138
- const hit = others.find(
139
- (o) =>
140
- box.left < o.right &&
141
- box.right > o.left &&
142
- box.top < o.bottom &&
143
- box.bottom > o.top,
144
- );
145
- if (!hit) break;
146
- top = hit.bottom + gap;
147
- if (top + rect.height > verticalEnd) {
148
- top = verticalStart;
149
- left += rect.width * 0.35 + gap;
150
- }
151
- }
152
- top = Math.max(verticalStart, Math.min(top, verticalEnd - rect.height));
153
- left = Math.max(
154
- edge,
155
- Math.min(left, window.innerWidth - rect.width - edge),
156
- );
157
-
158
- el.style.top = `${Math.round(top)}px`;
159
- el.style.left = `${Math.round(left)}px`;
160
- }
161
-
162
- function scheduleLeaveCheck(record: PopupRecord) {
163
- if (record.pinned) return;
164
- if (record.leaveTimer !== null) {
165
- window.cancelAnimationFrame(record.leaveTimer);
166
- }
167
- record.leaveTimer = window.requestAnimationFrame(() => {
168
- record.leaveTimer = null;
169
- if (!popups.has(record.key) || record.pinned) return;
170
- const staysOpen =
171
- record.trigger.matches(':hover') ||
172
- record.trigger === document.activeElement ||
173
- record.el.matches(':hover') ||
174
- record.el.contains(document.activeElement) ||
175
- // A hovered or pinned CHILD keeps its parent alive too — moving from
176
- // the trigger straight into a child popup (a real, legitimate path;
177
- // the child is positioned right next to it) must not close this one
178
- // out from under the reader mid-move.
179
- [...popups.values()].some((other) => other.parentKey === record.key);
180
- if (staysOpen) return;
181
- closePopup(record.key);
182
- });
183
- }
184
-
185
- function closePopup(key: string) {
186
- const record = popups.get(key);
187
- if (!record) return;
188
- // Children first — a card opened from inside this one has nothing left
189
- // to anchor to once it's gone.
190
- for (const other of [...popups.values()]) {
191
- if (other.parentKey === key) closePopup(other.key);
192
- }
193
- if (record.leaveTimer !== null) {
194
- window.cancelAnimationFrame(record.leaveTimer);
195
- }
196
- popups.delete(key);
197
- const at = order.indexOf(key);
198
- if (at !== -1) order.splice(at, 1);
199
- record.el.remove();
200
- if (popups.size === 0) releaseActiveSurface(config.groupId);
201
- notify();
202
- }
203
-
204
- function closeAll() {
205
- for (const key of [...popups.keys()]) closePopup(key);
206
- }
207
-
208
- function openPopup(key: string, trigger: HTMLElement, pin: boolean): void {
209
- const existing = popups.get(key);
210
- if (existing) {
211
- if (pin && !existing.pinned) {
212
- existing.pinned = true;
213
- notify();
214
- }
215
- return;
216
- }
217
-
218
- const el = config.createPopup(key, trigger);
219
- if (!el) return;
220
-
221
- if (popups.size === 0) claimActiveSurface(config.groupId, closeAll);
222
-
223
- el.dataset.popupKey = key;
224
- el.style.position = 'fixed';
225
- el.style.visibility = 'hidden';
226
- el.hidden = false;
227
- container.appendChild(el);
228
-
229
- const record: PopupRecord = {
230
- key,
231
- el,
232
- trigger,
233
- parentKey: parentOf(trigger),
234
- pinned: pin,
235
- leaveTimer: null,
236
- };
237
- popups.set(key, record);
238
- order.push(key);
239
-
240
- position(record);
241
- el.style.visibility = 'visible';
242
-
243
- el.addEventListener('pointerleave', () => scheduleLeaveCheck(record));
244
- el.addEventListener('focusout', () => scheduleLeaveCheck(record));
245
-
246
- notify();
247
- }
248
-
249
- document.addEventListener('pointerover', (event) => {
250
- if (event.pointerType === 'touch') return;
251
- const trigger = config.triggerFrom(event.target);
252
- if (!trigger || trigger === config.triggerFrom(event.relatedTarget)) {
253
- return;
254
- }
255
- const key = config.keyFor(trigger);
256
- if (!key) return;
257
- openPopup(key, trigger, false);
258
- });
259
-
260
- document.addEventListener('pointerout', (event) => {
261
- if (event.pointerType === 'touch') return;
262
- const trigger = config.triggerFrom(event.target);
263
- if (!trigger || trigger === config.triggerFrom(event.relatedTarget)) {
264
- return;
265
- }
266
- const key = config.keyFor(trigger);
267
- const record = key ? popups.get(key) : undefined;
268
- if (record) scheduleLeaveCheck(record);
269
- });
270
-
271
- document.addEventListener('focusin', (event) => {
272
- const trigger = config.triggerFrom(event.target);
273
- const key = trigger ? config.keyFor(trigger) : null;
274
- if (trigger && key) openPopup(key, trigger, false);
275
- });
276
-
277
- document.addEventListener('focusout', (event) => {
278
- const trigger = config.triggerFrom(event.target);
279
- const key = trigger ? config.keyFor(trigger) : null;
280
- const record = key ? popups.get(key) : undefined;
281
- if (record) scheduleLeaveCheck(record);
282
- });
283
-
284
- document.addEventListener('click', (event) => {
285
- const control = pinControlFrom(event.target);
286
- if (control) {
287
- const ownKey = config.keyFor(control);
288
- if (ownKey) openPopup(ownKey, control, true);
289
- return;
290
- }
291
-
292
- const trigger = config.triggerFrom(event.target);
293
- if (trigger && !trigger.closest('a')) {
294
- const key = config.keyFor(trigger);
295
- if (key) {
296
- event.preventDefault();
297
- if (popups.get(key)?.pinned) {
298
- closePopup(key);
299
- } else {
300
- openPopup(key, trigger, true);
301
- }
302
- return;
303
- }
304
- }
305
- if (trigger) return; // a real link trigger: let it navigate.
306
-
307
- // Anywhere else inside an open popup collapses just that one — its body
308
- // is never a trigger, so a click there can only mean "I'm done with
309
- // this." (The corner "×" falls under this same rule.) A real link
310
- // inside it — "Open the full definition" — is excluded so it navigates.
311
- const host = closestElement(event.target, '[data-popup-key]');
312
- const hostKey = host?.getAttribute('data-popup-key');
313
- if (
314
- hostKey &&
315
- event.target instanceof Node &&
316
- !closestElement(event.target, 'a')
317
- ) {
318
- closePopup(hostKey);
319
- }
320
- });
321
-
322
- // Deliberately no "click outside closes everything": the reader explicitly
323
- // asked that clicking the equation itself — the unexplained parts included
324
- // — never collapses a card. Closing is always a targeted gesture: the "×",
325
- // a click on a card's own body, Escape, or (for a card that was only ever
326
- // hovered, never pinned) the pointer simply leaving.
327
-
328
- document.addEventListener('keydown', (event) => {
329
- if (event.key !== 'Escape' || order.length === 0) return;
330
- closePopup(order[order.length - 1]);
331
- });
332
-
333
- const repositionAll = () => {
334
- for (const record of popups.values()) position(record);
335
- };
336
- window.addEventListener('resize', repositionAll, { passive: true });
337
- window.addEventListener('scroll', repositionAll, {
338
- passive: true,
339
- capture: true,
340
- });
341
-
342
- return { closeAll };
343
- }
344
-
345
- export { closestElement };