@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
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Where the body sits inside the block's source. The fence lines carry no glyph of their own, so
3
+ * a point measured against the rendered equation names a place in this span and nowhere else.
4
+ */
5
+ export declare function mathBodySpan(text: string): {
6
+ start: number;
7
+ end: number;
8
+ };
9
+ /**
10
+ * A `$$` block with no body LINE — `$$$$`, `$$\n$$`, a whitespace-only one-liner — has nowhere
11
+ * for a caret to sit once the fence lines hide, and Backspace has no byte it could mean. The
12
+ * completion every such block takes as its source is revealed: opener, one empty body line,
13
+ * closer, caret on that line. A block that already has a body line, blank or not, is left alone.
14
+ */
15
+ export declare function completeBareMathSource(text: string): {
16
+ text: string;
17
+ caret: number;
18
+ } | null;
19
+ export declare function renderMathSource(text: string): DocumentFragment;
@@ -0,0 +1,97 @@
1
+ /**
2
+ * The `$$` block's source as DOM, shaped like a code block's: each fence line in a
3
+ * `.md-fence-line` wrapper the marker-hiding modes collapse, the body as LaTeX highlight
4
+ * tokens. Text-preserving by construction — opener + body + closer is the input — so the
5
+ * offset walk and G1.28 hold. Anything not shaped like a fence paints as plain tokens.
6
+ */
7
+ import { highlightCode } from '../../plugin';
8
+ const FENCE = '$$';
9
+ function sliceMathSource(text) {
10
+ if (!text.startsWith(FENCE))
11
+ return { opener: '', body: text, closer: '' };
12
+ const firstNewline = text.indexOf('\n');
13
+ // One-line form: `$$x^2$$`.
14
+ if (firstNewline === -1) {
15
+ if (text.length >= 4 && text.endsWith(FENCE)) {
16
+ return { opener: FENCE, body: text.slice(2, -2), closer: FENCE };
17
+ }
18
+ return { opener: '', body: text, closer: '' };
19
+ }
20
+ if (text.slice(0, firstNewline) !== FENCE)
21
+ return { opener: '', body: text, closer: '' };
22
+ const opener = text.slice(0, firstNewline + 1);
23
+ const rest = text.slice(opener.length);
24
+ const lastNewline = rest.lastIndexOf('\n');
25
+ const lastLine = lastNewline === -1 ? rest : rest.slice(lastNewline + 1);
26
+ if (lastLine !== FENCE)
27
+ return { opener, body: rest, closer: '' };
28
+ if (lastNewline === -1)
29
+ return { opener, body: '', closer: FENCE };
30
+ // The newline before the closer belongs to the closer's line (the code block's rule), so
31
+ // collapsing that line leaves no blank line at the box's edge — unless the body is blank,
32
+ // where that newline IS the one line the caret can sit on.
33
+ const bodyWithEnding = rest.slice(0, lastNewline + 1);
34
+ if (!/\S/.test(bodyWithEnding))
35
+ return { opener, body: bodyWithEnding, closer: FENCE };
36
+ return { opener, body: rest.slice(0, lastNewline), closer: rest.slice(lastNewline) };
37
+ }
38
+ function fenceLine(text) {
39
+ const line = document.createElement('span');
40
+ line.className = 'md-fence-line';
41
+ const marker = document.createElement('span');
42
+ marker.className = 'md-marker md-fence';
43
+ marker.textContent = text.replace(/\n$/, '').replace(/^\n/, '');
44
+ if (text.startsWith('\n'))
45
+ line.appendChild(document.createTextNode('\n'));
46
+ line.appendChild(marker);
47
+ if (text.endsWith('\n'))
48
+ line.appendChild(document.createTextNode('\n'));
49
+ return line;
50
+ }
51
+ /**
52
+ * Where the body sits inside the block's source. The fence lines carry no glyph of their own, so
53
+ * a point measured against the rendered equation names a place in this span and nowhere else.
54
+ */
55
+ export function mathBodySpan(text) {
56
+ if (/^[ \t]*(?:`{3,}|~{3,})/.test(text)) {
57
+ const firstBreak = text.indexOf('\n');
58
+ if (firstBreak === -1)
59
+ return { start: text.length, end: text.length };
60
+ const closer = /(?:\r?\n)?[ \t]*(?:`{3,}|~{3,})[ \t]*\r?\n?$/.exec(text);
61
+ return { start: firstBreak + 1, end: closer ? closer.index : text.length };
62
+ }
63
+ const { opener, body } = sliceMathSource(text);
64
+ return { start: opener.length, end: opener.length + body.length };
65
+ }
66
+ /**
67
+ * A `$$` block with no body LINE — `$$$$`, `$$\n$$`, a whitespace-only one-liner — has nowhere
68
+ * for a caret to sit once the fence lines hide, and Backspace has no byte it could mean. The
69
+ * completion every such block takes as its source is revealed: opener, one empty body line,
70
+ * closer, caret on that line. A block that already has a body line, blank or not, is left alone.
71
+ */
72
+ export function completeBareMathSource(text) {
73
+ const { opener, body, closer } = sliceMathSource(text);
74
+ if (!opener || !closer)
75
+ return null;
76
+ if (body.includes('\n') || body.trim() !== '')
77
+ return null;
78
+ const completed = `${FENCE}\n\n${FENCE}`;
79
+ return { text: completed, caret: FENCE.length + 1 };
80
+ }
81
+ export function renderMathSource(text) {
82
+ const { opener, body, closer } = sliceMathSource(text);
83
+ const frag = document.createDocumentFragment();
84
+ if (opener)
85
+ frag.appendChild(fenceLine(opener));
86
+ frag.appendChild(highlightCode(body, 'latex'));
87
+ // An empty last body line has only the hidden closer after it; the anchor gives the caret
88
+ // that line (see `code-renderer.ts`).
89
+ if (closer.startsWith('\n') && body.endsWith('\n')) {
90
+ const anchor = document.createElement('br');
91
+ anchor.dataset.caretAnchor = 'closer';
92
+ frag.appendChild(anchor);
93
+ }
94
+ if (closer)
95
+ frag.appendChild(fenceLine(closer));
96
+ return frag;
97
+ }
@@ -5,6 +5,15 @@
5
5
  */
