explico 0.1.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 (113) hide show
  1. package/README.md +108 -0
  2. package/dist/analytics/AnalyticsEmitter.d.ts +22 -0
  3. package/dist/analytics/AnalyticsEmitter.js +5 -0
  4. package/dist/compiler/cli.d.ts +189 -0
  5. package/dist/compiler/cli.js +552 -0
  6. package/dist/compiler/collections.d.ts +70 -0
  7. package/dist/compiler/collections.js +383 -0
  8. package/dist/compiler/course-config.d.ts +31 -0
  9. package/dist/compiler/course-config.js +0 -0
  10. package/dist/compiler/lesson-derivation.d.ts +89 -0
  11. package/dist/compiler/lesson-derivation.js +91 -0
  12. package/dist/compiler/manifest.d.ts +178 -0
  13. package/dist/compiler/manifest.js +345 -0
  14. package/dist/compiler/resolution-report.d.ts +45 -0
  15. package/dist/compiler/resolution-report.js +97 -0
  16. package/dist/curriculum/validation.d.ts +76 -0
  17. package/dist/curriculum/validation.js +320 -0
  18. package/dist/progress/ProgressRepository.d.ts +53 -0
  19. package/dist/progress/ProgressRepository.js +12 -0
  20. package/dist/reference/base-library.mjs +27 -0
  21. package/dist/reference/citation-format.mjs +133 -0
  22. package/dist/reference/consistency.d.ts +64 -0
  23. package/dist/reference/consistency.js +234 -0
  24. package/dist/reference/curriculum-alignment.d.ts +7 -0
  25. package/dist/reference/curriculum-alignment.js +166 -0
  26. package/dist/reference/equations-validate.d.ts +50 -0
  27. package/dist/reference/equations-validate.js +90 -0
  28. package/dist/reference/equations.mjs +144 -0
  29. package/dist/reference/gate-math.d.ts +41 -0
  30. package/dist/reference/gate-math.js +151 -0
  31. package/dist/reference/katex-options.mjs +91 -0
  32. package/dist/reference/lab-math-scope.d.ts +21 -0
  33. package/dist/reference/lab-math-scope.js +28 -0
  34. package/dist/reference/label.d.ts +11 -0
  35. package/dist/reference/label.js +38 -0
  36. package/dist/reference/lint-ignore.d.ts +32 -0
  37. package/dist/reference/lint-ignore.js +39 -0
  38. package/dist/reference/math-glyphs.mjs +830 -0
  39. package/dist/reference/prose.d.ts +15 -0
  40. package/dist/reference/prose.js +12 -0
  41. package/dist/reference/references.d.ts +17 -0
  42. package/dist/reference/references.js +42 -0
  43. package/dist/reference/registry.d.ts +6 -0
  44. package/dist/reference/registry.js +548 -0
  45. package/dist/reference/rehype-fail-katex-errors.mjs +55 -0
  46. package/dist/reference/remark-citation.mjs +0 -0
  47. package/dist/reference/remark-notation.mjs +753 -0
  48. package/dist/reference/render-lab-math.d.ts +21 -0
  49. package/dist/reference/render-lab-math.js +58 -0
  50. package/dist/reference/types.d.ts +151 -0
  51. package/dist/reference/types.js +0 -0
  52. package/dist/reference/units-vocab.d.ts +26 -0
  53. package/dist/reference/units-vocab.js +119 -0
  54. package/dist/session/PreferenceStore.d.ts +28 -0
  55. package/dist/session/PreferenceStore.js +30 -0
  56. package/dist/session/user-context.d.ts +7 -0
  57. package/dist/session/user-context.js +10 -0
  58. package/dist/session/user.d.ts +21 -0
  59. package/dist/session/user.js +8 -0
  60. package/package.json +53 -0
  61. package/src/analytics/AnalyticsEmitter.ts +23 -0
  62. package/src/compiler/cli.ts +866 -0
  63. package/src/compiler/collections.ts +554 -0
  64. package/src/compiler/course-config.ts +32 -0
  65. package/src/compiler/lesson-derivation.ts +186 -0
  66. package/src/compiler/manifest.ts +601 -0
  67. package/src/compiler/resolution-report.ts +150 -0
  68. package/src/components/CurriculumMap.astro +208 -0
  69. package/src/components/HoverPanel.astro +67 -0
  70. package/src/components/assessments/AssessmentRunner.tsx +320 -0
  71. package/src/components/assessments/AssessmentSet.astro +54 -0
  72. package/src/components/citation/CitationLayer.astro +173 -0
  73. package/src/components/examples/CompactExample.astro +34 -0
  74. package/src/components/examples/CompactExamples.astro +253 -0
  75. package/src/components/hover-panel.ts +273 -0
  76. package/src/components/notation/NotationGlossary.astro +412 -0
  77. package/src/components/notation/NotationLayer.astro +594 -0
  78. package/src/components/notation/NotationMath.astro +24 -0
  79. package/src/components/notation/SafeMathNode.astro +23 -0
  80. package/src/components/notation/render-notation-math.ts +223 -0
  81. package/src/components/starlight/EquationEnhancer.astro +72 -0
  82. package/src/components/starlight/LayoutHeader.astro +102 -0
  83. package/src/components/starlight/LayoutPageSidebar.astro +33 -0
  84. package/src/components/starlight/LayoutSidebar.astro +29 -0
  85. package/src/components/starlight/LessonFooter.astro +58 -0
  86. package/src/content-config.ts +259 -0
  87. package/src/curriculum/validation.ts +481 -0
  88. package/src/progress/ProgressRepository.ts +64 -0
  89. package/src/reference/base-library.mjs +27 -0
  90. package/src/reference/citation-format.mjs +133 -0
  91. package/src/reference/consistency.ts +375 -0
  92. package/src/reference/curriculum-alignment.ts +228 -0
  93. package/src/reference/equations-validate.ts +167 -0
  94. package/src/reference/equations.mjs +144 -0
  95. package/src/reference/gate-math.ts +250 -0
  96. package/src/reference/katex-options.mjs +91 -0
  97. package/src/reference/lab-math-scope.ts +54 -0
  98. package/src/reference/label.ts +47 -0
  99. package/src/reference/lint-ignore.ts +75 -0
  100. package/src/reference/math-glyphs.mjs +830 -0
  101. package/src/reference/prose.ts +21 -0
  102. package/src/reference/references.ts +82 -0
  103. package/src/reference/registry.ts +723 -0
  104. package/src/reference/rehype-fail-katex-errors.mjs +55 -0
  105. package/src/reference/remark-citation.mjs +0 -0
  106. package/src/reference/remark-notation.mjs +753 -0
  107. package/src/reference/render-lab-math.ts +99 -0
  108. package/src/reference/types.ts +182 -0
  109. package/src/reference/units-vocab.ts +143 -0
  110. package/src/session/PreferenceStore.ts +54 -0
  111. package/src/session/user-context.ts +18 -0
  112. package/src/session/user.ts +24 -0
  113. package/src/styles/global.css +763 -0
