@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
@@ -26,8 +26,17 @@
26
26
  import type { TableAlignment } from '../../../core/nodes';
27
27
  import { trimTrailingLineEnding, normalizeLineEndings } from '../../../core/lines';
28
28
  import { pasteDispatch } from '../../../tree-operations/paste/dispatch';
29
- import { blockNodeAt, cutRangeFromDisplay } from '../../../tree-operations/node-ops';
29
+ import { blockNodeAt, isBlockNode, nodeAt } from '../../../tree-operations/node-primitives';
30
+ import { cutRangeFromDisplay } from '../../../tree-operations/node-ops';
30
31
  import { applyLiveRangeEdit } from '../text/live-selection-edit';
32
+ import {
33
+ gridToHtmlTable,
34
+ parseClipboardGrid,
35
+ tileGridTo
36
+ } from '../../../tree-operations/table-grid-clipboard';
37
+ import { tableCellCount } from '../../../selection/table-endpoint-snap';
38
+ import { pathsEqual } from '../../../selection/path-math';
39
+ import { applyDelimiterAutoPair } from '../text/delimiter-autopair';
31
40
  import { hasSelection as hasSelectionHelper } from '../../../cursor/content-offsets';
32
41
  import { FALLBACK_CONTENT_WIDTH } from '../../../cursor/typography-estimates';
33
42
  import {
@@ -45,7 +54,8 @@
45
54
  import {
46
55
  createEditableSurface,
47
56
  createClipboardHandlers,
48
- consumePendingRestore
57
+ consumePendingRestore,
58
+ withKeydownVerdict
49
59
  } from '../editable-surface';
50
60
  import { wireSurfaceContexts, useParkFocusOnUnmount } from '../surface-wiring.svelte';
51
61
  import { resetForPointerDown } from '../../../selection/cross-block/pointer';
@@ -59,7 +69,11 @@
59
69
  import { isAtFirstVisualLine, isAtLastVisualLine } from '../../../cursor/visual-lines';
60
70
  import { cellKeydownPlan, type CellKeyPlan, type CellKeyState } from './cell-keydown-plan';
61
71
  import { tableAxisCommand } from './cell-table-commands';
62
- import { intraTableRectPayload } from './cell-clipboard';
72
+ import {
73
+ intraTableRectPayload,
74
+ intraTableRectBounds,
75
+ intraTableRectGrid
76
+ } from './cell-clipboard';
63
77
  import { escapedCellOffset } from './table-cell-paste';
64
78
  import type { CellSelectionPoint, SelectionPoint } from '../../../selection/primitives';
65
79
  import type { ClipboardAction } from './table-menu-model';
@@ -325,6 +339,7 @@
325
339
  },
326
340
  isReading: () => readOnly,
327
341
  getEdgeAffinity: () => edgeAffinity.get(),
342
+ noteOutside: edgeAffinity.noteExtreme,
328
343
  pendingMarks,
329
344
  installedAs: 'cell'
330
345
  });
@@ -533,9 +548,9 @@
533
548
  carryCaret: pendingCursorOffset === null
534
549
  });
535
550
  if (pendingCursorOffset !== null) {
536
- consumePendingRestore(el, pendingCursorOffset, (offset) =>
537
- cursor.setRaw(asRawOffset(offset))
538
- );
551
+ consumePendingRestore(el, pendingCursorOffset, (offset) => {
552
+ if (!widgetInteraction.revealInterior(offset)) cursor.setRaw(asRawOffset(offset));
553
+ });
539
554
  pendingCursorOffset = null;
540
555
  }
541
556
  });
@@ -663,23 +678,13 @@
663
678
  if (edgeDispatch.handleKeydown(e, cursor.getRaw())) return;
664
679
  return;
665
680
  }
666
- // Cells override the document-level 2-stage Ctrl+A with a 3-stage table-aware
667
- // variant; the intra-cell step stays native.
681
+ // The document-level two-stage Ctrl+A, cell first: the intra-cell step stays native,
682
+ // the second press takes the document.
668
683
  case 'select-all-step':
669
684
  selection.incrementSelectAllCount();
