@voithos-labs/aragonite 0.10.2 → 0.10.4

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 (300) hide show
  1. package/README.md +6 -21
  2. package/THIRD-PARTY-NOTICES.md +25 -0
  3. package/dist/a11y-strings.d.ts +19 -0
  4. package/dist/a11y-strings.js +19 -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 +37 -24
  9. package/dist/components/BlockHost.svelte +18 -11
  10. package/dist/components/Editor.svelte +434 -198
  11. package/dist/components/Editor.svelte.d.ts +1 -1
  12. package/dist/components/SelectionOverlay.svelte +25 -17
  13. package/dist/components/SelectionOverlay.svelte.d.ts +1 -1
  14. package/dist/components/TailInsert.svelte +107 -0
  15. package/dist/components/TailInsert.svelte.d.ts +17 -0
  16. package/dist/components/block-content-selector.d.ts +6 -2
  17. package/dist/components/block-content-selector.js +6 -2
  18. package/dist/components/blocks/ThematicBreakBlock.svelte +19 -6
  19. package/dist/components/blocks/ThematicBreakBlock.svelte.d.ts +1 -0
  20. package/dist/components/blocks/code/CodeBlock.svelte +211 -30
  21. package/dist/components/blocks/code/CodeBlockRail.svelte +704 -0
  22. package/dist/components/blocks/code/CodeBlockRail.svelte.d.ts +26 -0
  23. package/dist/components/blocks/code/code-bootstrap.js +16 -0
  24. package/dist/components/blocks/code/code-context-actions.d.ts +1 -0
  25. package/dist/components/blocks/code/code-context-actions.js +24 -0
  26. package/dist/components/blocks/code/code-fence-exit.d.ts +15 -0
  27. package/dist/components/blocks/code/code-fence-exit.js +26 -0
  28. package/dist/components/blocks/code/code-languages.d.ts +6 -0
  29. package/dist/components/blocks/code/code-languages.js +22 -3
  30. package/dist/components/blocks/code/code-renderer.js +11 -0
  31. package/dist/components/blocks/directive/DirectiveContainerBlock.svelte +1 -1
  32. package/dist/components/blocks/editable-leaf.d.ts +37 -6
  33. package/dist/components/blocks/editable-leaf.js +248 -30
  34. package/dist/components/blocks/editable-surface.d.ts +9 -0
  35. package/dist/components/blocks/editable-surface.js +22 -3
  36. package/dist/components/blocks/list/ListBlock.svelte +1 -0
  37. package/dist/components/blocks/list/ListItemBlock.svelte +14 -5
  38. package/dist/components/blocks/list/ListItemBlock.svelte.d.ts +2 -0
  39. package/dist/components/blocks/list/task-checkbox.d.ts +2 -0
  40. package/dist/components/blocks/list/task-checkbox.js +11 -2
  41. package/dist/components/blocks/surface-wiring.svelte.d.ts +4 -0
  42. package/dist/components/blocks/surface-wiring.svelte.js +8 -1
  43. package/dist/components/blocks/table/TableActionMenu.svelte +210 -86
  44. package/dist/components/blocks/table/TableActionMenu.svelte.d.ts +5 -0
  45. package/dist/components/blocks/table/TableBlock.svelte +205 -186
  46. package/dist/components/blocks/table/TableBlock.svelte.d.ts +1 -0
  47. package/dist/components/blocks/table/TableCellBlock.svelte +87 -22
  48. package/dist/components/blocks/table/TableRowBlock.svelte +4 -18
  49. package/dist/components/blocks/table/TableRowBlock.svelte.d.ts +0 -5
  50. package/dist/components/blocks/table/cell-clipboard.d.ts +10 -0
  51. package/dist/components/blocks/table/cell-clipboard.js +34 -1
  52. package/dist/components/blocks/table/cell-keydown-plan.d.ts +1 -1
  53. package/dist/components/blocks/table/cell-keydown-plan.js +3 -1
  54. package/dist/components/blocks/table/table-cell-paste.js +2 -1
  55. package/dist/components/blocks/table/table-menu-model.d.ts +35 -8
  56. package/dist/components/blocks/table/table-menu-model.js +36 -17
  57. package/dist/components/blocks/text/TextEditableBlock.svelte +58 -11
  58. package/dist/components/blocks/text/click-snap-guard.d.ts +3 -0
  59. package/dist/components/blocks/text/click-snap-guard.js +8 -0
  60. package/dist/components/blocks/text/delimiter-autopair.d.ts +71 -0
  61. package/dist/components/blocks/text/delimiter-autopair.js +216 -0
  62. package/dist/components/blocks/text/edge-policy-dispatch.d.ts +4 -0
  63. package/dist/components/blocks/text/edge-policy-dispatch.js +89 -20
  64. package/dist/components/blocks/text/live-selection-edit.d.ts +5 -5
  65. package/dist/components/blocks/text/live-selection-edit.js +37 -7
  66. package/dist/components/blocks/text/text-clipboard.js +2 -2
  67. package/dist/components/blocks/text/text-keydown.d.ts +7 -1
  68. package/dist/components/blocks/text/text-keydown.js +11 -1
  69. package/dist/components/blocks/text/text-render.d.ts +1 -1
  70. package/dist/components/blocks/text/text-render.js +3 -1
  71. package/dist/components/blocks/text/widget-interaction.d.ts +3 -0
  72. package/dist/components/blocks/text/widget-interaction.js +181 -37
  73. package/dist/components/drag-handle.d.ts +41 -0
  74. package/dist/components/drag-handle.js +134 -0
  75. package/dist/components/editor-root-focus.d.ts +19 -0
  76. package/dist/components/editor-root-focus.js +67 -0
  77. package/dist/components/editor-root-geometry.d.ts +39 -0
  78. package/dist/components/editor-root-geometry.js +91 -0
  79. package/dist/components/editor-root-keydown.d.ts +1 -1
  80. package/dist/components/editor-root-keydown.js +12 -3
  81. package/dist/components/editor-root-listeners.d.ts +3 -6
  82. package/dist/components/editor-root-listeners.js +3 -24
  83. package/dist/components/editor-root-mode-flip.d.ts +36 -0
  84. package/dist/components/editor-root-mode-flip.js +92 -0
  85. package/dist/components/image/ImageOverlayHost.svelte +14 -6
  86. package/dist/components/image/ImageProperties.svelte +512 -70
  87. package/dist/components/image/ImageProperties.svelte.d.ts +6 -1
  88. package/dist/components/image/ImageResizeHandles.svelte +67 -34
  89. package/dist/components/image/image-crop.d.ts +39 -0
  90. package/dist/components/image/image-crop.js +74 -0
  91. package/dist/components/image/image-edit-commit.d.ts +1 -0
  92. package/dist/components/image/image-edit-commit.js +21 -5
  93. package/dist/components/image/image-source-bytes.js +10 -3
  94. package/dist/components/image/image-widget-editing.js +1 -0
  95. package/dist/components/image/widget-dom.js +5 -1
  96. package/dist/components/link-card/link-card-commit.js +1 -1
  97. package/dist/components/lrd-map-gate.js +1 -1
  98. package/dist/components/menu/BlockMenu.svelte +315 -0
  99. package/dist/components/menu/BlockMenu.svelte.d.ts +34 -0
  100. package/dist/components/menu/MenuIcon.svelte +153 -0
  101. package/dist/components/menu/MenuIcon.svelte.d.ts +51 -0
  102. package/dist/components/menu/SelectionToolbar.svelte +385 -0
  103. package/dist/components/menu/SelectionToolbar.svelte.d.ts +16 -0
  104. package/dist/components/menu/clipboard-actions.d.ts +12 -0
  105. package/dist/components/menu/clipboard-actions.js +42 -0
  106. package/dist/components/menu/default-context-actions.d.ts +15 -0
  107. package/dist/components/menu/default-context-actions.js +76 -0
  108. package/dist/components/menu/flyout-placement.d.ts +6 -0
  109. package/dist/components/menu/flyout-placement.js +25 -0
  110. package/dist/core/inline/format-toggle.d.ts +12 -4
  111. package/dist/core/inline/format-toggle.js +94 -40
  112. package/dist/core/inline/image-dimensions.d.ts +3 -0
  113. package/dist/core/inline/image-dimensions.js +46 -10
  114. package/dist/core/inline/inline-widgets.d.ts +19 -0
  115. package/dist/core/inline/inline-widgets.js +5 -0
  116. package/dist/core/inline/scan/brackets.js +1 -0
  117. package/dist/core/inline/scan/plugin-syntax.d.ts +8 -0
  118. package/dist/core/inline/scan/plugin-syntax.js +15 -1
  119. package/dist/core/inline/transparency.js +3 -3
  120. package/dist/core/inline-render.d.ts +6 -0
  121. package/dist/core/inline-render.js +32 -0
  122. package/dist/core/nodes.d.ts +14 -0
  123. package/dist/cursor/edge-affinity.js +2 -1
  124. package/dist/cursor/height-oracle.d.ts +2 -3
  125. package/dist/cursor/height-oracle.js +0 -1
  126. package/dist/cursor/overlay-remeasure.js +8 -0
  127. package/dist/cursor/reveal-source.js +7 -2
  128. package/dist/cursor/scroll-hold.d.ts +10 -0
  129. package/dist/cursor/scroll-hold.js +22 -0
  130. package/dist/cursor/scrollport.d.ts +9 -0
  131. package/dist/cursor/scrollport.js +30 -1
  132. package/dist/cursor/visual-lines.d.ts +5 -4
  133. package/dist/cursor/visual-lines.js +56 -11
  134. package/dist/cursor/widget-edge-snap.d.ts +28 -0
  135. package/dist/cursor/widget-edge-snap.js +38 -0
  136. package/dist/cursor/widget-offset.d.ts +9 -0
  137. package/dist/cursor/widget-offset.js +71 -4
  138. package/dist/debug/interaction-trace.d.ts +4 -0
  139. package/dist/debug/interaction-trace.js +15 -0
  140. package/dist/decorations/decoration-state.svelte.js +1 -1
  141. package/dist/decorations/reserved-attrs.js +1 -0
  142. package/dist/editor-actions/ancestry-folds.d.ts +2 -2
  143. package/dist/editor-actions/ancestry-folds.js +1 -1
  144. package/dist/editor-actions/block-edit-scope.js +1 -1
  145. package/dist/editor-actions/commit/text-batch.d.ts +3 -2
  146. package/dist/editor-actions/commit/text-batch.js +1 -1
  147. package/dist/editor-actions/commit/undo-controller.js +4 -2
  148. package/dist/editor-actions/container-edit.js +2 -1
  149. package/dist/editor-actions/enter-completion.d.ts +2 -0
  150. package/dist/editor-actions/enter-completion.js +23 -2
  151. package/dist/editor-actions/focus/focus-dispatch.js +7 -4
  152. package/dist/editor-actions/focus/focus-landing.d.ts +10 -1
  153. package/dist/editor-actions/focus/focus-landing.js +20 -6
  154. package/dist/editor-actions/inline-range-commit.js +1 -1
  155. package/dist/editor-actions/plugin/container.d.ts +7 -0
  156. package/dist/editor-actions/plugin/container.js +3 -1
  157. package/dist/editor-actions/reorder-action.js +19 -10
  158. package/dist/editor-actions/reorder-drag.js +29 -1
  159. package/dist/editor-actions/replacement-focus.d.ts +1 -1
  160. package/dist/editor-actions/replacement-focus.js +1 -1
  161. package/dist/editor-actions/search-replace.js +1 -1
  162. package/dist/editor-actions/table-context.d.ts +4 -1
  163. package/dist/editor-actions/table-context.js +57 -1
  164. package/dist/editor-events.d.ts +3 -0
  165. package/dist/editor-keys.d.ts +33 -0
  166. package/dist/editor-props.d.ts +24 -12
  167. package/dist/index.d.ts +1 -1
  168. package/dist/plugin.d.ts +7 -0
  169. package/dist/plugin.js +16 -0
  170. package/dist/plugins/latex/BlockMath.svelte +264 -29
  171. package/dist/plugins/latex/BlockMath.svelte.d.ts +2 -0
  172. package/dist/plugins/latex/index.d.ts +2 -1
  173. package/dist/plugins/latex/latex-kind.js +60 -10
  174. package/dist/plugins/latex/math-completion.js +4 -1
  175. package/dist/plugins/latex/math-layout.d.ts +15 -0
  176. package/dist/plugins/latex/math-layout.js +13 -0
  177. package/dist/plugins/latex/math-source.d.ts +19 -0
  178. package/dist/plugins/latex/math-source.js +103 -0
  179. package/dist/plugins/latex/register.d.ts +11 -2
  180. package/dist/plugins/latex/register.js +3 -1
  181. package/dist/plugins/latex/renderer.d.ts +3 -3
  182. package/dist/plugins/latex/renderer.js +13 -6
  183. package/dist/plugins/mermaid/MermaidBlock.svelte +19 -9
  184. package/dist/plugins/parrot/ParrotBlock.svelte +3 -1
  185. package/dist/reactivity/list-windowing.svelte.d.ts +8 -8
  186. package/dist/reactivity/list-windowing.svelte.js +75 -30
  187. package/dist/schema/block-completions.d.ts +8 -0
  188. package/dist/schema/block-completions.js +11 -0
  189. package/dist/schema/commands.d.ts +9 -5
  190. package/dist/schema/commands.js +12 -7
  191. package/dist/schema/context-actions.d.ts +31 -0
  192. package/dist/schema/context-actions.js +23 -0
  193. package/dist/schema/fenced-code-raw.js +31 -1
  194. package/dist/schema/operations.d.ts +8 -1
  195. package/dist/schema/reserved-chords.js +37 -4
  196. package/dist/schema/table-cell-raw.d.ts +1 -1
  197. package/dist/schema/table-cell-raw.js +1 -1
  198. package/dist/selection/block-hit-test.js +3 -2
  199. package/dist/selection/char-endpoint-snap.js +1 -1
  200. package/dist/selection/clipboard-text.js +6 -1
  201. package/dist/selection/covered-block.d.ts +10 -0
  202. package/dist/selection/covered-block.js +24 -0
  203. package/dist/selection/cross-block/dispatch.d.ts +3 -0
  204. package/dist/selection/cross-block/dispatch.js +13 -1
  205. package/dist/selection/cross-block/format-range.d.ts +1 -1
  206. package/dist/selection/cross-block/format-range.js +4 -15
  207. package/dist/selection/cross-block/format-toggle.js +1 -1
  208. package/dist/selection/cross-block/keydown.js +3 -29
  209. package/dist/selection/cross-block/ops.js +1 -1
  210. package/dist/selection/cross-block/paste.js +31 -35
  211. package/dist/selection/cross-block/type-replace.d.ts +3 -2
  212. package/dist/selection/cross-block/type-replace.js +52 -13
  213. package/dist/selection/dead-space-caret.d.ts +13 -0
  214. package/dist/selection/dead-space-caret.js +57 -1
  215. package/dist/selection/drag-pointer.d.ts +26 -2
  216. package/dist/selection/drag-pointer.js +103 -2
  217. package/dist/selection/gap-caret.js +1 -1
  218. package/dist/selection/keyboard-extend.d.ts +3 -2
  219. package/dist/selection/keyboard-extend.js +4 -3
  220. package/dist/selection/multi-click.d.ts +42 -0
  221. package/dist/selection/multi-click.js +203 -0
  222. package/dist/selection/native-bridge.d.ts +3 -0
  223. package/dist/selection/native-bridge.js +31 -2
  224. package/dist/selection/path-lookup.d.ts +10 -2
  225. package/dist/selection/path-lookup.js +11 -4
  226. package/dist/selection/pointer-gesture.d.ts +9 -0
  227. package/dist/selection/pointer-gesture.js +11 -0
  228. package/dist/selection/primitives.d.ts +7 -0
  229. package/dist/selection/primitives.js +19 -1
  230. package/dist/selection/range-delete-ceremony.js +4 -2
  231. package/dist/selection/range-delete-chrome.js +2 -1
  232. package/dist/selection/range-delete-table-coverage.js +2 -1
  233. package/dist/selection/range-delete-table.js +3 -2
  234. package/dist/selection/range-delete.d.ts +4 -0
  235. package/dist/selection/range-delete.js +31 -3
  236. package/dist/selection/selection-drop.d.ts +34 -0
  237. package/dist/selection/selection-drop.js +253 -0
  238. package/dist/selection/selection-restore.js +1 -1
  239. package/dist/selection/selection-state.svelte.d.ts +6 -0
  240. package/dist/selection/selection-state.svelte.js +36 -1
  241. package/dist/selection/table-endpoint-snap.js +1 -1
  242. package/dist/selection/table-rect-extend.js +1 -1
  243. package/dist/styles/editor-theme.css +75 -31
  244. package/dist/styles/editor.css +268 -18
  245. package/dist/testing/container-conformance.js +2 -2
  246. package/dist/testing/inline-conformance.js +2 -1
  247. package/dist/tree-operations/blockquote.js +1 -1
  248. package/dist/tree-operations/chain-rebuild.d.ts +63 -0
  249. package/dist/tree-operations/chain-rebuild.js +142 -0
  250. package/dist/tree-operations/children.d.ts +1 -1
  251. package/dist/tree-operations/children.js +1 -1
  252. package/dist/tree-operations/cleanup.js +1 -1
  253. package/dist/tree-operations/content-write.d.ts +50 -0
  254. package/dist/tree-operations/content-write.js +263 -0
  255. package/dist/tree-operations/index.d.ts +8 -3
  256. package/dist/tree-operations/index.js +6 -2
  257. package/dist/tree-operations/list/exit-replacement.js +1 -1
  258. package/dist/tree-operations/list/unwrap-merge.js +3 -3
  259. package/dist/tree-operations/node-ops.d.ts +17 -234
  260. package/dist/tree-operations/node-ops.js +47 -1113
  261. package/dist/tree-operations/node-primitives.d.ts +75 -0
  262. package/dist/tree-operations/node-primitives.js +117 -0
  263. package/dist/tree-operations/paste/apply.js +1 -1
  264. package/dist/tree-operations/paste/body-write.d.ts +1 -1
  265. package/dist/tree-operations/paste/body-write.js +2 -2
  266. package/dist/tree-operations/paste/container-match.js +4 -2
  267. package/dist/tree-operations/paste/dispatch.js +2 -1
  268. package/dist/tree-operations/paste/find-enclosing-list.js +1 -1
  269. package/dist/tree-operations/paste/focus-target.d.ts +1 -1
  270. package/dist/tree-operations/paste/list-absorb.js +1 -1
  271. package/dist/tree-operations/paste/list-break-out.js +1 -1
  272. package/dist/tree-operations/paste/parent-scope.js +1 -1
  273. package/dist/tree-operations/paste/paste-replacement.js +1 -1
  274. package/dist/tree-operations/paste/replace-block-at-parent.d.ts +3 -1
  275. package/dist/tree-operations/paste/replace-block-at-parent.js +5 -2
  276. package/dist/tree-operations/paste/replacement-parse.d.ts +17 -0
  277. package/dist/tree-operations/paste/replacement-parse.js +22 -0
  278. package/dist/tree-operations/path-mutate.d.ts +1 -1
  279. package/dist/tree-operations/path-mutate.js +2 -1
  280. package/dist/tree-operations/reorder-unit.js +1 -1
  281. package/dist/tree-operations/reorder.d.ts +5 -2
  282. package/dist/tree-operations/reorder.js +55 -2
  283. package/dist/tree-operations/settle.d.ts +105 -0
  284. package/dist/tree-operations/settle.js +660 -0
  285. package/dist/tree-operations/table-grid-clipboard.d.ts +21 -0
  286. package/dist/tree-operations/table-grid-clipboard.js +90 -0
  287. package/dist/tree-operations/unshare.d.ts +15 -77
  288. package/dist/tree-operations/unshare.js +15 -166
  289. package/docs/guide/consumer-guide.md +136 -104
  290. package/docs/guide/plugin-api.md +73 -29
  291. package/docs/guide/plugin-guide.md +55 -23
  292. package/package.json +9 -7
  293. package/dist/components/blocks/code/CodeLanguageChip.svelte +0 -127
  294. package/dist/components/blocks/code/CodeLanguageChip.svelte.d.ts +0 -14
  295. package/dist/components/blocks/table/TableGrip.svelte +0 -91
  296. package/dist/components/blocks/table/TableGrip.svelte.d.ts +0 -8
  297. package/dist/components/blocks/table/table-drop-target.d.ts +0 -1
  298. package/dist/components/blocks/table/table-drop-target.js +0 -16
  299. package/dist/components/blocks/table/table-reorder-drag.d.ts +0 -78
  300. package/dist/components/blocks/table/table-reorder-drag.js +0 -97