6
6
  import { type EditorPlugin } from '../../plugin';
7
7
  import { type MathRenderer } from './math-renderer';
8
- export declare function latexPlugin(options: {
8
+ import { type MathBlockLayout } from './math-layout';
9
+ export interface LatexPluginOptions {
9
10
  renderer: MathRenderer;
10
- }): EditorPlugin;
11
+ /**
12
+ * How a `$$` block opens for editing: `split` (source beside the preview, the default),
13
+ * `stacked` (preview below), or `source` (no preview). Definition-time, so it is the
14
+ * bare-install default; an editor's `{ plugin, options: { blockLayout } }` entry overrides it
15
+ * per instance. The block's own toggle cycles from whichever applies.
16
+ */
17
+ blockLayout?: MathBlockLayout;
18
+ }
19
+ export declare function latexPlugin(options: LatexPluginOptions): EditorPlugin;
@@ -6,8 +6,10 @@
6
6
  import { definePlugin, registerBlockComponent, defineBlockComponent, declaredPluginKind } from '../../plugin';
7
7
  import { registerMathInline, registerMathBlock, MATH_BLOCK, MATH_FENCE } from './latex-kind';
8
8
  import { setMathRenderer } from './math-renderer';
9
+ import { isMathBlockLayout } from './math-layout';
9
10
  import BlockMath from './BlockMath.svelte';
