@voithos-labs/aragonite 0.10.1 → 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 (256) hide show
  1. package/README.md +12 -22
  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 +383 -199
  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 +24 -13
  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/plugins/parrot/ParrotBlock.svelte +1 -1
  157. package/dist/reactivity/list-windowing.svelte.d.ts +8 -8
  158. package/dist/reactivity/list-windowing.svelte.js +49 -24
  159. package/dist/schema/block-completions.d.ts +8 -0
  160. package/dist/schema/block-completions.js +11 -0
  161. package/dist/schema/context-actions.d.ts +31 -0
  162. package/dist/schema/context-actions.js +23 -0
  163. package/dist/schema/fenced-code-raw.js +31 -1
  164. package/dist/schema/operations.d.ts +8 -1
  165. package/dist/schema/reserved-chords.js +25 -4
  166. package/dist/schema/table-cell-raw.d.ts +1 -1
  167. package/dist/schema/table-cell-raw.js +1 -1
  168. package/dist/selection/block-hit-test.js +3 -2
  169. package/dist/selection/char-endpoint-snap.js +1 -1
  170. package/dist/selection/clipboard-text.js +6 -1
  171. package/dist/selection/covered-block.d.ts +10 -0
  172. package/dist/selection/covered-block.js +24 -0
  173. package/dist/selection/cross-block/dispatch.d.ts +3 -0
  174. package/dist/selection/cross-block/dispatch.js +13 -1
  175. package/dist/selection/cross-block/format-range.d.ts +1 -1
  176. package/dist/selection/cross-block/format-range.js +4 -15
  177. package/dist/selection/cross-block/format-toggle.js +1 -1
  178. package/dist/selection/cross-block/keydown.js +1 -1
  179. package/dist/selection/cross-block/ops.js +1 -1
  180. package/dist/selection/cross-block/paste.js +32 -30
  181. package/dist/selection/cross-block/type-replace.d.ts +3 -2
  182. package/dist/selection/cross-block/type-replace.js +62 -13
  183. package/dist/selection/dead-space-caret.d.ts +10 -0
  184. package/dist/selection/dead-space-caret.js +47 -1
  185. package/dist/selection/double-click-trim.d.ts +17 -0
  186. package/dist/selection/double-click-trim.js +57 -0
  187. package/dist/selection/drag-pointer.d.ts +7 -2
  188. package/dist/selection/drag-pointer.js +61 -2
  189. package/dist/selection/gap-caret.js +1 -1
  190. package/dist/selection/keyboard-extend.js +1 -1
  191. package/dist/selection/path-lookup.js +1 -1
  192. package/dist/selection/range-delete-ceremony.js +4 -2
  193. package/dist/selection/range-delete-chrome.js +2 -1
  194. package/dist/selection/range-delete-table-coverage.js +2 -1
  195. package/dist/selection/range-delete-table.js +3 -2
  196. package/dist/selection/range-delete.js +30 -2
  197. package/dist/selection/selection-restore.js +1 -1
  198. package/dist/selection/selection-state.svelte.d.ts +6 -0
  199. package/dist/selection/selection-state.svelte.js +36 -1
  200. package/dist/selection/table-endpoint-snap.js +1 -1
  201. package/dist/selection/table-rect-extend.js +1 -1
  202. package/dist/styles/editor-theme.css +57 -28
  203. package/dist/styles/editor.css +217 -18
  204. package/dist/testing/container-conformance.js +2 -2
  205. package/dist/testing/inline-conformance.js +2 -1
  206. package/dist/tree-operations/blockquote.js +1 -1
  207. package/dist/tree-operations/chain-rebuild.d.ts +63 -0
  208. package/dist/tree-operations/chain-rebuild.js +142 -0
  209. package/dist/tree-operations/children.d.ts +1 -1
  210. package/dist/tree-operations/children.js +1 -1
  211. package/dist/tree-operations/cleanup.js +1 -1
  212. package/dist/tree-operations/content-write.d.ts +50 -0
  213. package/dist/tree-operations/content-write.js +263 -0
  214. package/dist/tree-operations/index.d.ts +8 -3
  215. package/dist/tree-operations/index.js +6 -2
  216. package/dist/tree-operations/list/exit-replacement.js +1 -1
  217. package/dist/tree-operations/list/unwrap-merge.js +3 -3
  218. package/dist/tree-operations/node-ops.d.ts +17 -234
  219. package/dist/tree-operations/node-ops.js +47 -1113
  220. package/dist/tree-operations/node-primitives.d.ts +75 -0
  221. package/dist/tree-operations/node-primitives.js +117 -0
  222. package/dist/tree-operations/paste/apply.js +1 -1
  223. package/dist/tree-operations/paste/body-write.d.ts +1 -1
  224. package/dist/tree-operations/paste/body-write.js +2 -2
  225. package/dist/tree-operations/paste/container-match.js +4 -2
  226. package/dist/tree-operations/paste/dispatch.js +2 -1
  227. package/dist/tree-operations/paste/find-enclosing-list.js +1 -1
  228. package/dist/tree-operations/paste/focus-target.d.ts +1 -1
  229. package/dist/tree-operations/paste/list-absorb.js +1 -1
  230. package/dist/tree-operations/paste/list-break-out.js +1 -1
  231. package/dist/tree-operations/paste/parent-scope.js +1 -1
  232. package/dist/tree-operations/paste/paste-replacement.js +1 -1
  233. package/dist/tree-operations/paste/replace-block-at-parent.js +1 -1
  234. package/dist/tree-operations/path-mutate.d.ts +1 -1
  235. package/dist/tree-operations/path-mutate.js +2 -1
  236. package/dist/tree-operations/reorder-unit.js +1 -1
  237. package/dist/tree-operations/reorder.d.ts +5 -2
  238. package/dist/tree-operations/reorder.js +55 -2
  239. package/dist/tree-operations/settle.d.ts +105 -0
  240. package/dist/tree-operations/settle.js +660 -0
  241. package/dist/tree-operations/table-grid-clipboard.d.ts +21 -0
  242. package/dist/tree-operations/table-grid-clipboard.js +90 -0
  243. package/dist/tree-operations/unshare.d.ts +15 -77
  244. package/dist/tree-operations/unshare.js +15 -166
  245. package/docs/guide/consumer-guide.md +132 -99
  246. package/docs/guide/plugin-api.md +31 -3
  247. package/docs/guide/plugin-guide.md +31 -6
  248. package/package.json +4 -2
  249. package/dist/components/blocks/code/CodeLanguageChip.svelte +0 -127
  250. package/dist/components/blocks/code/CodeLanguageChip.svelte.d.ts +0 -14
  251. package/dist/components/blocks/table/TableGrip.svelte +0 -91
  252. package/dist/components/blocks/table/TableGrip.svelte.d.ts +0 -8
  253. package/dist/components/blocks/table/table-drop-target.d.ts +0 -1
  254. package/dist/components/blocks/table/table-drop-target.js +0 -16
  255. package/dist/components/blocks/table/table-reorder-drag.d.ts +0 -78
  256. package/dist/components/blocks/table/table-reorder-drag.js +0 -97