670
685
  if (plan.step === 'native') return;
671
686
  e.preventDefault();
672
- if (plan.step === 'table') {
673
- const tablePath = myPath.slice(0, -2);
674
- // Flagged row-major like the drag/shift-click anchor, so a later
675
- // exit-the-table extend snaps its whole row.
676
- selection.enterCrossBlock(
677
- { path: tablePath, offset: 0, cellCoordinate: true } satisfies CellSelectionPoint,
678
- { path: tablePath, offset: columnCount * rowCount - 1 }
679
- );
680
- } else {
681
- selectWholeDocument(selection, getDoc(), getBlockElByPath);
682
- }
687
+ selectWholeDocument(selection, getDoc(), getBlockElByPath);
683
688
  return;
684
689
  default:
685
690
  e.preventDefault();
@@ -690,6 +695,8 @@
690
695
  }
691
696
  }
692
697
 
698
+ const onKeyDownTraced = withKeydownVerdict(onKeyDown);
699
+
693
700
  // The navigation plans, no live event needed; the caller preventDefaults. The fold is here
694
701
  // rather than at each caller: insert-row-below rebuilds every row from cell raws, so an open
695
702
  // reveal's edit would be re-derived away.
@@ -778,9 +785,29 @@
778
785
  );
779
786
  }
780
787
 