10
11
  export function latexPlugin(options) {
12
+ const blockLayout = isMathBlockLayout(options.blockLayout) ? options.blockLayout : 'split';
11
13
  return definePlugin({
12
14
  name: 'latex',
13
15
  setup() {
@@ -15,7 +17,7 @@ export function latexPlugin(options) {
15
17
  registerMathInline();
16
18
  // registerMathBlock co-registers the ```math fence kind; both render through BlockMath.
17
19
  registerMathBlock();
18
- const blockMath = defineBlockComponent(BlockMath);
20
+ const blockMath = defineBlockComponent(BlockMath, () => ({ blockLayout }));
19
21
  registerBlockComponent(declaredPluginKind(MATH_BLOCK), blockMath);
20
22
  registerBlockComponent(declaredPluginKind(MATH_FENCE), blockMath);
21
23
  }
@@ -6,8 +6,8 @@
6
6
  import 'katex/dist/katex.min.css';
7
7
  import type { MathRenderer } from './math-renderer';
8
8
  /**
9
- * `throwOnError: false` keeps a malformed formula from crashing the editor, but its fallback
10
- * renders the source verbatim, a bare raw-source strip A5 forbids: hence the `.katex-error`
11
- * detection and the legible message swapped in for it.
9
+ * `throwOnError: false` keeps a malformed formula from crashing the editor; its fallback is
10
+ * swapped for the source painted as an error — red, in the code face, the parser's message on
11
+ * hover — so a broken formula reads as what the author typed, not as a sentence about it.
12
12
  */
13
13
  export declare const katexRenderer: MathRenderer;
@@ -8,9 +8,9 @@ import katex from 'katex';
8
8
  // a `.katex-mathml` a11y tree this CSS clips to 1px, and unloaded every equation paints twice.
9
9
  import 'katex/dist/katex.min.css';
10
10
  /**
11
- * `throwOnError: false` keeps a malformed formula from crashing the editor, but its fallback
12
- * renders the source verbatim, a bare raw-source strip A5 forbids: hence the `.katex-error`
13
- * detection and the legible message swapped in for it.
11
+ * `throwOnError: false` keeps a malformed formula from crashing the editor; its fallback is
12
+ * swapped for the source painted as an error — red, in the code face, the parser's message on
13
+ * hover — so a broken formula reads as what the author typed, not as a sentence about it.
14
14
  */
15
15
  export const katexRenderer = (source, { display }) => {
16
16
  const container = document.createElement('span');
@@ -22,13 +22,20 @@ export const katexRenderer = (source, { display }) => {
22
22
  const errorSpan = container.querySelector('.katex-error');
23
23
  if (errorSpan) {
24
24
  const message = (errorSpan.getAttribute('title') ?? 'invalid LaTeX').replace(/^ParseError:\s*/, '');
25
- return { dom: buildErrorNode(message), error: message };
25
+ return { dom: buildErrorNode(source, message), error: message };
26
26
  }
27
27
  return { dom: container };
28
28
  };
29
- function buildErrorNode(message) {
29
+ function buildErrorNode(source, message) {
30
30
  const span = document.createElement('span');
31
31
  span.className = 'math-error';
32
- span.textContent = `Math error: ${message}`;
32
+ span.textContent = source;
33
+ span.title = message;
34
+ // Inline style rather than a stylesheet: the span lands inside any prose block, where no
35
+ // plugin sheet is guaranteed to be mounted.
36
+ span.style.color = 'var(--color-error, #d03025)';
37
+ span.style.fontFamily = 'var(--font-code, ui-monospace, monospace)';
38
+ span.style.fontSize = '0.9em';
39
+ span.style.cursor = 'help';
33
40
  return span;
34
41
  }
@@ -63,15 +63,15 @@ export interface ListWindowing {
63
63
  recordMeasuredChild(index: number, id: string, height: number): void;
64
64
  /** A PROPAGATED child-container subtotal: oracle + model slot, addressed by index. No anchor correction. */
65
65
  setChildSubtotal(index: number, total: number): void;
66
- /** Enroll a child in this scope's batched measure pass; returns an unregister fn to call on
67
- * the child's unmount. The scope reads ALL pending children before applying ANY write, so a
68
- * fling that mounts many costs one reflow. */
66
+ /** Enroll a child in this scope's batched measure pass, read after the flush that registered
67
+ * it; returns an unregister fn to call on the child's unmount. The scope reads ALL pending
68
+ * children before applying ANY write, so a fling that mounts many costs one reflow. */
69
69
  registerChild(id: string, child: MeasureEntry): () => void;
70
70
  /** Re-measure ONE registered child immediately (its content height changed on edit). */
71
71
  measureChildNow(id: string): void;
72
- /** ResizeObserver path: O(1)-gate `observedHeight` against the recorded height and
73
- * re-measure only on a genuine post-mount change, so the no-op mount resize on a fling
74
- * costs no DOM read. */
72
+ /** ResizeObserver path: O(1)-gate `observedHeight` against the height this scope last
73
+ * applied and re-measure only on a genuine post-mount change, so the no-op mount resize
74
+ * on a fling costs no DOM read. */
75
75
  measureChildOnResize(id: string, observedHeight: number): void;
76
76
  /**
77
77
  * True when the scroll position IS the reveal target's requested placement. Asked by
@@ -93,8 +93,8 @@ export interface ListWindowing {
93
93
  * Resize-gate decision (pure, unit-tested). Keyed on the height DIFFERING from what is
94
94
  * recorded, never on which callback delivered it: a cached remount can report the grown
95
95
  * size in the very first callback, so a callback-order heuristic would drop it.
96
- * `recorded === undefined` means the batched mount pass hasn't measured this block yet —
97
- * defer to it rather than racing a lone read in on a fling-dirtied layout.
96
+ * `recorded === undefined` means the scope has applied no height for this block yet —
97
+ * defer to the batched pass rather than racing a lone read in on a fling-dirtied layout.
98
98
  */
99
99
  export declare function shouldRemeasureOnResize(recorded: number | undefined, observed: number): boolean;
100
100
  export declare function createListWindowing(deps: ListWindowingDeps): ListWindowing;
@@ -14,8 +14,8 @@ import { runMeasureBatch } from './measure-batch';
14
14
  * Resize-gate decision (pure, unit-tested). Keyed on the height DIFFERING from what is
15
15
  * recorded, never on which callback delivered it: a cached remount can report the grown
16
16
  * size in the very first callback, so a callback-order heuristic would drop it.
17
- * `recorded === undefined` means the batched mount pass hasn't measured this block yet —
18
- * defer to it rather than racing a lone read in on a fling-dirtied layout.
17
+ * `recorded === undefined` means the scope has applied no height for this block yet —
18
+ * defer to the batched pass rather than racing a lone read in on a fling-dirtied layout.
19
19
  */
20
20
  export function shouldRemeasureOnResize(recorded, observed) {
21
21
  if (observed <= 0 || recorded === undefined)
@@ -59,7 +59,7 @@ export function createListWindowing(deps) {
59
59
  let heightVersion = $state(0);
60
60
  // One scope-owned batched pass rather than a per-child effect, which would interleave a
61
61
  // layout read with the prior child's write → one forced reflow per mounted block on a
62
- // fling. `pending` holds ids awaiting their first measure; the batch effect drains it.
62
+ // fling. `pending` holds ids awaiting their first measure; registration drains it.
63
63
  const registry = new Map();
64
64
  const pending = new Set();
65
65
  /**
@@ -123,6 +123,20 @@ export function createListWindowing(deps) {
123
123
  placeRevealTarget();
124
124
  return true;
125
125
  }
126
+ /**
127
+ * Which block this correction holds still: the top-of-viewport one by default, the FOCUSED
128
+ * one when it sits at or below the fold. Clicking a construct that reveals its source (a
129
+ * math block, a paragraph holding inline math) resizes that block and folds whichever was
130
+ * revealed before it; anchored on the viewport top, both slide the clicked block out from
131
+ * under the pointer. Anchoring on the focused block holds its start offset instead, so it
132
+ * stays put and only the content after it reflows.
133
+ */
134
+ function anchorIndexFor(topIndex) {
135
+ const pinned = pinnedIndex();
136
+ if (pinned === null || pinned < topIndex || pinned >= model.size)
137
+ return topIndex;
138
+ return pinned;
139
+ }
126
140
  // Hold the anchor block's screen position across a height mutation that would otherwise
127
141
  // slide the visible content (VR-2) — native `overflow-anchor` is off wherever this runs, so
128
142
  // nothing else holds the line. The delta comes from the Fenwick model, not
@@ -134,7 +148,7 @@ export function createListWindowing(deps) {
134
148
  if (reassertRevealAnchor(mutate))
135
149
  return;
136
150
  const port = deps.getPort();
137
- const anchorIndex = model.indexAtOffset(localScrollTop());
151
+ const anchorIndex = anchorIndexFor(model.indexAtOffset(localScrollTop()));
138
152
  const before = model.offsetOf(anchorIndex);
139
153
  mutate();
140
154
  const delta = model.offsetOf(anchorIndex) - before;
@@ -258,22 +272,23 @@ export function createListWindowing(deps) {
258
272
  $effect(() => {
259
273
  void heightVersion;
260
274
  const el = deps.getOwnEl?.();
261
- if (!el || !deps.reportSelfHeight)
275
+ const report = deps.reportSelfHeight;
276
+ if (!el || !report)
262
277
  return;
263
- const h = el.getBoundingClientRect().height;
264
- if (h > 0 && Math.abs(h - reportedSelfHeight) >= 1) {
265
- reportedSelfHeight = h;
266
- deps.reportSelfHeight(h);
267
- }
268
- });
269
- // The batched measure pass for NEWLY-MOUNTED children, keyed on the EFFECTIVE window: the
270
- // raw result can be identical across a collapse expand, stranding remounted children in
271
- // `pending` until the next scroll. Registration deliberately does NOT bump reactive
272
- // state — the mount that registers a child already moved the window, so a per-child
273
- // trigger would re-enter this effect O(children) times and trip the update-depth guard.
274
- $effect(() => {
275
- void effectiveWindow;
276
- untrack(() => flushMeasurements());
278
+ // After the flush, like the batch: at this effect's first run the children are unrendered.
279
+ let live = true;
280
+ void tick().then(() => {
281
+ if (!live)
282
+ return;
283
+ const h = el.getBoundingClientRect().height;
284
+ if (h > 0 && Math.abs(h - reportedSelfHeight) >= 1) {
285
+ reportedSelfHeight = h;
286
+ report(h);
287
+ }
288
+ });
289
+ return () => {
290
+ live = false;
291
+ };
277
292
  });
278
293
  // The UNCORRECTED batch: the caller owns the anchor correction, because nesting a second
279
294
  // one inside an outer `mutate` double-counts the delta.
@@ -348,11 +363,21 @@ export function createListWindowing(deps) {
348
363
  if (!skipWhileHostAnchors(write) && !reassertRevealAnchor(write))
349
364
  write();
350
365
  },
351
- // Enroll without touching reactive state: the mount that registers this child already
352
- // moved the window, which re-runs the batch effect to drain `pending`.
366
+ // Read after the flush that mounted the child, not inside it: content can land later in
367
+ // that flush (an inline widget's root flushes after this one), and an empty-host read is
368
+ // a scroll correction the observer undoes a frame later. Still ahead of paint.
353
369
  registerChild(id, child) {
354
- registry.set(id, child);
370
+ const entry = {
371
+ readHeight: child.readHeight,
372
+ applyHeight: (h) => {
373
+ entry.applied = h;
374
+ child.applyHeight(h);
375
+ },
376
+ applied: undefined
377
+ };
378
+ registry.set(id, entry);
355
379
  pending.add(id);
380
+ void tick().then(flushMeasurements);
356
381
  return () => {
357
382
  registry.delete(id);
358
383
  pending.delete(id);
@@ -362,11 +387,11 @@ export function createListWindowing(deps) {
362
387
  measureChildNow(id) {
363
388
  measureOne(id);
364
389
  },
365
- // ResizeObserver path for async growth. The gate reads the oracle's recorded height
390
+ // ResizeObserver path for async growth. The gate reads what this scope last applied
366
391
  // (O(1), no DOM), so the no-op mount resize a fling fires for every newly-mounted
367
392
  // block returns without a rect read on the spacer-dirtied layout (VR-4).
368
393
  measureChildOnResize(id, observedHeight) {
369
- if (shouldRemeasureOnResize(deps.oracle.measured(id), observedHeight))
394
+ if (shouldRemeasureOnResize(registry.get(id)?.applied, observedHeight))
370
395
  measureOne(id);
371
396
  },
372
397
  revealHoldsScroll() {
@@ -22,10 +22,18 @@ export interface CompletionResult {
22
22
  export interface BlockCompleter {
23
23
  /** Attempt to complete `line`; null declines, as does a claim whose lines would paint nothing. */
24
24
  tryComplete(line: string): CompletionResult | null;
25
+ /**
26
+ * Also consulted as the line is TYPED, not only at Enter — for a claim that can only ever mean
27
+ * one thing the moment it is complete (a lone `$$`), so the block forms as a ` ``` ` fence does.
28
+ * Off by default: a table's header row is a prefix of a longer row the user may still be typing.
29
+ */
30
+ onType?: boolean;
25
31
  }
26
32
  export declare function registerBlockCompleter(kind: AnyBlockKind, completer: BlockCompleter): void;
27
33
  /** The first claim on `line`, or null when no registered completer takes it. */
28
34
  export declare function completeTypedLine(line: string): CompletionResult | null;
35
+ /** The first claim on `line` among the completers that answer as the line is typed. */
36
+ export declare function completeLineOnType(line: string): CompletionResult | null;
29
37
  /** Whether `kind` already owns a completer — the probe a registrar re-run reads before it
30
38
  * registers, so a re-installed plugin never trips the register-once throw. */
31
39
  export declare function isBlockCompleterRegistered(kind: string): boolean;
@@ -33,6 +33,17 @@ export function completeTypedLine(line) {
33
33
  }
34
34
  return null;
35
35
  }
36
+ /** The first claim on `line` among the completers that answer as the line is typed. */
37
+ export function completeLineOnType(line) {
38
+ for (const completer of ordered()) {
39
+ if (!completer.onType)
40
+ continue;
41
+ const claim = completer.tryComplete(line);
42
+ if (claim)
43
+ return claim;
44
+ }
45
+ return null;
46
+ }
36
47
  /** Whether `kind` already owns a completer — the probe a registrar re-run reads before it
37
48
  * registers, so a re-installed plugin never trips the register-once throw. */
38
49
  export function isBlockCompleterRegistered(kind) {
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The block context menu's vocabulary: what a right-click on a block of a given kind offers.
3
+ * A kind's own providers come first, then the ones registered for every kind (`'*'`: the
4
+ * editor's copy, replace and remove rows). Providers are consulted on every open, so they read
5
+ * the block as it is. The defaults register at bootstrap; a plugin registers through the barrel.
6
+ */
7
+ export declare const EVERY_KIND = "*";
8
+ import type { NodeView } from '../core/node-views';
9
+ export interface BlockActionContext {
10
+ node: NodeView;
11
+ /** The block's path from the document root. Top-level blocks only, for now. */
12
+ path: number[];
13
+ /** Remove the block. */
14
+ deleteBlock(): Promise<void>;
15
+ /** Replace the block's bytes wholesale; the result reparses to whatever those bytes are. */
16
+ replaceRaw(raw: string): Promise<void>;
17
+ }
18
+ export interface BlockContextAction {
19
+ id: string;
20
+ label: string;
21
+ /** A glyph name the menu knows (`components/menu/MenuIcon.svelte`); absent draws none. */
22
+ icon?: string;
23
+ danger?: boolean;
24
+ run(ctx: BlockActionContext): void | Promise<void>;
25
+ }
26
+ export type BlockContextActionProvider = (node: NodeView, path: number[]) => BlockContextAction[];
27
+ /** Add a provider for `kind`, or for every kind with `EVERY_KIND`. Several may stack. */
28
+ export declare function registerBlockContextActions(kind: string, provider: BlockContextActionProvider): void;
29
+ export declare function blockContextActionsFor(node: NodeView, path: number[]): BlockContextAction[];
30
+ /** Test-only. */
31
+ export declare function __resetBlockContextActionsForTests(): void;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The block context menu's vocabulary: what a right-click on a block of a given kind offers.
3
+ * A kind's own providers come first, then the ones registered for every kind (`'*'`: the
4
+ * editor's copy, replace and remove rows). Providers are consulted on every open, so they read
5
+ * the block as it is. The defaults register at bootstrap; a plugin registers through the barrel.
6
+ */
7
+ export const EVERY_KIND = '*';
8
+ const providers = new Map();
9
+ /** Add a provider for `kind`, or for every kind with `EVERY_KIND`. Several may stack. */
10
+ export function registerBlockContextActions(kind, provider) {
11
+ const list = providers.get(kind) ?? [];
12
+ list.push(provider);
13
+ providers.set(kind, list);
14
+ }
15
+ export function blockContextActionsFor(node, path) {
16
+ const own = providers.get(node.kind) ?? [];
17
+ const every = providers.get(EVERY_KIND) ?? [];
18
+ return [...own, ...every].flatMap((provider) => provider(node, path));
19
+ }
20
+ /** Test-only. */
21
+ export function __resetBlockContextActionsForTests() {
22
+ providers.clear();
23
+ }
@@ -17,7 +17,8 @@ export function reconcileFenceWrite(input) {
17
17
  const { fence, mode } = input;
18
18
  if (!fence.closed && mode === 'authored')
19
19
  return { display: input.display, caret: input.caret };
20
- return escalateFenceRuns({ ...input, ...sanitizeInfoString(input) });
20
+ const unglued = separateGluedCloser(input);
21
+ return escalateFenceRuns({ ...input, ...sanitizeInfoString({ ...input, ...unglued }) });
21
22
  }
22
23
  /**
23
24
  * A whole fencedCode `raw` made legal — the kind's `normalizeRawWrite`. Literal by
@@ -166,6 +167,35 @@ function countDroppedBefore(info, infoStart, caret) {
166
167
  }
167
168
  return dropped;
168
169
  }
170
+ /**
171
+ * Un-glue a closer the write landed on top of. A fence with NO body line — ```` ```\n``` ```` —
172
+ * has nothing between its two runs, so the body's only caret seat IS the closer's line start,
173
+ * and the first character typed there arrives ahead of the closer run: ```` ```\nAB``` ````,
174
+ * which no longer reads as closed and shows the run as body text. The fence says it is closed
175
+ * and the display no longer carries a closer line, so the write broke it; the missing line
176
+ * ending is what puts it back.
177
+ */
178
+ function separateGluedCloser(input) {
179
+ const { display, caret, fence } = input;
180
+ if (!fence.closed)
181
+ return { display, caret };
182
+ const lines = display.split('\n');
183
+ // A closer still on its own line needs nothing; so does a display too short to hold one.
184
+ if (lines.length < 2 || lastCloserIndex(lines, fence) >= 1)
185
+ return { display, caret };
186
+ const run = new RegExp(`[${fence.marker}]{${fence.length},}[ \t]*$`).exec(lines[lines.length - 1]);
187
+ // `index === 0` is a bare run that some other rule owns, not a glued one.
188
+ if (!run || run.index === 0)
189
+ return { display, caret };
190
+ const at = display.length - run[0].length;
191
+ const ending = display.includes('\r\n') ? '\r\n' : '\n';
192
+ return {
193
+ display: display.slice(0, at) + ending + display.slice(at),
194
+ // The caret sits at the typed text's end, which is where the ending is inserted: it
195
+ // stays put, and only a caret already inside the closer run rides the shift.
196
+ caret: caret > at ? caret + ending.length : caret
197
+ };
198
+ }
169
199
  function escalateFenceRuns(input) {
170
200
  const { display, caret, fence } = input;
171
201
  const lines = display.split('\n');
@@ -39,7 +39,7 @@ export interface OperationDetailMap {
39
39
  parentItemIdx?: number;
40
40
  nestedItemIdx?: number;
41
41
  } | {
42
- source: 'paste-dispatch' | 'paste-dispatch-table-cell' | 'cross-block-paste-whole-table';
42
+ source: 'paste-dispatch' | 'paste-dispatch-table-cell' | 'cross-block-covered-block';
43
43
  };
44
44
  paste: {
45
45
  count: number;
@@ -70,6 +70,13 @@ export interface OperationDetailMap {
70
70
  colIdx: number;
71
71
  side: 'left' | 'right';
72
72
  };
73
+ /** A grid pasted from `rowIdx`/`colIdx`, the table grown to hold it. */
74
+ tablePasteGrid: {
75
+ rowIdx: number;
76
+ colIdx: number;
77
+ rows: number;
78
+ cols: number;
79
+ };
73
80
  tableDeleteColumn: {
74
81
  colIdx: number;
75
82
  crossBlock?: true;
@@ -15,6 +15,12 @@ export const HARDCODED_CHORD_SITES = [
15
15
  keys: [],
16
16
  note: 'Mod-click link activation — no keystroke is consumed.'
17
17
  },
18
+ {
19
+ file: 'components/editor-root-keydown.ts',
20
+ chords: [],
21
+ keys: ['Escape'],
22
+ note: 'Escape closes the find bar (a reserved UI chord, enumerated). The modifier read is a REFUSAL: an unchorded character over a live range goes to the range’s text door, a chorded one to the arms below it.'
23
+ },
18
24
  {
19
25
  file: 'components/editor-root-listeners.ts',
20
26
  chords: [],
@@ -45,8 +51,8 @@ export const HARDCODED_CHORD_SITES = [
45
51
  {
46
52
  file: 'components/blocks/editable-leaf.ts',
47
53
  chords: [],
48
- keys: ['Enter'],
49
- note: 'Shift-click gate on the rendered surface — a pointer read.'
54
+ keys: ['Backspace', 'Enter'],
55
+ note: "Shift-click gate on the rendered surface — a pointer read; plain Backspace (no modifier) at the top of an empty painted source deletes the block, as the code block does. The reveal's own undo resolves its chord through the keymap, so it claims none here."
50
56
  },
51
57
  {
52
58
  file: 'components/link-card/LinkCard.svelte',
@@ -54,6 +60,12 @@ export const HARDCODED_CHORD_SITES = [
54
60
  keys: ['Enter', 'K', 'Tab', 'k'],
55
61
  note: "Backwards step of the open card's focus trap, plus the entry chord swallowed as a no-op where the focus already is — the kind keymaps claim it everywhere else. Escape lives on the host, which must also close a card the document still holds the caret for."
56
62
  },
63
+ {
64
+ file: 'components/menu/BlockMenu.svelte',
65
+ chords: [],
66
+ keys: ['ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'Enter', 'Escape'],
67
+ note: 'The open block menu steps and picks; the modifier read is the guard that leaves every chord to the editor.'
68
+ },
57
69
  {
58
70
  file: 'components/blocks/table/TableActionMenu.svelte',
59
71
  chords: ['Shift+Tab'],
@@ -115,8 +127,17 @@ export const HARDCODED_CHORD_SITES = [
115
127
  {
116
128
  file: 'components/blocks/text/widget-interaction.ts',
117
129
  chords: ['Shift+ArrowLeft', 'Shift+ArrowRight'],
118
- keys: ['Arrow*', 'ArrowLeft', 'ArrowRight', 'Backspace', 'Delete', 'Escape'],
119
- note: 'Extends a selection into a widget. A modified arrow is also swallowed while a widget is selected, but that is a caret-coherence guard in a transient state, not a binding.'
130
+ keys: [
131
+ 'Arrow*',
132
+ 'ArrowDown',
133
+ 'ArrowLeft',
134
+ 'ArrowRight',
135
+ 'ArrowUp',
136
+ 'Backspace',
137
+ 'Delete',
138
+ 'Escape'
139
+ ],
140
+ note: 'Extends a selection into a widget. A modified arrow is also swallowed while a widget is selected, but that is a caret-coherence guard in a transient state, not a binding; a plain vertical arrow seats a caret and declines.'
120
141
  },
121
142
  {
122
143
  file: 'components/image/ImageResizeHandles.svelte',
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * The tableCell raw-write rule, declared on the kind as `normalizeRawWrite` and applied at the
3
- * write sink (`tree-operations/node-ops.updateNodeContent`). A cell's bytes are joined verbatim
3
+ * write sink (`tree-operations/content-write.updateNodeContent`). A cell's bytes are joined verbatim
4
4
  * into its row, and the parser truncates a row reparsing wider than the delimiter's column
5
5
  * count, so a bare `|` or line break reaching a cell raw deletes the last column's content.
6
6
  * Both passes are prefix-composable, which is what makes `escapedCellOffset` an exact caret image.
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * The tableCell raw-write rule, declared on the kind as `normalizeRawWrite` and applied at the
3
- * write sink (`tree-operations/node-ops.updateNodeContent`). A cell's bytes are joined verbatim
3
+ * write sink (`tree-operations/content-write.updateNodeContent`). A cell's bytes are joined verbatim
4
4
  * into its row, and the parser truncates a row reparsing wider than the delimiter's column
5
5
  * count, so a bare `|` or line break reaching a cell raw deletes the last column's content.
6
6
  * Both passes are prefix-composable, which is what makes `escapedCellOffset` an exact caret image.
@@ -26,10 +26,11 @@ export function blockAtPoint(editorRoot, clientX, clientY) {
26
26
  path,
27
27
  // A cell-addressed kind's first contenteditable is one of its CELLS, so declaring
28
28
  // the drag hook withdraws the block-level surface rather than offering that. The
29
- // whole-block editing host is chrome, not characters, so it is excluded too.
29
+ // whole-block editing host is chrome, not characters, so it is excluded too, as is
30
+ // the selection overlay (`contenteditable="false"`, mounted while a range is live).
30
31
  charSurface: dragHitTest
31
32
  ? null
32
- : wrapper.querySelector(`[contenteditable]:not([${WHOLE_BLOCK_INPUT_ATTR}])`),
33
+ : wrapper.querySelector(`[contenteditable]:not([${WHOLE_BLOCK_INPUT_ATTR}]):not(.selection-overlay)`),
33
34
  foreignDragHitTest: dragHitTest && ((cx, cy) => dragHitTest(wrapper, cx, cy)),
34
35
  caretTargetAtPoint: caretTarget && ((cx, cy) => caretTarget(wrapper, cx, cy))
35
36
  };
@@ -6,7 +6,7 @@
6
6
  * order against the other endpoint, never the anchor/focus role, which `normalize` reorders later.
7
7
  */
8
8
  import { displayLength, snapToScalarBoundary } from '../core/lines';
9
- import { isBlockNode, nodeAt } from '../tree-operations/node-ops';
9
+ import { isBlockNode, nodeAt } from '../tree-operations/node-primitives';
10
10
  import { isWholeBlockUnit } from '../schema/whole-block-unit';
11
11
  import { comparePaths } from './path-math';
12
12
  import { isWholeBlockEndpoint } from './primitives';