@@ -305,7 +305,7 @@ cNo.....................................oc
305
305
  color: #3fd3d3;
306
306
  }
307
307
  85.714% {
308
- color: var(--color-text-primary, #ffffff);
308
+ color: var(--color-text-primary, currentColor);
309
309
  }
310
310
  }
311
311
  @media (prefers-reduced-motion: reduce) {
@@ -916,7 +916,9 @@ Pass the plugin to the editor's `plugins` prop. It installs before the seed pars
916
916
  let editor = $state();
917
917
  </script>
918
918
 
919
- <Editor bind:this={editor} source={SEED} {plugins} theme="light" />
919
+ <div class="aragonite-editor-theme" data-editor-theme="light">
920
+ <Editor bind:this={editor} source={SEED} {plugins} theme="light" />
921
+ </div>
920
922
  ```
921
923
 
922
924
  The chords are live (a **chord** is a key combination, written `Mod+7` where `Mod` is Ctrl, or Cmd on a Mac). Focus the box, press `Mod+8` to debunk the theory (the string comes down and the stamp lands) and `Mod+7` to allege it again, then read `editor.getSource()` back and watch the opener line flip between the two names:
@@ -931,7 +933,7 @@ editor.getSource();
931
933
 
932
934
  The flip is one undoable edit, so undo un-debunks it, which is how conspiracies work anyway. And because the verdict lives in the bytes, a debunked conspiracy stays debunked across a reload.
933
935
 
934
- One thing that surprises people pasting this into a fresh app: `theme="light"` flips only the editor-owned colors, so the page still looks dark. The built-in light chrome wants the `aragonite-editor-theme` class on a wrapper element; [consumer-guide.md](consumer-guide.md)'s theming section explains the two tiers.
936
+ The wrapper and the two `light`s are there because the editor paints no background of its own. A fresh app's page is white, so the built-in chrome (the wrapper's attribute) and the editor's own surfaces (the prop) both have to say so; on a dark page, both say `dark`, or nothing. [consumer-guide.md](consumer-guide.md)'s theming section explains the two tiers.
935
937
 
936
938
  Want a collapse toggle? Give `reservedChrome` an `isCollapsed` probe over the node, and every focus walk, merge, and windowing decision (a collapsed body stays unmounted) reads that one declaration. Add `expandPatch` beside it, returning the metadata patch that opens the node, and a reveal into the collapsed body (a table-of-contents entry, a search match) opens the container first and commits it as one undoable edit. Without it, such a reveal has nowhere to land and reports that it didn't.
937
939
 
@@ -1159,9 +1161,11 @@ leaf.getOptions(); // this editor's options for your plugin, typed unknown
1159
1161
 
1160
1162
  **Native parity is the tier's whole claim**: the editor's caret enters and leaves your block like any built-in text block (including keeping its column as it walks up or down lines), IME composition is respected, undo batches like prose, the clipboard is intercepted for plain-Markdown copy/cut/paste like every editable surface, and a cross-block selection sweeps through your text.
1161
1163
 
1162
- **One spread wires the source surface.** Write `<div {...leaf.surfaceProps}>` on your source contenteditable and the nine DOM handlers, the `contenteditable` / `role` / `tabindex` / `spellcheck` attributes, and two view-lifecycle contracts all land at once, so a forgotten handler (a dropped `oncompositionend` that silently breaks IME) simply can't happen to you. The two contracts the spread owns are the ones every consumer used to hand-write: the source is populated as a **single text node** (so `textContent === source` and the walk that maps DOM positions to byte offsets stays exact), and focus is parked on the editor root when the source unmounts.
1164
+ **One spread wires the source surface.** Write `<div {...leaf.surfaceProps}>` on your source contenteditable and the DOM handlers, the `contenteditable` / `role` / `tabindex` / `spellcheck` attributes, and two view-lifecycle contracts all land at once, so a forgotten handler (a dropped `oncompositionend` that silently breaks IME) simply can't happen to you. The two contracts the spread owns are the ones every consumer used to hand-write: the source is populated so that **`textContent === source`** (the walk that maps DOM positions to byte offsets depends on it), and focus is parked on the editor root when the source unmounts.
1165
+
1166
+ That text carries every newline your source holds, which makes **`white-space: pre-wrap` (or `pre`) on your source element part of the contract** for any leaf whose bytes can span lines. Without it the browser collapses the line breaks on screen while the offset walk goes on counting them, and the caret sits nowhere near where it looks.
1163
1167
 
1164
- That single text node carries every newline your source holds, which makes **`white-space: pre-wrap` (or `pre`) on your source element part of the contract** for any leaf whose bytes can span lines. Without it the browser collapses the line breaks on screen while the offset walk goes on counting them, and the caret sits nowhere near where it looks.
1168
+ **A painted source.** By default the source is one text node. A `renderSource(text)` dep paints it as DOM instead (fence lines the marker-hiding modes collapse, highlight tokens; the `highlightCode` export is the code block's own tokenizer), and the factory asserts `textContent === text` on every paint, so a painter that drops a byte fails loudly in dev rather than corrupting a commit. A painted source takes its plain-text edits from the leaf, not the browser: typing, Enter, deletes and pastes splice the text and repaint, each reported through `onSourceEdit(text)` so a live preview can follow the draft, and undo inside the open reveal walks those edits back before it reaches the document's history. `repaintSource()` re-runs the painter after a native edit (an IME commit), and `completeBareSource(text)` lets a kind complete a chrome-only source (a `$$` straight over `$$`) to the shape a caret can sit in as it is revealed. Block math is the worked example.
1165
1169
 
1166
1170
  A leaf whose bytes are one line (the parrot's opener claims exactly one) declares `singleLine: true` and needs none of that. Enter in one of those ends the block: the text after the caret becomes a paragraph below and the caret goes with it, which is what Enter does in a heading. With the flag off, the default, Enter types a newline.
1167
1171
 
@@ -1186,7 +1190,7 @@ Editing past your own fence therefore re-splits the document instead of wedging
1186
1190
 
1187
1191
  **Per-instance configuration.** `leaf.getOptions()` returns this editor instance's options for the plugin owning your kind, typed `unknown` for you to narrow. It's the same route as the container factory's `getOptions()`, one tier down, and the same rule applies ([the options recipe](#recipe-per-instance-options-and-the-factory-closure-trap)). The bundled toc block resolves `maxDepth` this way and falls back to the factory argument, which then serves as the default for an instance declaring none.
1188
1192
 
1189
- Block math (`$$…$$` in the bundled `@voithos-labs/aragonite/plugins/latex` plugin) is the worked example, and it's smaller than you'd expect: its component script is the factory call, one render effect (KaTeX), a `{...leaf.surfaceProps}` spread on the source, and one-line re-exports of the returned surface. Registration is the ordinary leaf recipe: `registerBlockKind` (no container group), `registerBlockOpener`, `registerBlockComponent`.
1193
+ Block math (`$$…$$` in the bundled `@voithos-labs/aragonite/plugins/latex` plugin) is the worked example, and it's smaller than you'd expect: its component script is the factory call, one render effect (KaTeX), a `{...leaf.surfaceProps}` spread on the source, and one-line re-exports of the returned surface. Registration is the ordinary leaf recipe: `registerBlockKind` (no container group), `registerBlockOpener`, `registerBlockComponent`. Its `caretTargetAtPoint` is the other half of the parrot's: where the parrot's caption is the source bytes minus a prefix, KaTeX paints glyphs no offset maps back to, so the render effect stamps the body's span on the rendered element and the hook walks that span in proportion to how far along the press fell.
1190
1194
 
1191
1195
  ## Presentation modes
1192
1196
 
@@ -1379,6 +1383,8 @@ If your `revealSource` widget takes a click of its own, declare `claimsActivatio
1379
1383
 
1380
1384
  If your widget derives from the whole document, read the version inside the same `$derived` and use it as your memo key. The document itself isn't a usable key: the editor mutates it in place, so its identity never changes, and an identity-keyed memo hits forever on a stale answer. Reading the version inside the derived is also what subscribes your widget to edits anywhere, so N widgets sharing one memoized walk stay as live as N widgets each walking the document.
1381
1385
 
1386
+ **A symmetric delimiter can close itself as it is typed.** Pass `autoPair: true` on a bare trigger whose construct opens and closes on the same byte, the way the bundled latex plugin does for `$…$`: typing the trigger lands its twin after the caret, typing it again over that twin steps past it, and a first body byte the recognizer rejects (a `$` followed by a digit is a price) drops the twin again. Without it a lone `$` typed ahead of an existing formula pairs with that formula's closer and wraps the prose between them. The built-in backtick, `*`, `_` and `~~` behave this way without registration.
1387
+
1382
1388
  **A bare trigger must be a character no built-in scanner claims.** Registering a bare recognizer on a reserved trigger (`` ` ``, `&`, `<`, `*`, `_`, `~`, `[`, `]`, `!`, `\`, or newline) throws: built-in dispatch runs first, so a bare recognizer there would never fire, and a silent no-op is the one failure a public API must not have.
1383
1389
 
1384
1390
  The bundled **emoji** plugin (`@voithos-labs/aragonite/plugins/emoji`) is this bare-trigger recipe end to end and the worked reference for an inline kind on an unreserved trigger: `:shortcode:` recognizes on the bare `:` trigger, renders as an atomic glyph widget through `buildWidget` + `mintWidgetShell`, and carries the `{ deleteGranularity: 'atomic', onEdge: 'step-over' }` edge policy so a caret-adjacent Backspace removes the whole `:name:` in one press and a plain arrow steps over it. It shares the `:` trigger with the directive text tier, because disjoint grammars coexist happily on one trigger: a table-lookup miss declines and falls through with the bytes untouched. The literal `:name:` bytes stay in the raw, so an uninstalled document round-trips as ordinary prose.
@@ -1604,6 +1610,25 @@ registerGlobalCommand('mine.bold', handler, { chord: 'Mod+B' }); // fine: fires
1604
1610
 
1605
1611
  Chord strings follow the consumer guide's chord model: fixed-order `Mod` / `Alt` / `Shift` plus the key's own value. Shifted-symbol chords aren't modeled, so bind plain digits and letters.
1606
1612
 
1613
+ ## Block context actions
1614
+
1615
+ **`registerBlockContextActions(kind, provider)`**
1616
+
1617
+ The right-click menu on a block of `kind` (a code block, a table, a plugin's own block) lists what its providers return, ahead of the editor's own rows (copy, replace with the clipboard, remove). Register from `setup`. The provider is consulted on every open, so it reads the block as it is then; several may stack on one kind, and `EVERY_KIND` (`'*'`) registers for every kind. Prose is the page's background: a paragraph or heading keeps the browser's own menu and consults no provider.
1618
+
1619
+ ```ts
1620
+ registerBlockContextActions(conspiracy, (node) => [
1621
+ {
1622
+ id: 'conspiracy.debunk',
1623
+ label: 'Mark debunked',
1624
+ icon: 'check',
1625
+ run: (ctx) => ctx.replaceRaw(node.raw.replace(/^:::conspiracy/, ':::debunked'))
1626
+ }
1627
+ ]);
1628
+ ```
1629
+
1630
+ `run` receives a `BlockActionContext`: the node, its path, `deleteBlock()`, and `replaceRaw(raw)`, which rewrites the block's bytes wholesale and reparses them, the road the default replace row takes. Each is one undo entry. `icon` names a glyph the editor's menus already draw (the same set the code rail and the table menu use); a row without one shows none. `danger` paints the row in the error colour, for an action that is not one undo away.
1631
+
1607
1632
  ## Paste transforms
1608
1633
 
1609
1634
  `registerPasteTransform` records a **content-keyed, pre-parse** rewrite of pasted plain text. Each transform is a `{ name, transform(text) }` unit: `transform` returns a replacement string, or `null` to decline ("not mine"). Transforms run at every paste site before the clipboard text is parsed, in **install order**, each one seeing the previous transform's output, so a plugin keys off the _content_ it recognizes rather than the block it lands in. The name is unique (register-once; a duplicate throws, naming the owning plugin) and scopes the transform for attribution.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voithos-labs/aragonite",
3
- "version": "0.10.1",
3
+ "version": "0.10.3",
4
4
  "description": "Svelte 5 block editor for GFM Markdown with byte-for-byte lossless round-trip and a plugin platform",
5
5
  "keywords": [
6
6
  "markdown",
@@ -202,6 +202,8 @@
202
202
  "devDependencies": {
203
203
  "@axe-core/playwright": "^4.13.0",
204
204
  "@eslint/js": "^10.0.1",
205
+ "@fontsource/inter": "^5.3.0",
206
+ "@fontsource/jetbrains-mono": "^5.3.0",
205
207
  "@playwright/test": "^1.62.0",
206
208
  "@sveltejs/adapter-static": "^3.0.0",
207
209
  "@sveltejs/kit": "^2.70.3",
@@ -227,7 +229,7 @@
227
229
  "typescript-eslint": "^8.67.0",
228
230
  "vite": "^8.2.2",
229
231
  "vitest": "^4.1.0",
230
- "wrangler": "4.125.0"
232
+ "wrangler": "4.131.0"
231
233
  },
232
234
  "overrides": {
233
235
  "cookie": "^0.7.2"
@@ -1,127 +0,0 @@
1
- <script lang="ts">
2
- import { tick } from 'svelte';
3
- import { CODE_LANGUAGE_FIELD, codeLanguageLabel } from '../../../a11y-strings';
4
-
5
- // The fence-info door for the modes that paint no fence. The draft lives here and only a
6
- // commit reaches the tree.
7
- let {
8
- info,
9
- editable,
10
- onCommit,
11
- onCancel
12
- }: {
13
- /** The opener's full info string; the button shows its first token. */
14
- info: string;
15
- /** False in reading mode, which writes no bytes — the chip is then a label. */
16
- editable: boolean;
17
- /** Only Enter calls this, and it owns the caret's landing afterwards. */
18
- onCommit: (info: string) => void;
19
- /** Nothing written. Escape asks for the caret back; a blur must not yank it from
20
- * wherever the user just clicked, so it does not. */
21
- onCancel: (returnCaret: boolean) => void;
22
- } = $props();
23
-
24
- const language = $derived(info.split(/\s+/)[0] || 'text');
25
-
26
- let editing = $state(false);
27
- let draft = $state('');
28
- let inputEl: HTMLInputElement | undefined = $state();
29
-
30
- function open(): void {
31
- if (!editable) return;
32
- draft = info;
33
- editing = true;
34
- void tick().then(() => {
35
- inputEl?.focus();
36
- inputEl?.select();
37
- });
38
- }
39
-
40
- function onFieldKeyDown(e: KeyboardEvent): void {
41
- // The field owns its keys while open: no Escape of this state may reach whatever else
42
- // listens for one. A composing Enter/Escape is the IME's, never the field's.
43
- e.stopPropagation();
44
- if (e.isComposing) return;
45
- if (e.key === 'Enter') {
46
- e.preventDefault();
47
- const committed = draft;
48
- editing = false;
49
- onCommit(committed);
50
- } else if (e.key === 'Escape') {
51
- e.preventDefault();
52
- editing = false;
53
- onCancel(true);
54
- }
55
- }
56
-
57
- function onFieldBlur(): void {
58
- if (!editing) return;
59
- editing = false;
60
- onCancel(false);
61
- }
62
- </script>
63
-
64
- <span class="code-lang-chip">
65
- {#if editing}
66
- <input
67
- bind:this={inputEl}
68
- bind:value={draft}
69
- type="text"
70
- spellcheck="false"
71
- aria-label={CODE_LANGUAGE_FIELD}
72
- onkeydown={onFieldKeyDown}
73
- onfocusout={onFieldBlur}
74
- />
75
- {:else}
76
- <button type="button" aria-label={codeLanguageLabel(language)} onclick={open}>{language}</button
77
- >
78
- {/if}
79
- </span>
80
-
81
- <style>
82
- /* Positioned against the block host, whose box the code box fills, and out of the code
83
- box's own scroller so a horizontal scroll leaves it where it is. */
84
- .code-lang-chip {
85
- position: absolute;
86
- top: 5px;
87
- right: 5px;
88
- z-index: 1;
89
- display: flex;
90
- font-family: var(--font-editor, ui-monospace, monospace);
91
- font-size: 0.75em;
92
- line-height: 1;
93
- opacity: 0;
94
- pointer-events: none;
95
- }
96
-
97
- /* Transient, like the drag handle: the pointer over the block or the caret inside it, plus
98
- the open field, which outlives both. Child and sibling combinators, so an outer container's
99
- hover never reveals a nested block's chip. */
100
- :global(.block-host:hover) > .code-lang-chip,
101
- :global(.code-block:focus) ~ .code-lang-chip,
102
- .code-lang-chip:focus-within {
103
- opacity: 1;
104
- pointer-events: auto;
105
- }
106
-
107
- button,
108
- input {
109
- padding: 2px 6px;
110
- border: 1px solid var(--color-ui-muted, #a4a4a4);
111
- border-radius: var(--radius-ui, 3px);
112
- background: var(--color-bg-elevated, #2a2a2a);
113
- color: var(--color-accent, #567b67);
114
- font: inherit;
115
- cursor: pointer;
116
- }
117
-
118
- button:hover {
119
- background: var(--color-ui-faint, rgba(255, 255, 255, 0.07));
120
- }
121
-
122
- input {
123
- width: 9ch;
124
- color: var(--color-text-secondary, #eee);
125
- cursor: text;
126
- }
127
- </style>
@@ -1,14 +0,0 @@
1
- type $$ComponentProps = {
2
- /** The opener's full info string; the button shows its first token. */
3
- info: string;
4
- /** False in reading mode, which writes no bytes — the chip is then a label. */
5
- editable: boolean;
6
- /** Only Enter calls this, and it owns the caret's landing afterwards. */
7
- onCommit: (info: string) => void;
8
- /** Nothing written. Escape asks for the caret back; a blur must not yank it from
9
- * wherever the user just clicked, so it does not. */
10
- onCancel: (returnCaret: boolean) => void;
11
- };
12
- declare const CodeLanguageChip: import("svelte").Component<$$ComponentProps, {}, "">;
13
- type CodeLanguageChip = ReturnType<typeof CodeLanguageChip>;
14
- export default CodeLanguageChip;
@@ -1,91 +0,0 @@
1
- <script lang="ts">
2
- let {
3
- axis,
4
- onActivate,
5
- onpointerdown
6
- }: {
7
- axis: 'row' | 'column';
8
- onActivate: (e: MouseEvent) => void;
9
- onpointerdown?: (e: PointerEvent) => void;
10
- } = $props();
11
-
12
- function handlePointerDown(e: PointerEvent): void {
13
- // The affordance opens a menu, it doesn't place a caret, so the grip's pointer
14
- // stream stays off the editor's selection/focus handlers.
15
- e.stopPropagation();
16
- onpointerdown?.(e);
17
- }
18
- </script>
19
-
20
- <!-- Pointer-only affordance: the cell chords are the operable, screen-reader path, so the
21
- grip stays out of the tab/SR flow. Reveal is pure CSS: hover in editor.css, touch below. -->
22
- <span class="table-grip-anchor table-grip-anchor-{axis}">
23
- <span
24
- class="table-grip table-grip-{axis}"
25
- data-table-col-grip={axis === 'column' ? '' : undefined}
26
- data-table-row-grip={axis === 'row' ? '' : undefined}
27
- aria-hidden="true"
28
- onclick={onActivate}
29
- onpointerdown={handlePointerDown}
30
- ></span>
31
- </span>
32
-
33
- <style>
34
- /* Zero-height so it never adds a visible band or shifts caret-measured layout;
35
- z-index lifts the dots above the DOM-later header cell, which would eat the click. */
36
- .table-grip-anchor-column {
37
- position: relative;
38
- z-index: 2;
39
- height: 0;
40
- }
41
-
42
- /* The dotted bar shared by both axes; the axis rules add only the geometry. */
43
- .table-grip {
44
- position: absolute;
45
- color: var(--color-ui-muted, #a4a4a4);
46
- background-image: radial-gradient(currentColor 40%, transparent 45%);
47
- background-position: center;
48
- cursor: pointer;
49
- opacity: 0;
50
- pointer-events: none;
51
- }
52
-
53
- /* Sits within the header cell's top padding so it never overlaps the caret-click target. */
54
- .table-grip-column {
55
- top: 0;
56
- left: 50%;
57
- transform: translateX(-50%);
58
- width: 1.75rem;
59
- height: 0.35rem;
60
- background-size: 0.25rem 0.35rem;
61
- background-repeat: repeat-x;
62
- }
63
-
64
- /* Zero-width gutter item, stretched to the row height. z-index lifts the dots above
65
- cell A, which renders DOM-later and would otherwise paint over them. */
66
- .table-grip-anchor-row {
67
- position: relative;
68
- z-index: 2;
69
- }
70
-
71
- /* Overflows out of the zero-width gutter into cell A's left padding, clear of the text. */
72
- .table-grip-row {
73
- top: 50%;
74
- left: 0.15rem;
75
- transform: translateY(-50%);
76
- width: 0.35rem;
77
- height: 1rem;
78
- background-size: 0.35rem 0.25rem;
79
- background-repeat: repeat-y;
80
- }
81
-
82
- /* Touch fires no hover, so the editor.css reveal never runs and an opted-in host's grips
83
- would be unreachable. `touch-action` keeps a reorder drag off the table's own scroll. */
84
- @media (hover: none) {
85
- .table-grip {
86
- opacity: 1;
87
- pointer-events: auto;
88
- touch-action: none;
89
- }
90
- }
91
- </style>
@@ -1,8 +0,0 @@
1
- type $$ComponentProps = {
2
- axis: 'row' | 'column';
3
- onActivate: (e: MouseEvent) => void;
4
- onpointerdown?: (e: PointerEvent) => void;
5
- };
6
- declare const TableGrip: import("svelte").Component<$$ComponentProps, {}, "">;
7
- type TableGrip = ReturnType<typeof TableGrip>;
8
- export default TableGrip;
@@ -1 +0,0 @@
1
- export declare function dropGapIndex(pointer: number, edges: number[]): number;
@@ -1,16 +0,0 @@
1
- // Map a reorder drag's pointer to the insertion gap it sits nearest. `edges` are the
2
- // row/column boundaries in pointer space, so N tracks yield N+1 edges. The result is a
3
- // gap index, not the occupied cell `table-drag-hit-test` returns.
4
- export function dropGapIndex(pointer, edges) {
5
- let nearest = 0;
6
- let nearestDistance = Infinity;
7
- for (let i = 0; i < edges.length; i++) {
8
- const distance = Math.abs(pointer - edges[i]);
9
- // Strict `<` keeps the lower gap when the pointer is exactly equidistant.
10
- if (distance < nearestDistance) {
11
- nearest = i;
12
- nearestDistance = distance;
13
- }
14
- }
15
- return nearest;
16
- }
@@ -1,78 +0,0 @@
1
- /**
2
- * Pointer drag-to-reorder for table rows and columns: a shared drag session paints a single
3
- * insertion line (no tree mutation, no reflow) and commits ONE move on release. The axes diverge
4
- * only in geometry — rows are windowed under a fixed header, so their indices are absolute and
5
- * off-window targets need autoscroll to mount; columns are neither.
6
- */
7
- import type { UserScrollport } from '../../../cursor/scroll-ancestors';
8
- import { type PointerPosition } from '../../../selection/pointer-session';
9
- /** Members every reorder-axis context shares; the row/column contexts extend it. */
10
- interface AxisReorderDragContext<TLine> {
11
- /** The editor's scrollport, which in host mode may be an ancestor or the window. */
12
- getScrollContainer(): UserScrollport | null;
13
- setLine(line: TLine | null): void;
14
- /** Marks the gesture a drag (not a click) so the grip's menu stays closed. */
15
- onDragRecognized(): void;
16
- commit(from: number, to: number): void;
17
- /** Aborted on editor unmount — tears down a drag whose pointerup can't fire. */
18
- lifetimeSignal?: AbortSignal;
19
- }
20
- export interface TableReorderDragContext<TLine> extends AxisReorderDragContext<TLine> {
21
- from: number;
22
- /**
23
- * Pure per-axis map from the live pointer to the insertion line and clamped drop
24
- * index (null when geometry is unavailable). Returns rather than sets, so the axis
25
- * clamps stay unit-testable in isolation.
26
- */
27
- process(pointer: PointerPosition): {
28
- line: TLine;
29
- dropTo: number;
30
- } | null;
31
- /** The autoscroll axis only — rows scroll both ways, a wide table's columns horizontally. */
32
- autoScrollAxis: 'horizontal' | 'vertical' | 'both';
33
- }
34
- export declare function startTableReorderDrag<TLine>(down: PointerEvent, ctx: TableReorderDragContext<TLine>): void;
35
- export interface RowReorderLine {
36
- left: number;
37
- top: number;
38
- width: number;
39
- }
40
- export interface RowReorderGeometry {
41
- /** Viewport-Y boundaries of the mounted rows: each row's top, plus the last row's bottom. */
42
- rowEdges: number[];
43
- /**
44
- * Absolute gap index for each `rowEdges` entry, so a drop maps a local edge to a
45
- * body position even once the window has scrolled past row 0.
46
- */
47
- gapIndices: number[];
48
- /** Viewport-left and width of the table, for the insertion line's horizontal span. */
49
- left: number;
50
- width: number;
51
- }
52
- export interface RowReorderDragContext extends AxisReorderDragContext<RowReorderLine> {
53
- fromRowIdx: number;
54
- /** Live total row count — read each move so the header clamp tracks edits/re-slices. */
55
- getRowCount(): number;
56
- getGeometry(): RowReorderGeometry | null;
57
- }
58
- export declare function startRowReorderDrag(down: PointerEvent, ctx: RowReorderDragContext): void;
59
- export interface ColumnReorderLine {
60
- left: number;
61
- top: number;
62
- height: number;
63
- }
64
- export interface ColumnReorderGeometry {
65
- /** Viewport-X boundaries of the columns: each column's left, plus the last column's right. */
66
- colEdges: number[];
67
- /** Viewport-top and height of the table, for the insertion line's vertical span. */
68
- top: number;
69
- height: number;
70
- }
71
- export interface ColumnReorderDragContext extends AxisReorderDragContext<ColumnReorderLine> {
72
- fromColIdx: number;
73
- /** Live total column count — read each move so the clamp tracks edits. */
74
- getColCount(): number;
75
- getGeometry(): ColumnReorderGeometry | null;
76
- }
77
- export declare function startColumnReorderDrag(down: PointerEvent, ctx: ColumnReorderDragContext): void;
78
- export {};
@@ -1,97 +0,0 @@
1
- /**
2
- * Pointer drag-to-reorder for table rows and columns: a shared drag session paints a single
3
- * insertion line (no tree mutation, no reflow) and commits ONE move on release. The axes diverge
4
- * only in geometry — rows are windowed under a fixed header, so their indices are absolute and
5
- * off-window targets need autoscroll to mount; columns are neither.
6
- */
7
- import { dropGapIndex } from './table-drop-target';
8
- import { createPointerDragSession } from '../../../selection/pointer-session';
9
- // Past this much pointer travel the gesture is a drag, not a menu-opening click.
10
- const DRAG_THRESHOLD_PX = 4;
11
- export function startTableReorderDrag(down, ctx) {
12
- let dragging = false;
13
- let dropTo = null;
14
- createPointerDragSession(down, {
15
- threshold: DRAG_THRESHOLD_PX,
16
- onDragRecognized: () => {
17
- dragging = true;
18
- ctx.onDragRecognized();
19
- },
20
- onMove: (pointer) => {
21
- const result = ctx.process(pointer);
22
- if (!result)
23
- return;
24
- dropTo = result.dropTo;
25
- ctx.setLine(result.line);
26
- },
27
- // A sub-threshold press is a menu-opening click, not a reorder.
28
- onEnd: (reason) => {
29
- if (reason === 'up' && dragging && dropTo !== null && dropTo !== ctx.from) {
30
- ctx.commit(ctx.from, dropTo);
31
- }
32
- },
33
- onTeardown: () => ctx.setLine(null),
34
- autoScroll: {
35
- axis: ctx.autoScrollAxis,
36
- getTargets: () => {
37
- const sc = ctx.getScrollContainer();
38
- return sc ? [sc] : [];
39
- }
40
- },
41
- escape: true,
42
- // Up front, not at threshold: a native text selection can begin from the empty
43
- // grip before the first qualifying move and survive the drag.
44
- disableUserSelect: true,
45
- lifetimeSignal: ctx.lifetimeSignal
46
- });
47
- }
48
- export function startRowReorderDrag(down, ctx) {
49
- startTableReorderDrag(down, {
50
- from: ctx.fromRowIdx,
51
- autoScrollAxis: 'both',
52
- getScrollContainer: ctx.getScrollContainer,
53
- setLine: ctx.setLine,
54
- onDragRecognized: ctx.onDragRecognized,
55
- commit: ctx.commit,
56
- lifetimeSignal: ctx.lifetimeSignal,
57
- process(pointer) {
58
- const geom = ctx.getGeometry();
59
- if (!geom)
60
- return null;
61
- // The LOCAL edge among mounted rows; gapIndices maps it to the absolute gap.
62
- let edge = dropGapIndex(pointer.clientY, geom.rowEdges);
63
- // Never land above the fixed header (gap 0); reachable only while row 0 is mounted.
64
- if (geom.gapIndices[edge] < 1)
65
- edge = Math.min(edge + 1, geom.gapIndices.length - 1);
66
- const gap = geom.gapIndices[edge];
67
- // Insert semantics: removing the dragged row shifts later slots down by one.
68
- const target = gap <= ctx.fromRowIdx ? gap : gap - 1;
69
- const dropTo = Math.max(1, Math.min(target, ctx.getRowCount() - 1));
70
- return { line: { left: geom.left, top: geom.rowEdges[edge], width: geom.width }, dropTo };
71
- }
72
- });
73
- }
74
- export function startColumnReorderDrag(down, ctx) {
75
- startTableReorderDrag(down, {
76
- from: ctx.fromColIdx,
77
- // Pinned in the table's top band; vertical evaluation would spin the loop on a
78
- // `.table-block` that only scrolls horizontally (autoscroll.ts § axis).
79
- autoScrollAxis: 'horizontal',
80
- getScrollContainer: ctx.getScrollContainer,
81
- setLine: ctx.setLine,
82
- onDragRecognized: ctx.onDragRecognized,
83
- commit: ctx.commit,
84
- lifetimeSignal: ctx.lifetimeSignal,
85
- process(pointer) {
86
- const geom = ctx.getGeometry();
87
- if (!geom)
88
- return null;
89
- // Columns aren't windowed and have no header, so the edge index IS the drop gap.
90
- const gap = dropGapIndex(pointer.clientX, geom.colEdges);
91
- // Insert semantics: removing the dragged column shifts later slots left by one.
92
- const target = gap <= ctx.fromColIdx ? gap : gap - 1;
93
- const dropTo = Math.max(0, Math.min(target, ctx.getColCount() - 1));
94
- return { line: { left: geom.colEdges[gap], top: geom.top, height: geom.height }, dropTo };
95
- }
96
- });
97
- }