788
+ // The cell at the prose surface's self-closing delimiter arm (delimiter-autopair.ts).
789
+ function handleDelimiterAutoPair(e: InputEvent): boolean {
790
+ return applyDelimiterAutoPair(e, {
791
+ text: readCellText,
792
+ content: () => ({ start: 0, end: readCellText().length }),
793
+ caret: () => cursor.getRaw(),
794
+ hasSelection: () => cursor.getRawSelection() !== null,
795
+ isRevealing: widgetInteraction.isRevealing,
796
+ foldReveal: () => widgetInteraction.foldRevealBeforeMutation(),
797
+ markersPaint: () => paintsFocusedMarkers(presentationMode),
798
+ setCaret: (offset) => cursor.setRaw(asRawOffset(offset)),
799
+ seatOutside: edgeAffinity.noteExtreme,
800
+ write: (text, caretBefore, caretAfter) => {
801
+ void blockEdit.updateBlockContent(index, text, caretBefore, caretAfter);
802
+ parkCursor(caretAfter, text);
803
+ }
804
+ });
805
+ }
806
+
781
807
  async function onBeforeInput(e: InputEvent): Promise<void> {
782
808
  if (await handleSharedBeforeInput(e, sharedCtx)) return;
783
809
  if (handleLiveSelectionEdit(e)) return;
810
+ if (handleDelimiterAutoPair(e)) return;
784
811
  if (e.inputType === 'insertLineBreak') {
785
812
  // GFM cells can't carry raw newlines, so a line break is a literal `<br>`,
786
813
  // which the inline-HTML pipeline renders as a live widget.
@@ -853,6 +880,36 @@
853
880
  // Copy/cut/paste through the shared skeleton. The cell's extra arms are the intra-table
854
881
  // rectangle (copied as a GFM sub-table) and the intra-cell raw slice, which preserves
855
882
  // widget bytes like `<br>` that the browser's rendered-textContent copy drops.
883
+ // The rectangle's spreadsheet form rides beside the GFM: Excel and Sheets read text/html.
884
+ function writeRectHtml(e: ClipboardEvent): void {
885
+ const grid = intraTableRectGrid({ selection, getDoc });
886
+ if (grid) e.clipboardData?.setData('text/html', gridToHtmlTable(grid));
887
+ }
888
+
889
+ // A grid (tabs from a spreadsheet, or a GFM table) fills cells from here — the rectangle's
890
+ // top-left when one is live, else this cell — growing the table to fit. A whole-table
891
+ // selection keeps its replace route; a non-grid payload keeps the ordinary paste.
892
+ async function pasteGridHere(text: string): Promise<boolean> {
893
+ const grid = parseClipboardGrid(text);
894
+ if (!grid) return false;
895
+ const tablePath = myPath.slice(0, -2);
896
+ const bounds = intraTableRectBounds({ selection, getDoc });
897
+ const inThisTable = bounds !== null && pathsEqual(bounds.tablePath, tablePath);
898
+ if (bounds && !inThisTable) return false;
899
+ const tableNode = nodeAt(getDoc(), tablePath);
900
+ const wholeTable =
901
+ bounds !== null &&
902
+ tableNode !== null &&
903
+ isBlockNode(tableNode) &&
904
+ bounds.rows * bounds.cols === tableCellCount(tableNode);
905
+ if (wholeTable) return false;
906
+ const origin = bounds ? { rowIdx: bounds.top, colIdx: bounds.left } : { rowIdx, colIdx };
907
+ const fitted = bounds ? tileGridTo(grid, bounds.rows, bounds.cols) : grid;
908
+ if (bounds) selection.collapse();
909
+ await tableContext.pasteGrid(origin, fitted);
910
+ return true;
911
+ }
912
+
856
913
  const clipboard = createClipboardHandlers({
857
914
  stickyColumn,
858
915
  edgeAffinity,
@@ -869,8 +926,10 @@
869
926
  if (rectPayload === null) return false;
870
927
  e.preventDefault();
871
928
  e.clipboardData?.setData('text/plain', rectPayload);
929
+ writeRectHtml(e);
872
930
  return true;
873
931
  },
932
+ pastePreHook: pasteGridHere,
874
933
  // During a reveal the swapped DOM holds an edit `node.raw` hasn't seen; copy never
875
934
  // mutates, so it slices the live DOM text rather than folding first.
876
935
  copyTail: (e) => {
@@ -889,6 +948,7 @@
889
948
  const rectPayload = intraTableRectPayload({ selection, getDoc });
890
949
  if (rectPayload === null) return false;
891
950
  e.clipboardData?.setData('text/plain', rectPayload);
951
+ writeRectHtml(e);
892
952
  await crossBlock.performCrossBlockDeleteFromEvent();
893
953
  return true;
894
954
  },
@@ -982,7 +1042,7 @@
982
1042
  // mutating: execCommand needs the restored range, paste needs a focused caret.
983
1043
  stickyColumn.reset();
984
1044
  edgeAffinity.reset();
985
- el.focus();
1045
+ el.focus({ preventScroll: true });
986
1046
  if (action === 'paste') {
987
1047
  let raw: string;
988
1048
  try {
@@ -1046,7 +1106,7 @@
1046
1106
  role="cell"
1047
1107
  style:text-align={alignment === 'none' ? undefined : alignment}
1048
1108
  oninput={onInput}
1049
- onkeydown={onKeyDown}
1109
+ onkeydown={onKeyDownTraced}
1050
1110
  onbeforeinput={onBeforeInput}
1051
1111
  onpointerdown={onPointerDown}
1052
1112
  onclick={onClick}
@@ -1060,13 +1120,18 @@
1060
1120
  ></div>
1061
1121
 
1062
1122
  <style>
1123
+ /* Two sides only. The grid has no `border-collapse`, so a cell drawing all four put two
1124
+ 1px lines on every shared edge — visibly heavier inside the table than around it. The
1125
+ container draws the top and left, these draw the right and bottom, and every rule in
1126
+ the grid is one line wide. */
1063
1127
  .table-cell {
1064
1128
  outline: none;
1065
1129
  padding: 4px 8px;
1066
1130
  min-height: 1.4em;
1067
1131
  white-space: pre-wrap;
1068
1132
  word-wrap: break-word;
1069
- border: 1px solid var(--color-ui-muted, #a4a4a4);
1133
+ border-right: 1px solid var(--color-border, #3e3e3b);
1134
+ border-bottom: 1px solid var(--color-border, #3e3e3b);
1070
1135
  }
1071
1136
  .table-cell:focus {
1072
1137
  outline: 2px solid var(--color-accent, #567b67);
@@ -30,7 +30,6 @@
30
30
  } from '../../../editor-actions/nested/nested-actions';
31
31
  import { publishRefSlot, type RefSlots } from '../../../reactivity/publish-ref.svelte';
32
32
  import TableCellBlock from './TableCellBlock.svelte';
33
- import TableGrip from './TableGrip.svelte';
34
33
 
35
34
  let {
36
35
  node,
@@ -40,10 +39,7 @@
40
39
  rowCount,
41
40
  alignments = [],
42
41
  myPath = [],
43
- slots,
44
- onOpenRowMenu,
45
- onRowGripPointerDown,
46
- showGrips
42
+ slots
47
43
  }: {
48
44
  node: NodeView;
49
45
  index: number;
@@ -53,11 +49,6 @@
53
49
  alignments?: readonly TableAlignment[];
54
50
  myPath?: number[];
55
51
  slots?: RefSlots<BlockComponent>;
56
- onOpenRowMenu?: (rowIdx: number, e: MouseEvent) => void;
57
- onRowGripPointerDown?: (rowIdx: number, e: PointerEvent) => void;
58
- /** The table's `blockDragHandles` read, passed down rather than re-read: the row grip
59
- * and the gutter track it fills are one decision. */
60
- showGrips: boolean;
61
52
  } = $props();
62
53
 
63
54
  // A row's position among the table's children IS its row index.
@@ -204,15 +195,10 @@
204
195
  });
205
196
  </script>
206
197
 
207
- <!-- The grip is the row's first child so it lands in the table's zero-width gutter track
208
- and the cells fill the rest. No whitespace between it and the cells: a stray text node
209
- joins the table's raw-offset walk and misplaces a parked cross-block caret. -->
198
+ <!-- No whitespace between the row and its cells: a stray text node joins the table's
199
+ raw-offset walk and misplaces a parked cross-block caret. -->
210
200
  <div bind:this={rowEl} class="table-row" role="row" data-table-row-idx={rowIdx}>
211
- {#if showGrips}<TableGrip
212
- axis="row"
213
- onActivate={(e) => onOpenRowMenu?.(rowIdx, e)}
214
- onpointerdown={(e) => onRowGripPointerDown?.(rowIdx, e)}
215
- />{/if}{#each node.children ?? [] as cellNode, colIdx (cellsState.innerBlockIds[colIdx])}
201
+ {#each node.children ?? [] as cellNode, colIdx (cellsState.innerBlockIds[colIdx])}
216
202
  <TableCellBlock
217
203
  node={cellNode}
218
204
  index={colIdx}
@@ -11,11 +11,6 @@ type $$ComponentProps = {
11
11
  alignments?: readonly TableAlignment[];
12
12
  myPath?: number[];
13
13
  slots?: RefSlots<BlockComponent>;
14
- onOpenRowMenu?: (rowIdx: number, e: MouseEvent) => void;
15
- onRowGripPointerDown?: (rowIdx: number, e: PointerEvent) => void;
16
- /** The table's `blockDragHandles` read, passed down rather than re-read: the row grip
17
- * and the gutter track it fills are one decision. */
18
- showGrips: boolean;
19
14
  };
20
15
  declare const TableRowBlock: import("svelte").Component<$$ComponentProps, {
21
16
  editable: true;
@@ -21,3 +21,13 @@ export interface IntraTableRect {
21
21
  export declare function intraTableRect(selection: SelectionState): IntraTableRect | null;
22
22
  /** The GFM sub-table for the current selection, or null when it isn't a rectangle. */
23
23
  export declare function intraTableRectPayload(deps: CellClipboardDeps): string | null;
24
+ /** The rectangle's bounds in row/column terms, or null when the selection isn't one. */
25
+ export declare function intraTableRectBounds(deps: CellClipboardDeps): {
26
+ tablePath: number[];
27
+ top: number;
28
+ left: number;
29
+ rows: number;
30
+ cols: number;
31
+ } | null;
32
+ /** The rectangle's cell texts as a grid (pipes unescaped), for the spreadsheet formats. */
33
+ export declare function intraTableRectGrid(deps: CellClipboardDeps): string[][] | null;
@@ -3,10 +3,11 @@
3
3
  * owns the live ClipboardEvent wiring.
4
4
  */
5
5
  import { metadataOf } from '../../../core/nodes';
6
- import { isBlockNode, nodeAt } from '../../../tree-operations/node-ops';
6
+ import { isBlockNode, nodeAt } from '../../../tree-operations/node-primitives';
7
7
  import { pathsEqual } from '../../../selection/path-math';
8
8
  import { cellRowCol } from '../../../cursor/coordinate-spaces';
9
9
  import { copyRectangleAsSubTable } from '../../../tree-operations/sub-table-copy';
10
+ import { rectangleGrid } from '../../../tree-operations/table-grid-clipboard';
10
11
  /**
11
12
  * Both endpoints share the table path, so each `offset` is a row-major cell index
12
13
  * (unflagged — see `selection/primitives` on context-established cell offsets). Callers
@@ -32,3 +33,35 @@ export function intraTableRectPayload(deps) {
32
33
  const b = cellRowCol(rect.focusCellIdx, colCount);
33
34
  return copyRectangleAsSubTable(tableNode, { rowIdx: a.row, colIdx: a.col }, { rowIdx: b.row, colIdx: b.col });
34
35
  }
36
+ /** The rectangle's bounds in row/column terms, or null when the selection isn't one. */
37
+ export function intraTableRectBounds(deps) {
38
+ const rect = intraTableRect(deps.selection);
39
+ if (!rect)
40
+ return null;
41
+ const tableNode = nodeAt(deps.getDoc(), rect.tablePath);
42
+ if (!tableNode || !isBlockNode(tableNode) || tableNode.kind !== 'table')
43
+ return null;
44
+ const colCount = metadataOf(tableNode, 'table').columnCount;
45
+ const a = cellRowCol(rect.anchorCellIdx, colCount);
46
+ const b = cellRowCol(rect.focusCellIdx, colCount);
47
+ const top = Math.min(a.row, b.row);
48
+ const left = Math.min(a.col, b.col);
49
+ return {
50
+ tablePath: rect.tablePath,
51
+ top,
52
+ left,
53
+ rows: Math.max(a.row, b.row) - top + 1,
54
+ cols: Math.max(a.col, b.col) - left + 1
55
+ };
56
+ }
57
+ /** The rectangle's cell texts as a grid (pipes unescaped), for the spreadsheet formats. */
58
+ export function intraTableRectGrid(deps) {
59
+ const bounds = intraTableRectBounds(deps);
60
+ if (!bounds)
61
+ return null;
62
+ const tableNode = nodeAt(deps.getDoc(), bounds.tablePath);
63
+ if (!tableNode || !isBlockNode(tableNode))
64
+ return null;
65
+ const { top, left, rows, cols } = bounds;
66
+ return rectangleGrid(tableNode, { rowIdx: top, colIdx: left }, { rowIdx: top + rows - 1, colIdx: left + cols - 1 });
67
+ }
@@ -21,7 +21,7 @@ export type CellKeyPlan = {
21
21
  kind: 'native';
22
22
  } | {
23
23
  kind: 'select-all-step';
24
- step: 'native' | 'table' | 'document';
24
+ step: 'native' | 'document';
25
25
  } | {
26
26
  kind: 'focus-cell';
27
27
  rowIdx: number;
@@ -12,10 +12,12 @@ function isLetterA(key) {
12
12
  }
13
13
  export function cellKeydownPlan(e, s) {
14
14
  const pos = { rowIdx: s.rowIdx, colIdx: s.colIdx };
15
+ // Two stages, like every other block: the cell's own text natively, then the document. A
16
+ // table stage in between was this editor's alone; Docs and friends go straight to the page.
15
17
  if (e.ctrlOrMeta && isLetterA(e.key) && !e.shiftKey && !e.altKey) {
16
18
  return {
17
19
  kind: 'select-all-step',
18
- step: s.selectAllCount === 0 ? 'native' : s.selectAllCount === 1 ? 'table' : 'document'
20
+ step: s.selectAllCount === 0 ? 'native' : 'document'
19
21
  };
20
22
  }
21
23
  if (e.key === 'ArrowLeft' && !e.shiftKey && s.offset <= s.contentStart && s.collapsed) {
@@ -6,7 +6,8 @@
6
6
  */
7
7
  import { CURSOR_END } from '../../../block-component';
8
8
  import { normalizeCellRaw } from '../../../schema/table-cell-raw';
9
- import { blockNodeAt, cutRangeFromDisplay } from '../../../tree-operations/node-ops';
9
+ import { blockNodeAt } from '../../../tree-operations/node-primitives';
10
+ import { cutRangeFromDisplay } from '../../../tree-operations/node-ops';
10
11
  import { sliceTableAtRow } from '../../../tree-operations/paste/table-slice';
11
12
  import { focusIndexBeforeResidue } from '../../../tree-operations/paste/focus-target';
12
13
  import { replaceBlockAtParent } from '../../../tree-operations/paste/replace-block-at-parent';
@@ -1,14 +1,14 @@
1
1
  /**
2
- * Pure model for the table affordance menu: which items a target cell, row, or column
3
- * offers and whether each is enabled. Enablement reuses the context's own refusal
4
- * predicates, so a disabled item can never reach a no-op commit.
2
+ * Pure model for the cell menu: which items a cell offers and whether each is enabled.
3
+ * Enablement reuses the context's own refusal predicates, so a disabled item can never reach
4
+ * a no-op commit.
5
5
  */
6
6
  import type { TableAxisAction } from '../../../action-contracts';
7
7
  import type { TableAlignment } from '../../../core/nodes';
8
8
  export type ClipboardAction = 'cut' | 'copy' | 'paste';
9
9
  /**
10
10
  * Clamp a fixed-position menu's top-left into the viewport (minus `margin`) — menus
11
- * open at a raw pointer/grip coordinate. Larger than the viewport pins to top/left.
11
+ * open at a raw pointer coordinate. Larger than the viewport pins to top/left.
12
12
  */
13
13
  export declare function clampMenuToViewport(desired: {
14
14
  x: number;
@@ -23,6 +23,26 @@ export declare function clampMenuToViewport(desired: {
23
23
  x: number;
24
24
  y: number;
25
25
  };
26
+ /**
27
+ * Where a flyout hung off its parent row ends up on screen: shifted UP just enough to clear the
28
+ * viewport bottom (never above the top margin) and, when its right edge would overflow, flipped
29
+ * to the parent menu's left side. Shifting vertically keeps the hovered row pointing into it;
30
+ * flipping horizontally keeps it off its own parent. Pure, so the two flyouts share one rule.
31
+ */
32
+ export declare function flyoutPlacement(flyout: {
33
+ top: number;
34
+ bottom: number;
35
+ right: number;
36
+ width: number;
37
+ }, parentMenu: {
38
+ left: number;
39
+ }, viewport: {
40
+ width: number;
41
+ height: number;
42
+ }, margin?: number): {
43
+ dy: number;
44
+ flip: boolean;
45
+ };
26
46
  export type TableMenuItem = {
27
47
  kind: 'action';
28
48
  action: TableAxisAction;
@@ -39,14 +59,21 @@ export type TableMenuItem = {
39
59
  current: TableAlignment;
40
60
  } | {
41
61
  kind: 'separator';
62
+ }
63
+ /** A flyout: the row's or column's less-used actions behind one entry. */
64
+ | {
65
+ kind: 'group';
66
+ id: 'row' | 'column';
67
+ label: string;
68
+ items: TableMenuItem[];
42
69
  };
43
- export declare function tableMenuItems(target: {
44
- rowIdx?: number;
45
- colIdx?: number;
70
+ export declare function tableMenuItems(cell: {
71
+ rowIdx: number;
72
+ colIdx: number;
46
73
  }, dims: {
47
74
  rowCount: number;
48
75
  colCount: number;
49
- }, alignments: readonly TableAlignment[], clipboard?: {
76
+ }, alignments: readonly TableAlignment[], clipboard: {
50
77
  hasSelection: boolean;
51
78
  hasRect?: boolean;
52
79
  }): TableMenuItem[];
@@ -2,7 +2,7 @@ import { tableRowReorderTarget, tableColumnReorderTarget } from '../../../editor
2
2
  import { canDeleteRow, canDeleteColumn } from '../../../tree-operations/table-mutations';
3
3
  /**
4
4
  * Clamp a fixed-position menu's top-left into the viewport (minus `margin`) — menus
5
- * open at a raw pointer/grip coordinate. Larger than the viewport pins to top/left.
5
+ * open at a raw pointer coordinate. Larger than the viewport pins to top/left.
6
6
  */
7
7
  export function clampMenuToViewport(desired, menu, viewport, margin = 8) {
8
8
  const maxX = Math.max(margin, viewport.width - menu.width - margin);
@@ -12,23 +12,42 @@ export function clampMenuToViewport(desired, menu, viewport, margin = 8) {
12
12
  y: Math.min(Math.max(margin, desired.y), maxY)
13
13
  };
14
14
  }
15
- export function tableMenuItems(target, dims, alignments,
16
- // Present only for a cell right-click; grip menus never show the clipboard group. A
17
- // live rectangle (`hasRect`) suppresses the cell-local selection but still serves Cut/Copy.
15
+ /**
16
+ * Where a flyout hung off its parent row ends up on screen: shifted UP just enough to clear the
17
+ * viewport bottom (never above the top margin) and, when its right edge would overflow, flipped
18
+ * to the parent menu's left side. Shifting vertically keeps the hovered row pointing into it;
19
+ * flipping horizontally keeps it off its own parent. Pure, so the two flyouts share one rule.
20
+ */
21
+ export function flyoutPlacement(flyout, parentMenu, viewport, margin = 8) {
22
+ const overflow = flyout.bottom + margin - viewport.height;
23
+ const dy = overflow > 0 ? -Math.min(overflow, Math.max(0, flyout.top - margin)) : 0;
24
+ const overflowsRight = flyout.right + margin > viewport.width;
25
+ const fitsLeft = parentMenu.left - flyout.width - margin >= 0;
26
+ return { dy, flip: overflowsRight && fitsLeft };
27
+ }
28
+ export function tableMenuItems(cell, dims, alignments,
29
+ // A live rectangle (`hasRect`) suppresses the cell-local selection but still serves Cut/Copy.
18
30
  clipboard) {
19
- const items = [];
20
- const isCell = target.rowIdx != null && target.colIdx != null;
21
- if (isCell && clipboard)
22
- items.push(...clipboardGroup(clipboard.hasSelection || clipboard.hasRect === true), {
23
- kind: 'separator'
24
- });
25
- if (target.rowIdx != null)
26
- items.push(...rowGroup(target.rowIdx, dims.rowCount));
27
- if (isCell)
28
- items.push({ kind: 'separator' });
29
- if (target.colIdx != null)
30
- items.push(...columnGroup(target.colIdx, dims.colCount, alignments));
31
- return items;
31
+ const rows = rowGroup(cell.rowIdx, dims.rowCount);
32
+ const cols = columnGroup(cell.colIdx, dims.colCount, alignments);
33
+ const isDelete = (i) => i.kind === 'action' && (i.action === 'deleteRow' || i.action === 'deleteColumn');
34
+ // A cell knows both axes, so each axis's inserts and moves fold behind one flyout and only
35
+ // the two deletes and the alignment stay in the list.
36
+ return [
37
+ ...clipboardGroup(clipboard.hasSelection || clipboard.hasRect === true),
38
+ { kind: 'separator' },
39
+ { kind: 'group', id: 'row', label: 'Row', items: rows.filter((i) => !isDelete(i)) },
40
+ {
41
+ kind: 'group',
42
+ id: 'column',
43
+ label: 'Column',
44
+ items: cols.filter((i) => !isDelete(i) && i.kind !== 'alignment')
45
+ },
46
+ { kind: 'separator' },
47
+ ...rows.filter(isDelete),
48
+ ...cols.filter(isDelete),
49
+ ...cols.filter((i) => i.kind === 'alignment')
50
+ ];
32
51
  }
33
52
  // Paste always applies: clipboard contents aren't readable synchronously to gate it.
34
53
  function clipboardGroup(hasContent) {