@voithos-labs/aragonite 0.10.2 → 0.10.3

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 (255) hide show
  1. package/README.md +6 -21
  2. package/THIRD-PARTY-NOTICES.md +25 -0
  3. package/dist/a11y-strings.d.ts +18 -0
  4. package/dist/a11y-strings.js +18 -0
  5. package/dist/action-contracts.d.ts +7 -1
  6. package/dist/ambient/ambient-dom.js +5 -1
  7. package/dist/block-component.d.ts +14 -0
  8. package/dist/components/BlockDragHandle.svelte +35 -24
  9. package/dist/components/BlockHost.svelte +16 -9
  10. package/dist/components/Editor.svelte +381 -198
  11. package/dist/components/Editor.svelte.d.ts +1 -1
  12. package/dist/components/SelectionOverlay.svelte +17 -3
  13. package/dist/components/TailInsert.svelte +107 -0
  14. package/dist/components/TailInsert.svelte.d.ts +17 -0
  15. package/dist/components/block-content-selector.d.ts +6 -2
  16. package/dist/components/block-content-selector.js +6 -2
  17. package/dist/components/blocks/ThematicBreakBlock.svelte +19 -6
  18. package/dist/components/blocks/ThematicBreakBlock.svelte.d.ts +1 -0
  19. package/dist/components/blocks/code/CodeBlock.svelte +211 -30
  20. package/dist/components/blocks/code/CodeBlockRail.svelte +686 -0
  21. package/dist/components/blocks/code/CodeBlockRail.svelte.d.ts +26 -0
  22. package/dist/components/blocks/code/code-bootstrap.js +4 -0
  23. package/dist/components/blocks/code/code-context-actions.d.ts +1 -0
  24. package/dist/components/blocks/code/code-context-actions.js +24 -0
  25. package/dist/components/blocks/code/code-fence-exit.d.ts +15 -0
  26. package/dist/components/blocks/code/code-fence-exit.js +26 -0
  27. package/dist/components/blocks/code/code-languages.d.ts +6 -0
  28. package/dist/components/blocks/code/code-languages.js +11 -0
  29. package/dist/components/blocks/code/code-renderer.js +11 -0
  30. package/dist/components/blocks/directive/DirectiveContainerBlock.svelte +1 -1
  31. package/dist/components/blocks/editable-leaf.d.ts +37 -6
  32. package/dist/components/blocks/editable-leaf.js +243 -29
  33. package/dist/components/blocks/editable-surface.d.ts +9 -0
  34. package/dist/components/blocks/editable-surface.js +22 -3
  35. package/dist/components/blocks/list/ListItemBlock.svelte +3 -3
  36. package/dist/components/blocks/list/task-checkbox.d.ts +2 -0
  37. package/dist/components/blocks/list/task-checkbox.js +11 -2
  38. package/dist/components/blocks/surface-wiring.svelte.d.ts +4 -0
  39. package/dist/components/blocks/surface-wiring.svelte.js +8 -1
  40. package/dist/components/blocks/table/TableActionMenu.svelte +210 -86
  41. package/dist/components/blocks/table/TableActionMenu.svelte.d.ts +5 -0
  42. package/dist/components/blocks/table/TableBlock.svelte +205 -186
  43. package/dist/components/blocks/table/TableBlock.svelte.d.ts +1 -0
  44. package/dist/components/blocks/table/TableCellBlock.svelte +87 -22
  45. package/dist/components/blocks/table/TableRowBlock.svelte +4 -18
  46. package/dist/components/blocks/table/TableRowBlock.svelte.d.ts +0 -5
  47. package/dist/components/blocks/table/cell-clipboard.d.ts +10 -0
  48. package/dist/components/blocks/table/cell-clipboard.js +34 -1
  49. package/dist/components/blocks/table/cell-keydown-plan.d.ts +1 -1
  50. package/dist/components/blocks/table/cell-keydown-plan.js +3 -1
  51. package/dist/components/blocks/table/table-cell-paste.js +2 -1
  52. package/dist/components/blocks/table/table-menu-model.d.ts +35 -8
  53. package/dist/components/blocks/table/table-menu-model.js +36 -17
  54. package/dist/components/blocks/text/TextEditableBlock.svelte +58 -11
  55. package/dist/components/blocks/text/delimiter-autopair.d.ts +71 -0
  56. package/dist/components/blocks/text/delimiter-autopair.js +216 -0
  57. package/dist/components/blocks/text/edge-policy-dispatch.d.ts +4 -0
  58. package/dist/components/blocks/text/edge-policy-dispatch.js +56 -2
  59. package/dist/components/blocks/text/live-selection-edit.js +27 -0
  60. package/dist/components/blocks/text/text-keydown.d.ts +7 -1
  61. package/dist/components/blocks/text/text-keydown.js +11 -1
  62. package/dist/components/blocks/text/text-render.d.ts +1 -1
  63. package/dist/components/blocks/text/text-render.js +3 -1
  64. package/dist/components/blocks/text/widget-interaction.d.ts +3 -0
  65. package/dist/components/blocks/text/widget-interaction.js +144 -22
  66. package/dist/components/drag-handle.d.ts +35 -0
  67. package/dist/components/drag-handle.js +126 -0
  68. package/dist/components/editor-root-focus.d.ts +19 -0
  69. package/dist/components/editor-root-focus.js +67 -0
  70. package/dist/components/editor-root-geometry.d.ts +39 -0
  71. package/dist/components/editor-root-geometry.js +91 -0
  72. package/dist/components/editor-root-keydown.d.ts +1 -1
  73. package/dist/components/editor-root-keydown.js +12 -3
  74. package/dist/components/editor-root-listeners.d.ts +10 -6
  75. package/dist/components/editor-root-listeners.js +23 -22
  76. package/dist/components/editor-root-mode-flip.d.ts +36 -0
  77. package/dist/components/editor-root-mode-flip.js +92 -0
  78. package/dist/components/image/ImageOverlayHost.svelte +14 -6
  79. package/dist/components/image/ImageProperties.svelte +512 -70
  80. package/dist/components/image/ImageProperties.svelte.d.ts +6 -1
  81. package/dist/components/image/ImageResizeHandles.svelte +59 -34
  82. package/dist/components/image/image-crop.d.ts +39 -0
  83. package/dist/components/image/image-crop.js +74 -0
  84. package/dist/components/image/image-edit-commit.d.ts +1 -0
  85. package/dist/components/image/image-edit-commit.js +21 -5
  86. package/dist/components/image/image-source-bytes.js +10 -3
  87. package/dist/components/image/image-widget-editing.js +1 -0
  88. package/dist/components/image/widget-dom.js +5 -1
  89. package/dist/components/link-card/link-card-commit.js +1 -1
  90. package/dist/components/lrd-map-gate.js +1 -1
  91. package/dist/components/menu/BlockMenu.svelte +315 -0
  92. package/dist/components/menu/BlockMenu.svelte.d.ts +34 -0
  93. package/dist/components/menu/MenuIcon.svelte +153 -0
  94. package/dist/components/menu/MenuIcon.svelte.d.ts +51 -0
  95. package/dist/components/menu/clipboard-actions.d.ts +12 -0
  96. package/dist/components/menu/clipboard-actions.js +42 -0
  97. package/dist/components/menu/default-context-actions.d.ts +15 -0
  98. package/dist/components/menu/default-context-actions.js +76 -0
  99. package/dist/components/menu/flyout-placement.d.ts +6 -0
  100. package/dist/components/menu/flyout-placement.js +25 -0
  101. package/dist/core/inline/format-toggle.d.ts +12 -4
  102. package/dist/core/inline/format-toggle.js +94 -40
  103. package/dist/core/inline/image-dimensions.d.ts +3 -0
  104. package/dist/core/inline/image-dimensions.js +46 -10
  105. package/dist/core/inline/inline-widgets.d.ts +11 -0
  106. package/dist/core/inline/scan/brackets.js +1 -0
  107. package/dist/core/inline/scan/plugin-syntax.d.ts +8 -0
  108. package/dist/core/inline/scan/plugin-syntax.js +15 -1
  109. package/dist/core/inline-render.d.ts +6 -0
  110. package/dist/core/inline-render.js +32 -0
  111. package/dist/core/nodes.d.ts +14 -0
  112. package/dist/cursor/edge-affinity.js +2 -1
  113. package/dist/cursor/overlay-remeasure.js +8 -0
  114. package/dist/cursor/reveal-source.js +7 -2
  115. package/dist/cursor/widget-offset.d.ts +6 -0
  116. package/dist/cursor/widget-offset.js +61 -4
  117. package/dist/debug/interaction-trace.d.ts +4 -0
  118. package/dist/debug/interaction-trace.js +15 -0
  119. package/dist/decorations/decoration-state.svelte.js +1 -1
  120. package/dist/editor-actions/ancestry-folds.d.ts +2 -2
  121. package/dist/editor-actions/ancestry-folds.js +1 -1
  122. package/dist/editor-actions/block-edit-scope.js +1 -1
  123. package/dist/editor-actions/commit/text-batch.d.ts +3 -2
  124. package/dist/editor-actions/commit/text-batch.js +1 -1
  125. package/dist/editor-actions/commit/undo-controller.js +4 -2
  126. package/dist/editor-actions/container-edit.js +2 -1
  127. package/dist/editor-actions/enter-completion.d.ts +2 -0
  128. package/dist/editor-actions/enter-completion.js +23 -2
  129. package/dist/editor-actions/inline-range-commit.js +1 -1
  130. package/dist/editor-actions/reorder-action.js +19 -10
  131. package/dist/editor-actions/reorder-drag.js +29 -1
  132. package/dist/editor-actions/replacement-focus.d.ts +1 -1
  133. package/dist/editor-actions/replacement-focus.js +1 -1
  134. package/dist/editor-actions/search-replace.js +1 -1
  135. package/dist/editor-actions/table-context.d.ts +4 -1
  136. package/dist/editor-actions/table-context.js +57 -1
  137. package/dist/editor-events.d.ts +3 -0
  138. package/dist/editor-keys.d.ts +33 -0
  139. package/dist/editor-props.d.ts +21 -12
  140. package/dist/index.d.ts +1 -1
  141. package/dist/plugin.d.ts +6 -0
  142. package/dist/plugin.js +11 -0
  143. package/dist/plugins/latex/BlockMath.svelte +264 -29
  144. package/dist/plugins/latex/BlockMath.svelte.d.ts +2 -0
  145. package/dist/plugins/latex/index.d.ts +2 -1
  146. package/dist/plugins/latex/latex-kind.js +36 -3
  147. package/dist/plugins/latex/math-completion.js +4 -1
  148. package/dist/plugins/latex/math-layout.d.ts +15 -0
  149. package/dist/plugins/latex/math-layout.js +13 -0
  150. package/dist/plugins/latex/math-source.d.ts +19 -0
  151. package/dist/plugins/latex/math-source.js +97 -0
  152. package/dist/plugins/latex/register.d.ts +11 -2
  153. package/dist/plugins/latex/register.js +3 -1
  154. package/dist/plugins/latex/renderer.d.ts +3 -3
  155. package/dist/plugins/latex/renderer.js +13 -6
  156. package/dist/reactivity/list-windowing.svelte.d.ts +8 -8
  157. package/dist/reactivity/list-windowing.svelte.js +49 -24
  158. package/dist/schema/block-completions.d.ts +8 -0
  159. package/dist/schema/block-completions.js +11 -0
  160. package/dist/schema/context-actions.d.ts +31 -0
  161. package/dist/schema/context-actions.js +23 -0
  162. package/dist/schema/fenced-code-raw.js +31 -1
  163. package/dist/schema/operations.d.ts +8 -1
  164. package/dist/schema/reserved-chords.js +25 -4
  165. package/dist/schema/table-cell-raw.d.ts +1 -1
  166. package/dist/schema/table-cell-raw.js +1 -1
  167. package/dist/selection/block-hit-test.js +3 -2
  168. package/dist/selection/char-endpoint-snap.js +1 -1
  169. package/dist/selection/clipboard-text.js +6 -1
  170. package/dist/selection/covered-block.d.ts +10 -0
  171. package/dist/selection/covered-block.js +24 -0
  172. package/dist/selection/cross-block/dispatch.d.ts +3 -0
  173. package/dist/selection/cross-block/dispatch.js +13 -1
  174. package/dist/selection/cross-block/format-range.d.ts +1 -1
  175. package/dist/selection/cross-block/format-range.js +4 -15
  176. package/dist/selection/cross-block/format-toggle.js +1 -1
  177. package/dist/selection/cross-block/keydown.js +1 -1
  178. package/dist/selection/cross-block/ops.js +1 -1
  179. package/dist/selection/cross-block/paste.js +32 -30
  180. package/dist/selection/cross-block/type-replace.d.ts +3 -2
  181. package/dist/selection/cross-block/type-replace.js +62 -13
  182. package/dist/selection/dead-space-caret.d.ts +10 -0
  183. package/dist/selection/dead-space-caret.js +47 -1
  184. package/dist/selection/double-click-trim.d.ts +17 -0
  185. package/dist/selection/double-click-trim.js +57 -0
  186. package/dist/selection/drag-pointer.d.ts +7 -2
  187. package/dist/selection/drag-pointer.js +61 -2
  188. package/dist/selection/gap-caret.js +1 -1
  189. package/dist/selection/keyboard-extend.js +1 -1
  190. package/dist/selection/path-lookup.js +1 -1
  191. package/dist/selection/range-delete-ceremony.js +4 -2
  192. package/dist/selection/range-delete-chrome.js +2 -1
  193. package/dist/selection/range-delete-table-coverage.js +2 -1
  194. package/dist/selection/range-delete-table.js +3 -2
  195. package/dist/selection/range-delete.js +30 -2
  196. package/dist/selection/selection-restore.js +1 -1
  197. package/dist/selection/selection-state.svelte.d.ts +6 -0
  198. package/dist/selection/selection-state.svelte.js +36 -1
  199. package/dist/selection/table-endpoint-snap.js +1 -1
  200. package/dist/selection/table-rect-extend.js +1 -1
  201. package/dist/styles/editor-theme.css +57 -28
  202. package/dist/styles/editor.css +217 -18
  203. package/dist/testing/container-conformance.js +2 -2
  204. package/dist/testing/inline-conformance.js +2 -1
  205. package/dist/tree-operations/blockquote.js +1 -1
  206. package/dist/tree-operations/chain-rebuild.d.ts +63 -0
  207. package/dist/tree-operations/chain-rebuild.js +142 -0
  208. package/dist/tree-operations/children.d.ts +1 -1
  209. package/dist/tree-operations/children.js +1 -1
  210. package/dist/tree-operations/cleanup.js +1 -1
  211. package/dist/tree-operations/content-write.d.ts +50 -0
  212. package/dist/tree-operations/content-write.js +263 -0
  213. package/dist/tree-operations/index.d.ts +8 -3
  214. package/dist/tree-operations/index.js +6 -2
  215. package/dist/tree-operations/list/exit-replacement.js +1 -1
  216. package/dist/tree-operations/list/unwrap-merge.js +3 -3
  217. package/dist/tree-operations/node-ops.d.ts +17 -234
  218. package/dist/tree-operations/node-ops.js +47 -1113
  219. package/dist/tree-operations/node-primitives.d.ts +75 -0
  220. package/dist/tree-operations/node-primitives.js +117 -0
  221. package/dist/tree-operations/paste/apply.js +1 -1
  222. package/dist/tree-operations/paste/body-write.d.ts +1 -1
  223. package/dist/tree-operations/paste/body-write.js +2 -2
  224. package/dist/tree-operations/paste/container-match.js +4 -2
  225. package/dist/tree-operations/paste/dispatch.js +2 -1
  226. package/dist/tree-operations/paste/find-enclosing-list.js +1 -1
  227. package/dist/tree-operations/paste/focus-target.d.ts +1 -1
  228. package/dist/tree-operations/paste/list-absorb.js +1 -1
  229. package/dist/tree-operations/paste/list-break-out.js +1 -1
  230. package/dist/tree-operations/paste/parent-scope.js +1 -1
  231. package/dist/tree-operations/paste/paste-replacement.js +1 -1
  232. package/dist/tree-operations/paste/replace-block-at-parent.js +1 -1
  233. package/dist/tree-operations/path-mutate.d.ts +1 -1
  234. package/dist/tree-operations/path-mutate.js +2 -1
  235. package/dist/tree-operations/reorder-unit.js +1 -1
  236. package/dist/tree-operations/reorder.d.ts +5 -2
  237. package/dist/tree-operations/reorder.js +55 -2
  238. package/dist/tree-operations/settle.d.ts +105 -0
  239. package/dist/tree-operations/settle.js +660 -0
  240. package/dist/tree-operations/table-grid-clipboard.d.ts +21 -0
  241. package/dist/tree-operations/table-grid-clipboard.js +90 -0
  242. package/dist/tree-operations/unshare.d.ts +15 -77
  243. package/dist/tree-operations/unshare.js +15 -166
  244. package/docs/guide/consumer-guide.md +126 -96
  245. package/docs/guide/plugin-api.md +31 -3
  246. package/docs/guide/plugin-guide.md +26 -3
  247. package/package.json +4 -2
  248. package/dist/components/blocks/code/CodeLanguageChip.svelte +0 -127
  249. package/dist/components/blocks/code/CodeLanguageChip.svelte.d.ts +0 -14
  250. package/dist/components/blocks/table/TableGrip.svelte +0 -91
  251. package/dist/components/blocks/table/TableGrip.svelte.d.ts +0 -8
  252. package/dist/components/blocks/table/table-drop-target.d.ts +0 -1
  253. package/dist/components/blocks/table/table-drop-target.js +0 -16
  254. package/dist/components/blocks/table/table-reorder-drag.d.ts +0 -78
  255. package/dist/components/blocks/table/table-reorder-drag.js +0 -97