@@ -18,9 +18,15 @@ import { createMarkerCompletion } from './marker-completion';
18
18
  import { resolveEdgeDeletion } from './construct-edge-delete';
19
19
  import { hidesStructuralSuffix } from './hidden-suffix';
20
20
  import { resolveEdgeSeat } from './edge-seat';
21
- import { deleteRangeRaw } from './live-selection-edit';
21
+ import { replaceRangeRaw } from './live-selection-edit';
22
22
  import { resolveMarkedInsertion } from './pending-mark-insert';
23
23
  import { widgetAtCursor } from './widget-adjacency';
24
+ import { resolveDelimiterAutoPair } from './delimiter-autopair';
25
+ import { soleProseReparse } from './screen-diff';
26
+ /** Whether `line` still reloads as `node`'s kind, the auto-pair resolver's guard. */
27
+ export function keepsBlockKind(node, line) {
28
+ return soleProseReparse(line + trailingLineEnding(node.raw))?.block.kind === node.kind;
29
+ }
24
30
  const REPLACE_ISLAND_POLICY = {
25
31
  onEdge: 'select',
26
32
  deleteGranularity: 'select-then-delete'
@@ -34,6 +40,11 @@ export function createEdgePolicyDispatch(deps) {
34
40
  reason: 'an explicit instruction about the very next byte, so it outranks every classification below, which decide by where the caret happens to be',
35
41
  claims: handlePendingMarks
36
42
  },
43
+ {
44
+ id: 'transitional-hard-break',
45
+ reason: 'a hard break at the end of a block has no following line yet, so its own position is where the next byte starts that line',
46
+ claims: handleTransitionalHardBreak
47
+ },
37
48
  {
38
49
  id: 'cst-widget',
39
50
  reason: 'a key aimed at an atomic construct is the widget branch’s before any byte rule',
@@ -101,6 +112,12 @@ export function createEdgePolicyDispatch(deps) {
101
112
  const d = display();
102
113
  writeDisplay(d.slice(0, start) + insert + d.slice(end), start + insert.length, source, caretBefore);
103
114
  }
115
+ /** The range this surface holds, or null at a caret. A key aimed at one edits the RANGE: the
116
+ * caret the arms read is only its start. */
117
+ function heldRange() {
118
+ const selection = deps.getRawSelection();
119
+ return selection && selection.start < selection.end ? selection : null;
120
+ }
104
121
  // ── The seams the arms below defer to ────────────────────────────────────
105
122
  /**
106
123
  * The rung a range rewrite can be asked about. Past the reading cut a surface revealing no
@@ -139,6 +156,20 @@ export function createEdgePolicyDispatch(deps) {
139
156
  return null;
140
157
  return resolveEdgeSeat(caret, inlinesOf(deps.node), deps.getEdgeAffinity(), deps.node.raw, screenVisibilityOf(el), typed);
141
158
  }
159
+ /** A printable byte an arm claimed because the engine drops it at an element-level caret. Over
160
+ * a held range it REPLACES through the join seam, the road a mid-block range takes; at a caret
161
+ * the seat answers which side of an unpainted run it lands on. */
162
+ function writeTypedByte(el, caretOffset, typed, source) {
163
+ const range = heldRange();
164
+ if (range) {
165
+ const edit = replaceRangeRaw(deps.node, range, typed, joinSeamMode(el), deps.linkRef, deps.getAmbientPrefix?.() ?? '');
166
+ void deps.blockEdit.updateBlockContent(deps.index, edit.raw, range.start, edit.caret);
167
+ deps.setPendingCursor(edit.caret, source, edit.raw);
168
+ return;
169
+ }
170
+ const seatedAt = typingSeatAt(el, caretOffset, typed)?.offset ?? caretOffset;
171
+ editDisplay(seatedAt, seatedAt, typed, source, caretOffset);
172
+ }
142
173
  // ── CST inline widget ────────────────────────────────────────────────────
143
174
  function handleCstWidget(e, caretOffset) {
144
175
  if (deps.isRevealing())
@@ -152,9 +183,11 @@ export function createEdgePolicyDispatch(deps) {
152
183
  const widgetAt = widgetAtCursor(caretOffset, inlinesOf(node), node.raw, direction);
153
184
  if (!widgetAt)
154
185
  return false;
155
- // Unchorded only: a modifier makes the key a word-scoped platform command, and
156
- // entering here would swap that word-step for the modal widget-selected state.
157
- const plainEdgeKey = !e.shiftKey && !hasModifier(e);
186
+ // Below the bail: every keystroke in every prose block reaches the line above.
187
+ const range = heldRange();
188
+ // Unchorded, and at a CARET: a modifier makes the key a word-scoped platform command, and
189
+ // a held range is an edit of the range, not an entry into the construct beside its start.
190
+ const plainEdgeKey = !e.shiftKey && !hasModifier(e) && !range;
158
191
  const enterFromRight = plainEdgeKey && widgetAt.atRight && (e.key === 'ArrowLeft' || e.key === 'Backspace');
159
192
  const enterFromLeft = plainEdgeKey && !widgetAt.atRight && (e.key === 'ArrowRight' || e.key === 'Delete');
160
193
  if (enterFromRight || enterFromLeft) {
@@ -189,14 +222,7 @@ export function createEdgePolicyDispatch(deps) {
189
222
  !deps.isReading()) {
190
223
  e.preventDefault();
191
224
  deps.setSnapTarget(null);
192
- const typed = e.key;
193
- // This branch owns only the fact that the engine drops the key beside a widget; which
194
- // side of an unpainted run the byte belongs on is still the seat's answer.
195
- const seatedAt = typingSeatAt(el, caretOffset, typed)?.offset ?? caretOffset;
196
- const newRaw = node.raw.slice(0, seatedAt) + typed + node.raw.slice(seatedAt);
197
- const postEdit = seatedAt + typed.length;
198
- void deps.blockEdit.updateBlockContent(deps.index, newRaw, caretOffset, postEdit);
199
- deps.setPendingCursor(postEdit, 'widget', newRaw);
225
+ writeTypedByte(el, caretOffset, e.key, 'widget');
200
226
  return true;
201
227
  }
202
228
  return false;
@@ -243,8 +269,8 @@ export function createEdgePolicyDispatch(deps) {
243
269
  // Second press: the native selection already wraps a replace island, so delete its
244
270
  // whole hidden range through the CST as one undo entry.
245
271
  if (isDestructive) {
246
- const selection = deps.getRawSelection();
247
- if (selection && selection.start < selection.end) {
272
+ const selection = heldRange();
273
+ if (selection) {
248
274
  const selected = islands.find((i) => islandPolicy(i).onEdge === 'select' &&
249
275
  i.start === selection.start &&
250
276
  i.end === selection.end);
@@ -301,7 +327,7 @@ export function createEdgePolicyDispatch(deps) {
301
327
  // branch in e2e, so it is unit-pinned instead.
302
328
  if (isTyping && !caretIsInTextContent(el, window.getSelection())) {
303
329
  e.preventDefault();
304
- editDisplay(caretOffset, caretOffset, e.key);
330
+ writeTypedByte(el, caretOffset, e.key, 'island');
305
331
  return true;
306
332
  }
307
333
  return false;
@@ -326,11 +352,11 @@ export function createEdgePolicyDispatch(deps) {
326
352
  if (!touchesAmbient)
327
353
  return false;
328
354
  e.preventDefault();
329
- const range = deps.getRawSelection();
330
- if (range && range.start < range.end) {
355
+ const range = heldRange();
356
+ if (range) {
331
357
  // Consumed at KEYDOWN, so no `beforeinput` carries this range to the shared seam: the arm
332
358
  // asks it here, or a literal splice prints the runs the cut stranded (live-mode.md § 4.5).
333
- const edit = deleteRangeRaw(deps.node, range, joinSeamMode(el), deps.linkRef, deps.getAmbientPrefix?.() ?? '');
359
+ const edit = replaceRangeRaw(deps.node, range, '', joinSeamMode(el), deps.linkRef, deps.getAmbientPrefix?.() ?? '');
334
360
  void deps.blockEdit.updateBlockContent(deps.index, edit.raw, range.start, edit.caret);
335
361
  deps.setPendingCursor(edit.caret, 'ambient-delete');
336
362
  }
@@ -389,6 +415,35 @@ export function createEdgePolicyDispatch(deps) {
389
415
  return true;
390
416
  }
391
417
  // ── Pending marks (the toggle seat) ────────────────────────────────────────
418
+ /**
419
+ * The byte that completes a hard break made at the END of a block. `insertHardBreak` writes
420
+ * `\\` plus the block's ending there, but that ending IS the block's trailing one, so the
421
+ * display stops at the backslash and the caret has nowhere to sit past it. The next
422
+ * printable key is what supplies the following line: it lands AFTER the break rather than
423
+ * between the backslash and its newline, where it would read as content and undo the break.
424
+ */
425
+ function handleTransitionalHardBreak(e, caretOffset) {
426
+ if (deps.isReading())
427
+ return false;
428
+ if (!isPlainTypingKey(e) || caretOffset === null || hasSelectionHelper())
429
+ return false;
430
+ const d = display();
431
+ // Only at the very end, and only when the block's last byte is the break's backslash.
432
+ if (caretOffset !== d.length || !d.endsWith('\\'))
433
+ return false;
434
+ // An escaped backslash (`\\\\`) is content, not a break.
435
+ if (d.endsWith('\\\\'))
436
+ return false;
437
+ // Backslash before ASCII punctuation is an escape (`\|`, `\*`), never a break's backslash.
438
+ if (/^[!-/:-@[-`{-~]$/.test(e.key))
439
+ return false;
440
+ const ending = trailingLineEnding(deps.node.raw);
441
+ e.preventDefault();
442
+ deps.setSnapTarget(null);
443
+ const next = d + ending + e.key;
444
+ writeDisplay(next, next.length, 'transitional-hard-break', caretOffset);
445
+ return true;
446
+ }
392
447
  /**
393
448
  * A printable key while a collapsed-caret toggle has marks pending. Marks are the newer
394
449
  * instruction about the same bytes, so they outrank the arrival side the seat below reads
@@ -442,14 +497,28 @@ export function createEdgePolicyDispatch(deps) {
442
497
  function handleConstructSeat(e, caretOffset) {
443
498
  if (!isPlainTypingKey(e) || caretOffset === null || hasSelectionHelper())
444
499
  return false;
500
+ // A delimiter typed over its own closer is the auto-pair's step-over, which the beforeinput
501
+ // arm owns (delimiter-autopair.ts); seated outside the run it would be typed instead.
502
+ const content = getContentRange(deps.node);
503
+ const autoPair = resolveDelimiterAutoPair(display(), content, caretOffset, e.key);
504
+ if (autoPair?.kind === 'step-over')
505
+ return false;
445
506
  const el = deps.getEl();
446
507
  const seat = el && typingSeatAt(el, caretOffset, e.key);
447
508
  if (!seat)
448
509
  return false;
449
510
  e.preventDefault();
450
511
  deps.setSnapTarget(null);
451
- // The kind rides the trace: it names the construct whose hidden edge the caret sat at, the
452
- // question this seam answered, so a trace line without it cannot be read.
512
+ // The seat decides WHERE the byte lands; what a delimiter keystroke writes there is still
513
+ // the auto-pair's answer, or a byte seated past a run would arrive without its twin. The
514
+ // kind rides the trace: it names the construct whose hidden edge the caret sat at.
515
+ const paired = resolveDelimiterAutoPair(display(), content, seat.offset, e.key, (line) => keepsBlockKind(deps.node, line));
516
+ if (paired && paired.kind !== 'step-over') {
517
+ writeDisplay(paired.text, paired.caret, `seat:${seat.kind}`, caretOffset);
518
+ if (paired.kind === 'close')
519
+ deps.noteOutside?.();
520
+ return true;
521
+ }
453
522
  editDisplay(seat.offset, seat.offset, e.key, `seat:${seat.kind}`, caretOffset);
454
523
  return true;
455
524
  }
@@ -59,10 +59,10 @@ export declare function resolveSelectionEdit(node: NodeView, selection: {
59
59
  start: number;
60
60
  end: number;
61
61
  }, typed: string, presentationMode: PresentationMode | undefined, linkRef: InlineResolverRef | undefined, ambientPrefix?: string): SelectionEdit | null;
62
- /** The delete of `[start, end)` a destructive gesture installs: the seam's cleaned rewrite where
63
- * it has one, else the literal display splice with the caret at the cut — the fallback's one
64
- * home, so no arm carrying it can drift. */
65
- export declare function deleteRangeRaw(node: NodeView, range: {
62
+ /** The rewrite of `[start, end)` to `typed` a gesture the surface consumed installs, where a
63
+ * delete is the empty one. The seam's cleaned bytes where it has them, else the literal display
64
+ * splice with the caret past the insert: the fallback's one home, so no arm can drift. */
65
+ export declare function replaceRangeRaw(node: NodeView, range: {
66
66
  start: number;
67
67
  end: number;
68
- }, presentationMode: PresentationMode | undefined, linkRef: InlineResolverRef | undefined, ambientPrefix: string): SelectionEdit;
68
+ }, typed: string, presentationMode: PresentationMode | undefined, linkRef: InlineResolverRef | undefined, ambientPrefix: string): SelectionEdit;
@@ -19,6 +19,8 @@ export function resolveLiveRangeEdit(e, node, cursor, presentationMode, linkRef,
19
19
  if (!range)
20
20
  return null;
21
21
  const insert = replacementText(e);
22
+ if (range.start === range.end)
23
+ return parkedCaretInsertion(node, cursor, range.start, insert);
22
24
  const edit = resolveSelectionEdit(node, range, insert ?? '', presentationMode, linkRef, ambientPrefix);
23
25
  if (!edit)
24
26
  return null;
@@ -26,6 +28,31 @@ export function resolveLiveRangeEdit(e, node, cursor, presentationMode, linkRef,
26
28
  ? { kind: 'swallow' }
27
29
  : { kind: 'rewrite', range, raw: edit.raw, caret: edit.caret };
28
30
  }
31
+ /**
32
+ * A collapsed insertion has no seam to clean, but Chromium inserts at its canonical spelling of
33
+ * the caret's pixel, UPSTREAM across a hidden run, while the DOM caret may sit where a commit
34
+ * parked it, past that run (after a `)` just typed). A target behind the caret is that
35
+ * canonicalization, and the byte belongs where the caret is, the side the keydown seat settled. A
36
+ * target at or past the caret is the engine landing the byte itself, which a split's reopened run
37
+ * relies on: parked at its start, the byte goes inside.
38
+ */
39
+ function parkedCaretInsertion(node, cursor, engineTarget, insert) {
40
+ if (!insert)
41
+ return null;
42
+ const selection = window.getSelection();
43
+ if (!selection || selection.rangeCount === 0 || !selection.isCollapsed)
44
+ return null;
45
+ const caret = cursor.rawRangeOf(selection.getRangeAt(0))?.start ?? null;
46
+ if (caret === null || engineTarget >= caret)
47
+ return null;
48
+ const display = trimTrailingLineEnding(node.raw);
49
+ return {
50
+ kind: 'rewrite',
51
+ range: { start: caret, end: caret },
52
+ raw: display.slice(0, caret) + insert + display.slice(caret) + trailingLineEnding(node.raw),
53
+ caret: caret + insert.length
54
+ };
55
+ }
29
56
  /**
30
57
  * The whole surface arm around {@link resolveLiveRangeEdit}: stand down while a source reveal has
31
58
  * outrun the CST, resolve, preventDefault what the seam owns, then swallow or hand the rewrite to
@@ -75,17 +102,20 @@ export function resolveSelectionEdit(node, selection, typed, presentationMode, l
75
102
  caret: joined.seam + typed.length
76
103
  };
77
104
  }
78
- /** The delete of `[start, end)` a destructive gesture installs: the seam's cleaned rewrite where
79
- * it has one, else the literal display splice with the caret at the cut — the fallback's one
80
- * home, so no arm carrying it can drift. */
81
- export function deleteRangeRaw(node, range, presentationMode, linkRef, ambientPrefix) {
82
- const cleaned = resolveSelectionEdit(node, range, '', presentationMode, linkRef, ambientPrefix);
105
+ /** The rewrite of `[start, end)` to `typed` a gesture the surface consumed installs, where a
106
+ * delete is the empty one. The seam's cleaned bytes where it has them, else the literal display
107
+ * splice with the caret past the insert: the fallback's one home, so no arm can drift. */
108
+ export function replaceRangeRaw(node, range, typed, presentationMode, linkRef, ambientPrefix) {
109
+ const cleaned = resolveSelectionEdit(node, range, typed, presentationMode, linkRef, ambientPrefix);
83
110
  if (cleaned)
84
111
  return cleaned;
85
112
  const display = trimTrailingLineEnding(node.raw);
86
113
  return {
87
- raw: display.slice(0, range.start) + display.slice(range.end) + trailingLineEnding(node.raw),
88
- caret: range.start
114
+ raw: display.slice(0, range.start) +
115
+ typed +
116
+ display.slice(range.end) +
117
+ trailingLineEnding(node.raw),
118
+ caret: range.start + typed.length
89
119
  };
90
120
  }
91
121
  // ── Reading the event ────────────────────────────────────────────────────────
@@ -6,7 +6,7 @@ import { resolvedInlineContent } from '../../../core/inline/inline-cache';
6
6
  import { isInlineWidget } from '../../../core/inline/inline-widgets';
7
7
  import { createClipboardHandlers } from '../editable-surface';
8
8
  import { pasteDispatch } from '../../../tree-operations/paste/dispatch';
9
- import { deleteRangeRaw } from './live-selection-edit';
9
+ import { replaceRangeRaw } from './live-selection-edit';
10
10
  export function createTextClipboard(deps) {
11
11
  function getSelectedTextFromRaw() {
12
12
  const offsets = deps.cursor.getRawSelection();
@@ -77,7 +77,7 @@ export function createTextClipboard(deps) {
77
77
  return;
78
78
  // A cut is a delete, so it crosses the same join seam: in live the range can span
79
79
  // delimiter runs the reader never saw, and a raw splice would print them.
80
- const edit = deleteRangeRaw(deps.node, selOffsets, deps.getPresentationMode(), deps.linkRef, deps.getAmbientPrefix());
80
+ const edit = replaceRangeRaw(deps.node, selOffsets, '', deps.getPresentationMode(), deps.linkRef, deps.getAmbientPrefix());
81
81
  void deps.blockEdit.updateBlockContent(deps.index, edit.raw, selOffsets.start);
82
82
  deps.setPendingCursor(edit.caret);
83
83
  },
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Pure raw/caret transforms for TextEditableBlock keydown branches — heading-level
2
+ * Pure raw/caret transforms for TextEditableBlock's structural gestures — heading-level
3
3
  * swap, demotion to prose, hard-break insertion, literal-tab insertion. The component
4
4
  * owns the plumbing; these own the string math.
5
5
  */
@@ -21,6 +21,12 @@ export declare function dropStructuralPrefix(raw: string, contentStart: number,
21
21
  /** Drop everything past `contentEnd` but the block's own trailing line ending — the setext
22
22
  * underline, which sits after every caret the content range admits. */
23
23
  export declare function dropStructuralSuffix(raw: string, contentEnd: number, preEditOffset: number): TextEditResult;
24
+ /**
25
+ * An ATX heading left with no text is a marker standing over nothing: unfocused it paints
26
+ * nothing (the chrome-only stamp is focus-scoped), so the block would survive invisibly and
27
+ * resurface as a `#` on the next click. On blur it becomes the empty paragraph it looks like.
28
+ */
29
+ export declare function demoteEmptyAtxHeading(raw: string, content: ContentRange): TextEditResult | null;
24
30
  /**
25
31
  * Re-mark the block's CONTENT with an ATX prefix for `level`, replacing whatever structural bytes
26
32
  * the current kind keeps — the same content range {@link demoteToParagraph} reads, so an indented
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Pure raw/caret transforms for TextEditableBlock keydown branches — heading-level
2
+ * Pure raw/caret transforms for TextEditableBlock's structural gestures — heading-level
3
3
  * swap, demotion to prose, hard-break insertion, literal-tab insertion. The component
4
4
  * owns the plumbing; these own the string math.
5
5
  */
@@ -33,6 +33,16 @@ export function dropStructuralSuffix(raw, contentEnd, preEditOffset) {
33
33
  caretOffset: Math.min(preEditOffset, contentEnd)
34
34
  };
35
35
  }
36
+ /**
37
+ * An ATX heading left with no text is a marker standing over nothing: unfocused it paints
38
+ * nothing (the chrome-only stamp is focus-scoped), so the block would survive invisibly and
39
+ * resurface as a `#` on the next click. On blur it becomes the empty paragraph it looks like.
40
+ */
41
+ export function demoteEmptyAtxHeading(raw, content) {
42
+ if (content.start === 0 || content.end > content.start)
43
+ return null;
44
+ return demoteToParagraph(raw, content, 0);
45
+ }
36
46
  /**
37
47
  * Re-mark the block's CONTENT with an ATX prefix for `level`, replacing whatever structural bytes
38
48
  * the current kind keeps — the same content range {@link demoteToParagraph} reads, so an indented
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import type { AmbientPrefix } from '../../../block-component';
8
8
  import type { DocumentView, NodeView } from '../../../core/node-views';
9
- import type { PresentationMode } from '../../../presentation-mode';
9
+ import { type PresentationMode } from '../../../presentation-mode';
10
10
  import type { ResolveImageUrl, ResolveLinkUrl } from '../../../editor-keys';
11
11
  import type { LinkReferenceResolver } from '../../../core/inline/link-reference-resolver';
12
12
  import { type ImageLoadPolicy } from '../../../core/inline-render';
@@ -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