@@ -0,0 +1,253 @@
1
+ ---
2
+ interface Props {
3
+ /** Text shown on the native disclosure while the examples are collapsed. */
4
+ summary?: string;
5
+ /** Optional one-line cue below the disclosure title. */
6
+ hint?: string;
7
+ /** Accessible name for the tab list created by progressive enhancement. */
8
+ tabsLabel?: string;
9
+ }
10
+
11
+ const {
12
+ summary = 'Worked examples',
13
+ hint = 'Open and choose an example.',
14
+ tabsLabel = summary,
15
+ } = Astro.props;
16
+ ---
17
+
18
+ <compact-examples data-tabs-label={tabsLabel}>
19
+ <details class="compact-examples">
20
+ <summary class="compact-examples__summary">
21
+ <span class="compact-examples__title">{summary}</span>
22
+ {hint && <span class="compact-examples__hint">{hint}</span>}
23
+ </summary>
24
+ <div class="compact-examples__body">
25
+ <slot />
26
+ </div>
27
+ </details>
28
+ </compact-examples>
29
+
30
+ <style>
31
+ compact-examples {
32
+ display: block;
33
+ margin-block: 1.5rem;
34
+ }
35
+
36
+ .compact-examples {
37
+ overflow: clip;
38
+ border: 1px solid var(--sl-color-gray-5);
39
+ border-radius: 0.75rem;
40
+ background: var(--sl-color-gray-7);
41
+ }
42
+
43
+ .compact-examples__summary {
44
+ padding: 0.8rem 1rem;
45
+ cursor: pointer;
46
+ }
47
+
48
+ .compact-examples__summary::marker {
49
+ color: var(--sl-color-text-accent);
50
+ }
51
+
52
+ .compact-examples__summary:focus-visible {
53
+ outline: 3px solid var(--sl-color-accent);
54
+ outline-offset: -3px;
55
+ }
56
+
57
+ .compact-examples__title {
58
+ font-weight: 700;
59
+ }
60
+
61
+ .compact-examples__hint {
62
+ display: block;
63
+ margin-block-start: 0.15rem;
64
+ color: var(--sl-color-gray-3);
65
+ font-size: var(--sl-text-sm);
66
+ }
67
+
68
+ .compact-examples__body {
69
+ padding: 0.25rem 1rem 1rem;
70
+ border-block-start: 1px solid var(--sl-color-gray-5);
71
+ }
72
+
73
+ :global(.compact-examples__tabs) {
74
+ display: flex;
75
+ gap: 0.25rem;
76
+ overflow-x: auto;
77
+ margin: 0 -1rem 1rem;
78
+ padding: 0.55rem 1rem 0;
79
+ border-block-end: 1px solid var(--sl-color-gray-5);
80
+ }
81
+
82
+ :global(.compact-examples__tab) {
83
+ flex: 0 0 auto;
84
+ padding: 0.45rem 0.7rem;
85
+ color: var(--sl-color-gray-3);
86
+ font: inherit;
87
+ font-size: var(--sl-text-sm);
88
+ font-weight: 600;
89
+ line-height: var(--sl-line-height-headings);
90
+ cursor: pointer;
91
+ border: 0;
92
+ border-block-end: 3px solid transparent;
93
+ background: transparent;
94
+ }
95
+
96
+ :global(.compact-examples__tab[aria-selected='true']) {
97
+ color: var(--sl-color-text-accent);
98
+ border-block-end-color: var(--sl-color-text-accent);
99
+ }
100
+
101
+ :global(.compact-examples__tab:focus-visible) {
102
+ outline: 3px solid var(--sl-color-accent);
103
+ outline-offset: -3px;
104
+ }
105
+
106
+ :global(.compact-example[role='tabpanel']) {
107
+ margin: 0;
108
+ }
109
+
110
+ @media print {
111
+ .compact-examples {
112
+ overflow: visible;
113
+ border: 0;
114
+ background: transparent;
115
+ }
116
+
117
+ .compact-examples__summary,
118
+ :global(.compact-examples__tabs) {
119
+ display: none;
120
+ }
121
+
122
+ .compact-examples__body {
123
+ display: block !important;
124
+ padding: 0;
125
+ border: 0;
126
+ }
127
+
128
+ :global(.compact-example[hidden]) {
129
+ display: block !important;
130
+ }
131
+
132
+ :global(.compact-example + .compact-example) {
133
+ margin-block-start: 1.5rem;
134
+ }
135
+
136
+ :global(.compact-example__heading[hidden]) {
137
+ display: block !important;
138
+ }
139
+ }
140
+ </style>
141
+
142
+ <script>
143
+ class CompactExamplesElement extends HTMLElement {
144
+ static nextId = 0;
145
+
146
+ connectedCallback() {
147
+ if (this.dataset.enhanced === 'true') return;
148
+
149
+ const details =
150
+ this.querySelector<HTMLDetailsElement>(':scope > details');
151
+ const body = details?.querySelector<HTMLElement>(
152
+ ':scope > .compact-examples__body',
153
+ );
154
+ if (!details || !body) return;
155
+
156
+ const panels = Array.from(
157
+ body.querySelectorAll<HTMLElement>(':scope > [data-compact-example]'),
158
+ );
159
+ if (panels.length < 2) return;
160
+
161
+ this.dataset.enhanced = 'true';
162
+ const instanceId = `compact-examples-${CompactExamplesElement.nextId++}`;
163
+ const tablist = document.createElement('div');
164
+ tablist.className = 'compact-examples__tabs';
165
+ tablist.setAttribute('role', 'tablist');
166
+ tablist.setAttribute(
167
+ 'aria-label',
168
+ this.dataset.tabsLabel || 'Worked examples',
169
+ );
170
+
171
+ const tabs = panels.map((panel, index) => {
172
+ const label = panel.dataset.exampleLabel || `Example ${index + 1}`;
173
+ const panelId = panel.id || `${instanceId}-panel-${index + 1}`;
174
+ const tabId = `${instanceId}-tab-${index + 1}`;
175
+ panel.id = panelId;
176
+ panel.setAttribute('role', 'tabpanel');
177
+ panel.setAttribute('aria-labelledby', tabId);
178
+
179
+ const heading = panel.querySelector<HTMLElement>(
180
+ ':scope > .compact-example__heading',
181
+ );
182
+ if (heading) heading.hidden = true;
183
+
184
+ const tab = document.createElement('button');
185
+ tab.type = 'button';
186
+ tab.className = 'compact-examples__tab';
187
+ tab.id = tabId;
188
+ tab.textContent = label;
189
+ tab.setAttribute('role', 'tab');
190
+ tab.setAttribute('aria-controls', panelId);
191
+ tablist.append(tab);
192
+ return tab;
193
+ });
194
+
195
+ const activate = (activeIndex: number, moveFocus = false) => {
196
+ tabs.forEach((tab, index) => {
197
+ const selected = index === activeIndex;
198
+ tab.setAttribute('aria-selected', String(selected));
199
+ tab.tabIndex = selected ? 0 : -1;
200
+ panels[index].hidden = !selected;
201
+ });
202
+ if (moveFocus) tabs[activeIndex].focus();
203
+ };
204
+
205
+ tabs.forEach((tab, index) => {
206
+ tab.addEventListener('click', () => activate(index));
207
+ tab.addEventListener('keydown', (event) => {
208
+ let nextIndex: number | undefined;
209
+ if (event.key === 'ArrowLeft') {
210
+ nextIndex = (index - 1 + tabs.length) % tabs.length;
211
+ } else if (event.key === 'ArrowRight') {
212
+ nextIndex = (index + 1) % tabs.length;
213
+ } else if (event.key === 'Home') {
214
+ nextIndex = 0;
215
+ } else if (event.key === 'End') {
216
+ nextIndex = tabs.length - 1;
217
+ }
218
+
219
+ if (nextIndex === undefined) return;
220
+ event.preventDefault();
221
+ activate(nextIndex, true);
222
+ });
223
+ });
224
+
225
+ body.prepend(tablist);
226
+ activate(0);
227
+
228
+ let openBeforePrint: boolean | undefined;
229
+ const enterPrintMode = () => {
230
+ if (openBeforePrint !== undefined) return;
231
+ openBeforePrint = details.open;
232
+ details.open = true;
233
+ };
234
+ const leavePrintMode = () => {
235
+ if (openBeforePrint === undefined) return;
236
+ details.open = openBeforePrint;
237
+ openBeforePrint = undefined;
238
+ };
239
+ const printMedia = window.matchMedia('print');
240
+ printMedia.addEventListener('change', (event) => {
241
+ if (event.matches) enterPrintMode();
242
+ else leavePrintMode();
243
+ });
244
+ window.addEventListener('beforeprint', enterPrintMode);
245
+ window.addEventListener('afterprint', leavePrintMode);
246
+ if (printMedia.matches) enterPrintMode();
247
+ }
248
+ }
249
+
250
+ if (!customElements.get('compact-examples')) {
251
+ customElements.define('compact-examples', CompactExamplesElement);
252
+ }
253
+ </script>
@@ -0,0 +1,273 @@
1
+ /**
2
+ * Interaction engine for the shared hover/pin panel (see `HoverPanel.astro`).
3
+ *
4
+ * One copy of the positioning, pin, Escape, outside-close, and
5
+ * article-column-clamp logic that the notation layer and the citation layer
6
+ * both need. Each layer supplies the domain-specific seams — how to find a
7
+ * trigger, how to key it, how to fill the panel — and keeps its own markup.
8
+ *
9
+ * The panel is `position: fixed`. Placement clears the whole display equation
10
+ * a marker sits inside (never just the glyph), decides the side against the
11
+ * space left by the sticky site header, and keeps the box within the article
12
+ * column so it never lands under a navigation rail.
13
+ */
14
+
15
+ export interface HoverPanelState {
16
+ activeKey: string | null;
17
+ pinnedKey: string | null;
18
+ visible: boolean;
19
+ }
20
+
21
+ export interface HoverPanelConfig {
22
+ /** The `<aside data-hover-panel>` element. */
23
+ panel: HTMLElement;
24
+ /**
25
+ * Element used only to locate the enclosing article column for the
26
+ * horizontal clamp. Defaults to the panel itself.
27
+ */
28
+ root?: HTMLElement;
29
+ /** Interactive element (marker, list row, control) for an event target. */
30
+ triggerFrom: (node: EventTarget | null) => HTMLElement | null;
31
+ /** Stable key identifying the content a trigger points at. */
32
+ keyFor: (trigger: HTMLElement) => string | null;
33
+ /**
34
+ * Fill the panel for `trigger`/`key`. Return `false` to abort the open
35
+ * (unknown key); the panel stays hidden.
36
+ */
37
+ render: (trigger: HTMLElement, key: string) => boolean | void;
38
+ /** A pin toggle for an event target (a list button, an in-panel button). */
39
+ pinControlFrom?: (node: EventTarget | null) => HTMLElement | null;
40
+ /** Called after the panel is shown for `key`. */
41
+ onShow?: (key: string, trigger: HTMLElement) => void;
42
+ /** Called after the panel is hidden. */
43
+ onClose?: () => void;
44
+ /** Called after every open, close, or pin change. */
45
+ onSync?: (state: HoverPanelState) => void;
46
+ }
47
+
48
+ function closestElement(node: EventTarget | null, selector: string) {
49
+ return node instanceof Element ? node.closest<HTMLElement>(selector) : null;
50
+ }
51
+
52
+ export function createHoverPanel(config: HoverPanelConfig) {
53
+ const { panel } = config;
54
+ if (panel.dataset.hoverPanelReady === 'true') return;
55
+ panel.dataset.hoverPanelReady = 'true';
56
+
57
+ const anchorRoot = config.root ?? panel;
58
+
59
+ let activeKey: string | null = null;
60
+ let activeAnchor: HTMLElement | null = null;
61
+ let pinned: { key: string; anchor: HTMLElement } | null = null;
62
+
63
+ const sync = () => {
64
+ config.onSync?.({
65
+ activeKey,
66
+ pinnedKey: pinned?.key ?? null,
67
+ visible: panel.hidden === false,
68
+ });
69
+ };
70
+
71
+ const pinControlFrom = (node: EventTarget | null) =>
72
+ config.pinControlFrom?.(node) ?? null;
73
+
74
+ const interactiveFrom = (node: EventTarget | null) =>
75
+ pinControlFrom(node) ?? config.triggerFrom(node);
76
+
77
+ const position = () => {
78
+ if (panel.hidden || !activeAnchor) return;
79
+
80
+ const edge = 12;
81
+ const gap = 10;
82
+ const displayEquation = activeAnchor.closest<HTMLElement>('.katex-display');
83
+ const safeRegion = displayEquation ?? activeAnchor;
84
+ const regionRect = safeRegion.getBoundingClientRect();
85
+ const anchorRect = activeAnchor.getBoundingClientRect();
86
+
87
+ // The usable band is bounded by the sticky header, not the raw viewport
88
+ // edge. Decide the side against that real space so a clamp against the
89
+ // header can never push the panel back down over the equation.
90
+ const headerBottom =
91
+ document.querySelector<HTMLElement>('.header')?.getBoundingClientRect()
92
+ .bottom ?? 0;
93
+ const verticalStart = Math.max(edge, headerBottom + edge);
94
+ const verticalEnd = window.innerHeight - edge;
95
+ const spaceAbove = regionRect.top - gap - verticalStart;
96
+ const spaceBelow = verticalEnd - regionRect.bottom - gap;
97
+
98
+ // Measure the panel in the roomier band, then keep it on whichever side
99
+ // actually clears the safe region; shrink only when neither side fits.
100
+ panel.style.maxHeight = `${Math.max(spaceAbove, spaceBelow, 0)}px`;
101
+ let panelRect = panel.getBoundingClientRect();
102
+ const placeBelow =
103
+ spaceBelow >= panelRect.height || spaceBelow >= spaceAbove;
104
+ panel.style.maxHeight = `${Math.max(placeBelow ? spaceBelow : spaceAbove, 0)}px`;
105
+ panelRect = panel.getBoundingClientRect();
106
+
107
+ let top = placeBelow
108
+ ? regionRect.bottom + gap
109
+ : regionRect.top - gap - panelRect.height;
110
+ let left = anchorRect.left + anchorRect.width / 2 - panelRect.width / 2;
111
+
112
+ const article =
113
+ anchorRoot
114
+ .closest<HTMLElement>('.main-pane')
115
+ ?.querySelector<HTMLElement>('.sl-markdown-content') ??
116
+ document.querySelector<HTMLElement>('.sl-markdown-content');
117
+ const articleRect = article?.getBoundingClientRect();
118
+ const horizontalStart = Math.max(edge, articleRect?.left ?? edge);
119
+ const horizontalEnd = Math.min(
120
+ window.innerWidth - edge,
121
+ articleRect?.right ?? window.innerWidth - edge,
122
+ );
123
+
124
+ top = Math.max(
125
+ verticalStart,
126
+ Math.min(top, verticalEnd - panelRect.height),
127
+ );
128
+ left =
129
+ horizontalEnd - horizontalStart >= panelRect.width
130
+ ? Math.max(
131
+ horizontalStart,
132
+ Math.min(left, horizontalEnd - panelRect.width),
133
+ )
134
+ : Math.max(
135
+ edge,
136
+ Math.min(left, window.innerWidth - panelRect.width - edge),
137
+ );
138
+
139
+ panel.style.top = `${Math.round(top)}px`;
140
+ panel.style.left = `${Math.round(left)}px`;
141
+ };
142
+
143
+ const show = (anchor: HTMLElement) => {
144
+ const key = config.keyFor(anchor);
145
+ if (!key) return;
146
+ if (config.render(anchor, key) === false) return;
147
+
148
+ activeKey = key;
149
+ activeAnchor = anchor;
150
+ config.onShow?.(key, anchor);
151
+
152
+ panel.style.visibility = 'hidden';
153
+ panel.hidden = false;
154
+ position();
155
+ panel.style.visibility = 'visible';
156
+ sync();
157
+ };
158
+
159
+ const close = () => {
160
+ activeKey = null;
161
+ activeAnchor = null;
162
+ panel.hidden = true;
163
+ config.onClose?.();
164
+ sync();
165
+ };
166
+
167
+ const restorePinnedOrClose = () => {
168
+ window.requestAnimationFrame(() => {
169
+ const focused = config.triggerFrom(document.activeElement);
170
+ if (focused && config.keyFor(focused) === activeKey) return;
171
+ if (pinned) {
172
+ show(pinned.anchor);
173
+ return;
174
+ }
175
+ if (
176
+ activeAnchor?.matches(':hover') ||
177
+ panel.matches(':hover') ||
178
+ panel.contains(document.activeElement)
179
+ ) {
180
+ return;
181
+ }
182
+ close();
183
+ });
184
+ };
185
+
186
+ const togglePin = (key: string, anchor: HTMLElement) => {
187
+ if (pinned?.key === key && pinned.anchor === anchor) {
188
+ pinned = null;
189
+ close();
190
+ return;
191
+ }
192
+ pinned = { key, anchor };
193
+ show(anchor);
194
+ };
195
+
196
+ document.addEventListener('pointerover', (event) => {
197
+ if (event.pointerType === 'touch' || pinned) return;
198
+ const anchor = config.triggerFrom(event.target);
199
+ const key = anchor ? config.keyFor(anchor) : null;
200
+ if (anchor && key && anchor !== config.triggerFrom(event.relatedTarget)) {
201
+ show(anchor);
202
+ }
203
+ });
204
+
205
+ document.addEventListener('pointerout', (event) => {
206
+ if (event.pointerType === 'touch') return;
207
+ const anchor = config.triggerFrom(event.target);
208
+ if (!anchor || anchor === config.triggerFrom(event.relatedTarget)) return;
209
+ if (
210
+ event.relatedTarget instanceof Node &&
211
+ panel.contains(event.relatedTarget)
212
+ ) {
213
+ return;
214
+ }
215
+ restorePinnedOrClose();
216
+ });
217
+
218
+ document.addEventListener('focusin', (event) => {
219
+ const anchor = config.triggerFrom(event.target);
220
+ const key = anchor ? config.keyFor(anchor) : null;
221
+ if (anchor && key) show(anchor);
222
+ });
223
+ document.addEventListener('focusout', restorePinnedOrClose);
224
+
225
+ document.addEventListener('click', (event) => {
226
+ const control = pinControlFrom(event.target);
227
+ if (control) {
228
+ const ownKey = config.keyFor(control);
229
+ if (ownKey) {
230
+ togglePin(ownKey, control);
231
+ } else if (activeKey && activeAnchor) {
232
+ togglePin(activeKey, activeAnchor);
233
+ }
234
+ return;
235
+ }
236
+
237
+ const trigger = config.triggerFrom(event.target);
238
+ if (!trigger || trigger.closest('a')) return;
239
+ const key = config.keyFor(trigger);
240
+ if (!key) return;
241
+ event.preventDefault();
242
+ togglePin(key, trigger);
243
+ });
244
+
245
+ document.addEventListener('pointerdown', (event) => {
246
+ if (!pinned) return;
247
+ if (
248
+ interactiveFrom(event.target) ||
249
+ (event.target instanceof Node && panel.contains(event.target))
250
+ ) {
251
+ return;
252
+ }
253
+ pinned = null;
254
+ close();
255
+ });
256
+
257
+ document.addEventListener('keydown', (event) => {
258
+ if (event.key !== 'Escape' || (!activeKey && !pinned)) return;
259
+ pinned = null;
260
+ close();
261
+ });
262
+
263
+ panel.addEventListener('pointerleave', restorePinnedOrClose);
264
+ window.addEventListener('resize', position, { passive: true });
265
+ window.addEventListener('scroll', position, {
266
+ passive: true,
267
+ capture: true,
268
+ });
269
+
270
+ sync();
271
+ }
272
+
273
+ export { closestElement };