@@ -4,6 +4,7 @@
4
4
  * factory owns the imperative inline-DOM construction and carries a live caret across
5
5
  * a decoration-driven rebuild the edit path's pending restore doesn't cover.
6
6
  */
7
+ import { hidesMarkers } from '../../../presentation-mode';
7
8
  import { buildAmbientSpan } from '../../../ambient/ambient-dom';
8
9
  import { computeInlineContent, contentLengthOf, getContentRange, isProseKind } from '../../../core/inline';
9
10
  import { renderInlineNodes } from '../../../core/inline-render';
@@ -90,7 +91,8 @@ export function createTextRender(deps) {
90
91
  buildPortalWidget,
91
92
  // Attribute-only stamps for the construct-reveal trigger, mode-gated so the
92
93
  // other modes' DOM stays byte-identical.
93
- tagConstructMarkers: deps.presentationMode === 'preview-inline'
94
+ tagConstructMarkers: deps.presentationMode === 'preview-inline',
95
+ pendingBreakSeat: hidesMarkers(deps.presentationMode)
94
96
  }));
95
97
  return frag;
96
98
  }
@@ -69,6 +69,9 @@ export interface WidgetInteraction {
69
69
  /** Snap a click that landed outside any text node to the nearest widget edge, or open a
70
70
  * reveal-source widget it hit. */
71
71
  snapClickToWidgetEdge(clickX: number | null, clickY: number | null, press?: WidgetPress): void;
72
+ /** A caret restored strictly inside a reveal-source widget — a formula that just closed
73
+ * around it — opens that source there instead of being pushed past the island. */
74
+ revealInterior(offset: number): boolean;
72
75
  /** A reveal-source widget currently shows its editable `$…$` source. */
73
76
  isRevealing(): boolean;
74
77
  /** Escape (cancel to rendered) while source is shown. Enter is deliberately NOT
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { tick } from 'svelte';
7
7
  import { resolvedInlineContent } from '../../../core/inline/inline-cache';
8
- import { isInlineWidget, flattenInlineWidgets, getInlineWidgetEditing, isWidgetActivationClick } from '../../../core/inline/inline-widgets';
8
+ import { flattenInlineWidgets, getInlineWidgetEditing, isWidgetActivationClick } from '../../../core/inline/inline-widgets';
9
9
  import { isVerticallyTransparentNode } from '../../../core/inline/transparency';
10
10
  import { trimTrailingLineEnding, trailingLineEnding } from '../../../core/lines';
11
11
  import { asRawOffset, toClampedRawOffset, toDomTextOffset } from '../../../cursor/coordinate-spaces';
@@ -15,6 +15,42 @@ import { traceRevealOpen, traceRevealFold } from '../../../debug/interaction-tra
15
15
  import { assertInvariant } from '../../../assert';
16
16
  import { caretIsInTextContent, hasModifier, isPlainTypingKey } from './click-snap-guard';
17
17
  import { findWidgetNodeByStart, findFirstEdgeWidget, findLastEdgeWidget, rawHasNoTextBefore, rawHasNoTextAfter, widgetElByStart } from './widget-adjacency';
18
+ /**
19
+ * The inline-code wash over a revealed source, painted through the CSS Custom Highlight API
20
+ * (`::highlight(md-inline-reveal)` in editor.css) rather than a wrapper span: the reveal's
21
+ * contract, and every offset read on it, is that the source is a bare text node in the block.
22
+ * The range selects the NODE, so it keeps covering the text as typing grows it. Absent the API
23
+ * (jsdom, an old engine) the source simply shows unwashed.
24
+ */
25
+ const REVEAL_HIGHLIGHT = 'md-inline-reveal';
26
+ const washRanges = new WeakMap();
27
+ function revealHighlight() {
28
+ if (typeof CSS === 'undefined' || !('highlights' in CSS) || typeof Highlight !== 'function') {
29
+ return null;
30
+ }
31
+ let highlight = CSS.highlights.get(REVEAL_HIGHLIGHT);
32
+ if (!highlight) {
33
+ highlight = new Highlight();
34
+ CSS.highlights.set(REVEAL_HIGHLIGHT, highlight);
35
+ }
36
+ return highlight;
37
+ }
38
+ function washRevealedSource(node) {
39
+ const highlight = revealHighlight();
40
+ if (!highlight)
41
+ return;
42
+ const range = document.createRange();
43
+ range.selectNode(node);
44
+ washRanges.set(node, range);
45
+ highlight.add(range);
46
+ }
47
+ function unwashRevealedSource(node) {
48
+ const range = washRanges.get(node);
49
+ if (!range)
50
+ return;
51
+ washRanges.delete(node);
52
+ revealHighlight()?.delete(range);
53
+ }
18
54
  export function createWidgetInteraction(deps) {
19
55
  const isReading = () => deps.getPresentationMode?.() === 'reading';
20
56
  // Resolver-aware so widget detection matches the render path's view; a mismatch
@@ -22,12 +58,26 @@ export function createWidgetInteraction(deps) {
22
58
  function inlinesOf(node) {
23
59
  return resolvedInlineContent(node, deps.linkRef);
24
60
  }
61
+ /**
62
+ * Every widget in this block, DESCENDING into non-widget parents. The flat inline list is
63
+ * the wrong shape to scan for widgets: a construct wrapping one — emphasis around a math
64
+ * span, a link around an image — hides it, and every pointer and arrow-key path that
65
+ * scanned the flat list simply did not see it. `> *… $L/9 \times 10^{20}$ …*` is the case
66
+ * that surfaced it: the quote's whole text is one emphasis node, so nothing inside it was
67
+ * ever clickable.
68
+ */
69
+ function widgetsOf() {
70
+ return flattenInlineWidgets(inlinesOf(deps.node), deps.node.raw);
71
+ }
25
72
  let revealState = null;
26
73
  // Outside the record because they must survive `resetReveal` for the kernel's async
27
74
  // restore. Identity is load-bearing: two byte-identical widgets share a reuse-pool key,
28
75
  // so a lookup can return the OTHER instance and `replaceWith` would MOVE it.
29
76
  let activeSourceNode = null;
30
77
  let revealedWidget = null;
78
+ // The offset the last fold parked, which lies inside the construct it just folded: the
79
+ // restore must not read it as a formula closing around the caret and reopen the reveal.
80
+ let foldParkedCaret = null;
31
81
  // Every fold entry is module-private and pre-guarded by all of its callers, so a fold
32
82
  // with no active reveal means a new caller skipped the guard or a flag leaked (G1.26).
33
83
  function assertFoldTargetsActiveReveal(entry) {
@@ -38,6 +88,7 @@ export function createWidgetInteraction(deps) {
38
88
  function restoreRenderedWidget() {
39
89
  if (activeSourceNode === null || revealedWidget === null)
40
90
  return;
91
+ unwashRevealedSource(activeSourceNode);
41
92
  activeSourceNode.replaceWith(revealedWidget);
42
93
  activeSourceNode = null;
43
94
  revealedWidget = null;
@@ -93,6 +144,7 @@ export function createWidgetInteraction(deps) {
93
144
  revealedWidget = widget;
94
145
  activeSourceNode = document.createTextNode(source);
95
146
  widget.replaceWith(activeSourceNode);
147
+ washRevealedSource(activeSourceNode);
96
148
  },
97
149
  // Re-inserts the exact element the swap detached, still current because the edit
98
150
  // was discarded. The persist path re-renders reactively instead.
@@ -138,9 +190,12 @@ export function createWidgetInteraction(deps) {
138
190
  const { caretBefore, originalDisplay } = active;
139
191
  // The reactive re-render rebuilds the island, so drop the swap handles without a
140
192
  // DOM restore, then run the canonical teardown.
193
+ if (sourceNode)
194
+ unwashRevealedSource(sourceNode);
141
195
  activeSourceNode = null;
142
196
  revealedWidget = null;
143
197
  resetReveal();
198
+ foldParkedCaret = caretAfter;
144
199
  // No edit: fold without touching the CST. A zero-diff write still pushes a dead
145
200
  // undo entry, so the user's next Ctrl+Z would revert nothing instead of their
146
201
  // prior action. The pending-cursor set alone re-renders from the untouched CST.
@@ -243,10 +298,49 @@ export function createWidgetInteraction(deps) {
243
298
  }
244
299
  // The ONE hit test the pointerdown probe, the click dispatch, and the post-fold
245
300
  // re-resolve share.
301
+ /**
302
+ * What the pointer is actually over, asked of the DOM rather than of a rectangle. An
303
+ * island's border box is not what the reader sees: a KaTeX render (a superscript, a
304
+ * fraction) paints ABOVE and BELOW its own box, so a click on the visible glyphs missed
305
+ * the rect by a pixel or two and the widget looked inert to the mouse while the keyboard
306
+ * reached it fine. `elementFromPoint` has no such gap — it answers with whatever is
307
+ * painted at the point, overflow included.
308
+ */
309
+ function revealWidgetFromDom(el, x, y) {
310
+ // Feature-detected: jsdom implements no hit testing, and the rect scan below is the
311
+ // answer there — the same shape as `getTargetRanges`' detection on the code surface.
312
+ if (typeof document.elementFromPoint !== 'function')
313
+ return null;
314
+ const at = document.elementFromPoint(x, y);
315
+ const island = at instanceof Element ? at.closest('[data-inline-widget][data-source-start]') : null;
316
+ // Containment guard: the point may sit over another block's widget entirely.
317
+ if (!island || !el.contains(island))
318
+ return null;
319
+ const start = Number(island.getAttribute('data-source-start'));
320
+ if (!Number.isInteger(start))
321
+ return null;
322
+ const inline = widgetsOf().find((n) => n.start === start && getInlineWidgetEditing(n.kind)?.revealSource);
323
+ return inline ? { inline } : null;
324
+ }
325
+ /**
326
+ * The caret a click on a rendered widget wants: the END of the content a kind names, INSIDE
327
+ * its delimiters, so typing continues the construct rather than escaping it. A kind naming no
328
+ * span keeps the leading edge: guessing an end from its parsed text put a footnote's caret
329
+ * past its bracket.
330
+ */
331
+ function insideEndOffset(inline) {
332
+ const source = deps.node.raw.slice(inline.start, inline.end);
333
+ const span = getInlineWidgetEditing(inline.kind)?.revealContentSpan?.(source);
334
+ return span && span.end >= 0 && span.end <= source.length ? span.end : 0;
335
+ }
246
336
  function hitTestRevealWidget(el, x, y) {
247
- for (const inline of inlinesOf(deps.node)) {
248
- if (!isInlineWidget(inline, deps.node.raw))
249
- continue;
337
+ const painted = revealWidgetFromDom(el, x, y);
338
+ if (painted)
339
+ return painted;
340
+ // Fallback for a point the DOM cannot answer for (a synthetic call with no element
341
+ // under it): the original box test, which still covers a widget that paints inside
342
+ // its own bounds.
343
+ for (const inline of widgetsOf()) {
250
344
  if (!getInlineWidgetEditing(inline.kind)?.revealSource)
251
345
  continue;
252
346
  const widget = widgetElByStart(el, inline.start);
@@ -292,26 +386,30 @@ export function createWidgetInteraction(deps) {
292
386
  targetStart += deps.node.raw.length - rawBefore;
293
387
  }
294
388
  }
295
- const target = inlinesOf(deps.node).find((n) => n.start === targetStart &&
296
- isInlineWidget(n, deps.node.raw) &&
297
- getInlineWidgetEditing(n.kind)?.revealSource);
389
+ const target = widgetsOf().find((n) => n.start === targetStart && getInlineWidgetEditing(n.kind)?.revealSource);
298
390
  if (!target)
299
391
  return;
300
392
  el.focus();
301
393
  revealOpenedByLastClick = true;
302
- // A click can't map to a source glyph — land at the leading edge (offset 0).
303
- void startReveal(target, target.start, 0);
394
+ // A click can't map to a source glyph, so it lands at the end of the content rather
395
+ // than guessing: the caret sits inside the delimiters, ready to type.
396
+ void startReveal(target, target.start, insideEndOffset(target));
304
397
  }
305
- /** Take the whole revealed token; declines unless the native selection sits inside it. */
306
- function selectRevealedSource() {
398
+ /**
399
+ * Take the whole revealed token; declines unless the double-click landed on it. Keyed off the
400
+ * point, not the selection: the root's second-press word select runs before this click and
401
+ * may have moved the selection off the source.
402
+ */
403
+ function selectRevealedSource(x, y) {
307
404
  const source = activeSourceNode;
308
- if (!revealState || !source)
309
- return false;
310
- const selection = window.getSelection();
311
- if (!selection || !selection.anchorNode || !source.contains(selection.anchorNode))
405
+ if (!revealState || !source || y === null)
312
406
  return false;
313
407
  const range = document.createRange();
314
408
  range.selectNodeContents(source);
409
+ const onSource = Array.from(range.getClientRects()).some((r) => x >= r.left && x <= r.right && y >= r.top && y <= r.bottom);
410
+ const selection = window.getSelection();
411
+ if (!onSource || !selection)
412
+ return false;
315
413
  selection.removeAllRanges();
316
414
  selection.addRange(range);
317
415
  return true;
@@ -411,6 +509,13 @@ export function createWidgetInteraction(deps) {
411
509
  }
412
510
  return true;
413
511
  }
512
+ // A vertical arrow is the shared pipeline's line walk, which needs a real caret to read:
513
+ // seat one at the edge the arrow leaves from and decline, so the walk runs from there.
514
+ if (!e.shiftKey && (e.key === 'ArrowUp' || e.key === 'ArrowDown')) {
515
+ deps.cursor.setRaw(asRawOffset(e.key === 'ArrowUp' ? widget.start : widget.end));
516
+ deps.widgetSelection.clear();
517
+ return false;
518
+ }
414
519
  // Reading mode still swallows — a selected widget owns its keys — but commits nothing.
415
520
  // Undo anchored at the pre-select caret, so Ctrl+Z restores the caret where the user
416
521
  // actually was when selection took over.
@@ -477,6 +582,26 @@ export function createWidgetInteraction(deps) {
477
582
  });
478
583
  }
479
584
  }
585
+ // Only where the kind names a content span and the caret is inside it: a hard break is two
586
+ // bytes with a caret position between them and nothing to edit there.
587
+ function revealInterior(offset) {
588
+ const parkedByFold = foldParkedCaret === offset;
589
+ foldParkedCaret = null;
590
+ if (parkedByFold || revealState)
591
+ return false;
592
+ const widget = widgetsOf().find((w) => w.start < offset && offset < w.end);
593
+ if (!widget)
594
+ return false;
595
+ const editing = getInlineWidgetEditing(widget.kind);
596
+ if (!editing?.revealSource || !editing.revealContentSpan)
597
+ return false;
598
+ const at = offset - widget.start;
599
+ const span = editing.revealContentSpan(deps.node.raw.slice(widget.start, widget.end));
600
+ if (!span || at < span.start || at > span.end)
601
+ return false;
602
+ void startReveal(widget, offset, at);
603
+ return true;
604
+ }
480
605
  function enterEdgeWidget(side) {
481
606
  const inlines = inlinesOf(deps.node);
482
607
  if (inlines.length === 0)
@@ -518,14 +643,12 @@ export function createWidgetInteraction(deps) {
518
643
  }
519
644
  // The first click of a double-click already revealed, so the second lands in the source
520
645
  // text and the browser's word rule takes `[` or `$` as a word of its own.
521
- if (doubleClick && revealOpenedByLastClick && selectRevealedSource())
646
+ if (doubleClick && revealOpenedByLastClick && selectRevealedSource(clickX, clickY))
522
647
  return;
523
648
  // A click in a real text node keeps the native caret; a synthetic overlay would compete.
524
649
  if (caretIsInTextContent(el, window.getSelection()))
525
650
  return;
526
- for (const inline of inlinesOf(deps.node)) {
527
- if (!isInlineWidget(inline, deps.node.raw))
528
- continue;
651
+ for (const inline of widgetsOf()) {
529
652
  const widget = widgetElByStart(el, inline.start);
530
653
  if (!widget)
531
654
  continue;
@@ -549,9 +672,7 @@ export function createWidgetInteraction(deps) {
549
672
  const focus = selectionFocusWalkOffset(el, deps.getAmbientLength());
550
673
  if (focus === null)
551
674
  return null;
552
- for (const inline of inlinesOf(deps.node)) {
553
- if (!isInlineWidget(inline, deps.node.raw))
554
- continue;
675
+ for (const inline of widgetsOf()) {
555
676
  if (key === 'ArrowRight' && focus >= inline.start && focus < inline.end) {
556
677
  return inline.end;
557
678
  }
@@ -579,6 +700,7 @@ export function createWidgetInteraction(deps) {
579
700
  handleSelectedWidgetKeydown,
580
701
  handleShiftArrowIntoWidget,
581
702
  enterWidget,
703
+ revealInterior,
582
704
  enterEdgeWidget,
583
705
  snapClickToWidgetEdge,
584
706
  isRevealing,
@@ -0,0 +1,35 @@
1
+ /**
2
+ * The block drag handle's two policies, kept out of the components that render it: which
3
+ * blocks carry one, and where its grip sits.
4
+ *
5
+ * Presence: prose carries none. A paragraph, a heading, a quote and the note cards read as
6
+ * text on the page, and a grip beside every one of them is noise; the blocks that get one are
7
+ * the objects a reader picks up whole — pictures, code, tables, equations, diagrams, list
8
+ * items, dividers. A gripless block is still a reorder unit: keyboard reorder moves it and a
9
+ * dragged block still drops beside it.
10
+ */
11
+ import type { NodeView } from '../core/node-views';
12
+ export declare function isImageOnlyParagraph(node: NodeView): boolean;
13
+ /**
14
+ * A picture's grip does NOT wait for `blockDragHandles`: dragging it is the only pointer road
15
+ * to move a picture, and unlike prose it is a discrete object a reader expects to pick up. The
16
+ * caller still gates reading mode, which shows no affordances at all.
17
+ */
18
+ export declare function showsDragHandle(node: NodeView, handlesEnabled: boolean): boolean;
19
+ /**
20
+ * Vertical centre of the grip. A one-line block centres on its row. A taller one takes the
21
+ * first line-height of its OWN box — not its first line of text, since a card pads above that
22
+ * and a grip level with the first code line hangs below the card's shoulder. A declared anchor
23
+ * SHORTER than a line is a marker to centre on instead (a wrapped task item's checkbox); a
24
+ * taller one is a card, and the band already covers it.
25
+ */
26
+ export declare function dragHandleAnchorY(host: HTMLElement): number | null;
27
+ /**
28
+ * Svelte attachment for the handle element: places the grip once the block has laid out, and
29
+ * again on every pointerover inside its host, so it follows edits made while hovered.
30
+ *
31
+ * The mount placement is what makes the grip HITTABLE where it appears: it is its own hit
32
+ * target before any hover, so a target parked at the stylesheet's default while the grip would
33
+ * settle elsewhere is a target the pointer misses. One rAF-batched read per handle.
34
+ */
35
+ export declare function alignDragHandle(handle: HTMLElement): (() => void) | void;
@@ -0,0 +1,126 @@
1
+ /**
2
+ * The block drag handle's two policies, kept out of the components that render it: which
3
+ * blocks carry one, and where its grip sits.
4
+ *
5
+ * Presence: prose carries none. A paragraph, a heading, a quote and the note cards read as
6
+ * text on the page, and a grip beside every one of them is noise; the blocks that get one are
7
+ * the objects a reader picks up whole — pictures, code, tables, equations, diagrams, list
8
+ * items, dividers. A gripless block is still a reorder unit: keyboard reorder moves it and a
9
+ * dragged block still drops beside it.
10
+ */
11
+ import { BLOCK_CONTENT_SELECTOR, DRAG_ANCHOR_ATTR } from './block-content-selector';
12
+ /**
13
+ * Prose — the page's background and its asides — plus the list SHELL, whose own grip would
14
+ * land in the gutter on top of its first item's and take the hit test with it. A list moves
15
+ * an item at a time; the shell is a wrapper, not a thing to pick up.
16
+ */
17
+ const GRIPLESS_KINDS = new Set([
18
+ 'paragraph',
19
+ 'heading',
20
+ 'blockquote',
21
+ 'admonition',
22
+ 'githubAlert',
23
+ 'list'
24
+ ]);
25
+ /**
26
+ * A paragraph holding nothing but images is a picture in the page, not prose: it reads as a
27
+ * block of its own and is exactly the thing a reader reaches to move. A false negative here
28
+ * (an alt with an escaped bracket, an image beside a word) is the plain no-grip paragraph.
29
+ */
30
+ const IMAGE_ONLY_PARAGRAPH = /^\s*(?:!\[[^\]]*\](?:\([^)]*\)|\[[^\]]*\])\s*)+$/;
31
+ export function isImageOnlyParagraph(node) {
32
+ return node.kind === 'paragraph' && IMAGE_ONLY_PARAGRAPH.test(node.raw);
33
+ }
34
+ /**
35
+ * A picture's grip does NOT wait for `blockDragHandles`: dragging it is the only pointer road
36
+ * to move a picture, and unlike prose it is a discrete object a reader expects to pick up. The
37
+ * caller still gates reading mode, which shows no affordances at all.
38
+ */
39
+ export function showsDragHandle(node, handlesEnabled) {
40
+ if (isImageOnlyParagraph(node))
41
+ return true;
42
+ return handlesEnabled && !GRIPLESS_KINDS.has(node.kind);
43
+ }
44
+ /** A box within this many line-heights holds one row of content, margins included. */
45
+ const SINGLE_LINE = 1.5;
46
+ /**
47
+ * Vertical centre of the grip. A one-line block centres on its row. A taller one takes the
48
+ * first line-height of its OWN box — not its first line of text, since a card pads above that
49
+ * and a grip level with the first code line hangs below the card's shoulder. A declared anchor
50
+ * SHORTER than a line is a marker to centre on instead (a wrapped task item's checkbox); a
51
+ * taller one is a card, and the band already covers it.
52
+ */
53
+ export function dragHandleAnchorY(host) {
54
+ const content = host.querySelector(BLOCK_CONTENT_SELECTOR);
55
+ if (!content)
56
+ return null;
57
+ const hostRect = host.getBoundingClientRect();
58
+ const line = lineHeightOf(content);
59
+ const rect = paintedRect(content);
60
+ // A one-line block IS its own band, and the eye puts the grip level with the row rather
61
+ // than a hair above its middle — a list item, a task row, a rule.
62
+ const box = rect.height <= line * SINGLE_LINE ? rect : (markerRect(content, line) ?? bandRect(rect, line));
63
+ if (!box || box.height === 0)
64
+ return null;
65
+ const y = box.top + box.height / 2 - hostRect.top;
66
+ // A box painting outside the host (a scrolled or offscreen region) seats nothing.
67
+ return y >= 0 && y <= hostRect.height ? y : null;
68
+ }
69
+ function lineHeightOf(content) {
70
+ const line = parseFloat(getComputedStyle(content).lineHeight);
71
+ return Number.isFinite(line) && line > 0 ? line : 20;
72
+ }
73
+ /** The declared anchor, taken only while it is marker-sized and on the block's first line. */
74
+ function markerRect(content, line) {
75
+ const declared = content.matches(`[${DRAG_ANCHOR_ATTR}]`)
76
+ ? content
77
+ : content.querySelector(`[${DRAG_ANCHOR_ATTR}]`);
78
+ const rect = declared?.getBoundingClientRect();
79
+ if (!rect || rect.height === 0 || rect.height > line)
80
+ return null;
81
+ return rect.top - content.getBoundingClientRect().top > line ? null : rect;
82
+ }
83
+ /**
84
+ * The box the band measures from: the content's own, unless it leads with an atomic widget
85
+ * (a picture in a paragraph), whose margin would otherwise seat the grip above the picture and
86
+ * flush with its corner.
87
+ */
88
+ function paintedRect(content) {
89
+ const rect = content.getBoundingClientRect();
90
+ const widget = content.firstElementChild?.closest('[data-inline-widget]');
91
+ const widgetRect = widget?.getBoundingClientRect();
92
+ return widgetRect && widgetRect.height > 0 && widgetRect.top > rect.top ? widgetRect : rect;
93
+ }
94
+ /** The top line-height of a box; a box shorter than a line is its own band. */
95
+ function bandRect(rect, line) {
96
+ return new DOMRect(rect.x, rect.y, rect.width, Math.min(line, rect.height));
97
+ }
98
+ /**
99
+ * Svelte attachment for the handle element: places the grip once the block has laid out, and
100
+ * again on every pointerover inside its host, so it follows edits made while hovered.
101
+ *
102
+ * The mount placement is what makes the grip HITTABLE where it appears: it is its own hit
103
+ * target before any hover, so a target parked at the stylesheet's default while the grip would
104
+ * settle elsewhere is a target the pointer misses. One rAF-batched read per handle.
105
+ */
106
+ export function alignDragHandle(handle) {
107
+ const host = handle.parentElement;
108
+ if (!host)
109
+ return;
110
+ const grip = handle.firstElementChild;
111
+ if (!grip)
112
+ return;
113
+ // Inline `top` on the grip (px from the host's top to its centre; the stylesheet's
114
+ // translateY(-50%) does the centring) overrides the stylesheet's half-line default.
115
+ const place = () => {
116
+ const y = dragHandleAnchorY(host);
117
+ if (y !== null)
118
+ grip.style.top = `${y}px`;
119
+ };
120
+ const settled = requestAnimationFrame(place);
121
+ host.addEventListener('pointerover', place);
122
+ return () => {
123
+ cancelAnimationFrame(settled);
124
+ host.removeEventListener('pointerover', place);
125
+ };
126
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Editor-root focus attribution: which block host holds the caret. The path drives each
3
+ * windowing scope's per-level pin, so a scroll that pushes the caret off-screen never tears
4
+ * down native focus/IME; the `data-focused` attribute is what both preview modes' CSS key
5
+ * their focused-block reveal off. The installing `$effect`s stay in `Editor.svelte`.
6
+ */
7
+ import { type PresentationMode } from '../presentation-mode';
8
+ export interface FocusAttributionDeps {
9
+ /** A getter, never a value: every attribute write is gated on the mode in force. */
10
+ get mode(): PresentationMode;
11
+ }
12
+ export interface FocusAttribution {
13
+ /** `root` is the element the installing effect captured, not a live binding. */
14
+ install(root: HTMLElement): () => void;
15
+ /** Entering a preview mode marks the already-focused block, since no re-focus fires. */
16
+ applyForMode(): void;
17
+ getFocusedPath(): number[] | null;
18
+ }
19
+ export declare function createFocusAttribution(deps: FocusAttributionDeps): FocusAttribution;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Editor-root focus attribution: which block host holds the caret. The path drives each
3
+ * windowing scope's per-level pin, so a scroll that pushes the caret off-screen never tears
4
+ * down native focus/IME; the `data-focused` attribute is what both preview modes' CSS key
5
+ * their focused-block reveal off. The installing `$effect`s stay in `Editor.svelte`.
6
+ */
7
+ import { assertInvariant } from '../assert';
8
+ import { checkLandableCaret } from '../invariants/landable-caret';
9
+ import { isPreviewMode } from '../presentation-mode';
10
+ import { readBlockPath } from '../selection/path-lookup';
11
+ import { onRoot, removeAll } from './editor-root-listeners';
12
+ export function createFocusAttribution(deps) {
13
+ // Plain fields, never reactive: focusout fires mid-teardown during a structural commit,
14
+ // where a reactive write would trip state_unsafe_mutation.
15
+ let focusedPath = null;
16
+ let focusedHostEl = null;
17
+ // Mode-gated so source/reading DOM stays byte-identical.
18
+ function applyFocusedAttr() {
19
+ if (focusedHostEl && isPreviewMode(deps.mode)) {
20
+ focusedHostEl.setAttribute('data-focused', '');
21
+ }
22
+ else {
23
+ focusedHostEl?.removeAttribute('data-focused');
24
+ }
25
+ }
26
+ function setFocusedHost(host) {
27
+ if (focusedHostEl === host)
28
+ return;
29
+ focusedHostEl?.removeAttribute('data-focused');
30
+ focusedHostEl = host;
31
+ applyFocusedAttr();
32
+ }
33
+ function clear() {
34
+ focusedPath = null;
35
+ setFocusedHost(null);
36
+ }
37
+ function install(root) {
38
+ const onFocusIn = (e) => {
39
+ const host = e.target?.closest('[data-block-path]');
40
+ if (!host || !root.contains(host)) {
41
+ clear();
42
+ return;
43
+ }
44
+ setFocusedHost(host);
45
+ const path = readBlockPath(host);
46
+ focusedPath = path && path.length > 0 ? path : null;
47
+ // G1.33 at the seam every caret door crosses: a door seats a caret by focusing the
48
+ // surface, whoever minted it, so a consumer's own door inherits the guard here.
49
+ const landed = e.target;
50
+ if (landed instanceof HTMLElement) {
51
+ assertInvariant('landable-caret', () => checkLandableCaret(landed, deps.mode, path ?? []));
52
+ }
53
+ };
54
+ const onFocusOut = (e) => {
55
+ const next = e.relatedTarget;
56
+ if (next && root.contains(next))
57
+ return; // moving between blocks — keep the pin
58
+ clear();
59
+ };
60
+ return removeAll(onRoot(root, 'focusin', onFocusIn), onRoot(root, 'focusout', onFocusOut));
61
+ }
62
+ return {
63
+ install,
64
+ applyForMode: applyFocusedAttr,
65
+ getFocusedPath: () => focusedPath
66
+ };
67
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Editor-root geometry observers: the width and viewport-height watchers, the type-scale probe
3
+ * and the header-slot compensation. Each observes the element its installing `$effect`
4
+ * captured and returns the teardown; the reactive signals they feed (`widthVersion`,
5
+ * `typeScale`, `viewportHeightVersion`) stay in `Editor.svelte`, which is what the rest of the
6
+ * root reads. ResizeObserver's per-callback batching is the coalescing; no timer debounce (G4.4).
7
+ */
8
+ import type { UserScrollport } from '../cursor/scroll-ancestors';
9
+ import type { Scrollport } from '../cursor/scrollport';
10
+ /** Calls `onWidthChange` when `el`'s width moves. A height-only resize is ignored: only a
11
+ * re-wrap stales the measured heights. */
12
+ export declare function installWidthWatcher(el: HTMLElement, onWidthChange: () => void): () => void;
13
+ /** Calls `bump` on a height change of the resolved scrollport `target`. */
14
+ export declare function installViewportHeightWatcher(target: UserScrollport, bump: () => void): () => void;
15
+ export interface TypeScaleProbeDeps {
16
+ /** Read at each report, so the sub-percent guard compares against the scale in force. */
17
+ getScale(): number;
18
+ onScale(next: number): void;
19
+ }
20
+ /**
21
+ * The width watcher's sibling: a font-size move puts the height estimates off several-fold, so
22
+ * a document whose true height clears the activation watermark can fail to window at all. No
23
+ * other box in the root reports it, hence the `1em` probe; the scale is relative to the size
24
+ * `HEIGHT_ESTIMATES` were calibrated at.
25
+ */
26
+ export declare function installTypeScaleProbe(el: HTMLElement, deps: TypeScaleProbeDeps): () => void;
27
+ export interface HeaderSlotCompensationDeps {
28
+ el: HTMLElement;
29
+ port: Pick<Scrollport, 'scrollTop' | 'setScrollTop'>;
30
+ ownsScrollCorrection(): boolean;
31
+ revealHoldsScroll(): boolean;
32
+ }
33
+ /**
34
+ * The header slot's height lives outside the height model, so while the editor owns the
35
+ * correction a growing header would slide the document under the reader. Compensating from
36
+ * the SLOT's own resize composes with `correctAnchor` instead of double-correcting; a reveal
37
+ * already holding the scroll outranks it.
38
+ */
39
+ export declare function installHeaderSlotCompensation(deps: HeaderSlotCompensationDeps): () => void;