@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
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { keepFlyoutOnScreen } from '../../menu/flyout-placement';
2
3
  import {
3
4
  clampMenuToViewport,
4
5
  type TableMenuItem,
@@ -12,6 +13,27 @@
12
13
  COLUMN_ALIGNMENT,
13
14
  TABLE_ACTIONS
14
15
  } from '../../../a11y-strings';
16
+ import MenuIcon, { type MenuIconName } from '../../menu/MenuIcon.svelte';
17
+ import { tick, untrack } from 'svelte';
18
+
19
+ // The glyph each entry carries, limestone's context-menu convention: an icon slot per row,
20
+ // the destructive rows in the accent.
21
+ const ICONS: Record<TableAxisAction | ClipboardAction, MenuIconName> = {
22
+ insertRowAbove: 'plus',
23
+ insertRowBelow: 'plus',
24
+ insertColumnLeft: 'plus',
25
+ insertColumnRight: 'plus',
26
+ moveRowUp: 'arrow-up',
27
+ moveRowDown: 'arrow-down',
28
+ moveColumnLeft: 'arrow-left',
29
+ moveColumnRight: 'arrow-right',
30
+ deleteRow: 'trash',
31
+ deleteColumn: 'trash',
32
+ cycleAlignment: 'align-left',
33
+ cut: 'scissors',
34
+ copy: 'copy',
35
+ paste: 'clipboard'
36
+ };
15
37
 
16
38
  let {
17
39
  items,
@@ -21,11 +43,14 @@
21
43
  onclipboard,
22
44
  onalign,
23
45
  onclose,
24
- onescape
46
+ onescape,
47
+ anchor
25
48
  }: {
26
49
  items: TableMenuItem[];
27
50
  x: number;
28
51
  y: number;
52
+ /** Where the open point is NOW, re-read on scroll and resize so the menu stays on it. */
53
+ anchor?: () => { x: number; y: number } | null;
29
54
  onaction: (action: TableAxisAction, index: number) => void;
30
55
  onclipboard: (action: ClipboardAction) => void;
31
56
  onalign: (alignment: 'left' | 'center' | 'right') => void;
@@ -34,23 +59,46 @@
34
59
  } = $props();
35
60
 
36
61
  let menuEl: HTMLDivElement | undefined = $state();
62
+ /** The open flyout, if any: hover or ArrowRight on its row opens it, ArrowLeft closes it. */
63
+ let openGroup = $state<'row' | 'column' | null>(null);
37
64
 
38
- // Resolved once the menu's measured size is known; until then the template falls back
39
- // to the raw open coordinate, which the post-mount measure corrects before it paints.
40
- let clamped = $state<{ x: number; y: number } | null>(null);
65
+ // The open point, viewport coordinates, re-read on scroll and resize so the menu stays on
66
+ // what it opened on and leaves the viewport with it never re-clamped into view, which
67
+ // would float it over unrelated content. The clamp runs once, when the size is first known,
68
+ // and its shift rides along as a constant offset.
69
+ // svelte-ignore state_referenced_locally
70
+ let at = $state({ x, y });
71
+ let shift = $state<{ x: number; y: number } | null>(null);
41
72
  $effect(() => {
42
- if (!menuEl) return;
73
+ const follow = () => {
74
+ const next = anchor?.();
75
+ if (next) at = next;
76
+ };
77
+ window.addEventListener('scroll', follow, { capture: true, passive: true });
78
+ window.addEventListener('resize', follow, { passive: true });
79
+ return () => {
80
+ window.removeEventListener('scroll', follow, { capture: true });
81
+ window.removeEventListener('resize', follow);
82
+ };
83
+ });
84
+ $effect(() => {
85
+ if (!menuEl || shift) return;
43
86
  const rect = menuEl.getBoundingClientRect();
44
- clamped = clampMenuToViewport(
87
+ const clamped = clampMenuToViewport(
45
88
  { x, y },
46
89
  { width: rect.width, height: rect.height },
47
90
  { width: window.innerWidth, height: window.innerHeight }
48
91
  );
92
+ shift = { x: clamped.x - x, y: clamped.y - y };
49
93
  });
94
+ const left = $derived(at.x + (shift?.x ?? 0));
95
+ const top = $derived(at.y + (shift?.y ?? 0));
50
96
 
51
97
  // The first enabled item is the keyboard entry point; disabled items are never stops.
98
+ // Untracked: `focusStop` reads the open flyout, and a tracked read here would re-run this
99
+ // mount-time landing every time a flyout opened, pulling focus straight back out of it.
52
100
  $effect(() => {
53
- if (menuEl) focusStop(0);
101
+ if (menuEl) untrack(() => focusStop(0));
54
102
  });
55
103
 
56
104
  $effect(() => {
@@ -90,7 +138,27 @@
90
138
  const stops = focusableStops();
91
139
  if (stops.length === 0) return;
92
140
  const n = stops.length;
93
- stops[((index % n) + n) % n].focus();
141
+ const stop = stops[((index % n) + n) % n];
142
+ stop.focus();
143
+ // Focus that leaves an open flyout (and its own row) closes it, so the list reads as one.
144
+ if (
145
+ openGroup &&
146
+ !stop.closest('.table-action-menu-flyout') &&
147
+ stop.dataset.group !== openGroup
148
+ ) {
149
+ openGroup = null;
150
+ }
151
+ }
152
+
153
+ /** Open a group's flyout and land on its first enabled item. */
154
+ function openFlyout(id: 'row' | 'column'): void {
155
+ openGroup = id;
156
+ void tick().then(() => {
157
+ const first = menuEl?.querySelector<HTMLElement>(
158
+ '.table-action-menu-flyout [role="menuitem"]:not([disabled])'
159
+ );
160
+ first?.focus();
161
+ });
94
162
  }
95
163
 
96
164
  function activeStopIndex(stops: HTMLElement[]): number {
@@ -121,10 +189,30 @@
121
189
  e.preventDefault();
122
190
  focusStop(last);
123
191
  return;
124
- case 'ArrowRight':
125
- case 'ArrowLeft':
192
+ case 'ArrowRight': {
193
+ const group = (document.activeElement as HTMLElement | null)?.dataset.group;
194
+ if (group === 'row' || group === 'column') {
195
+ e.preventDefault();
196
+ openFlyout(group);
197
+ return;
198
+ }
199
+ moveWithinAlignment(e);
200
+ return;
201
+ }
202
+ case 'ArrowLeft': {
203
+ const inFlyout = (document.activeElement as HTMLElement | null)?.closest(
204
+ '.table-action-menu-flyout'
205
+ );
206
+ if (inFlyout && openGroup) {
207
+ e.preventDefault();
208
+ const row = menuEl?.querySelector<HTMLElement>(`[data-group="${openGroup}"]`);
209
+ openGroup = null;
210
+ row?.focus();
211
+ return;
212
+ }
126
213
  moveWithinAlignment(e);
127
214
  return;
215
+ }
128
216
  }
129
217
  }
130
218
 
@@ -140,44 +228,78 @@
140
228
  segments[(((i + delta) % n) + n) % n].focus();
141
229
  }
142
230
 
231
+ // The actions that live inside a flyout: hovering one keeps its flyout open; hovering a
232
+ // top-level row closes whichever flyout is open.
233
+ const GROUPED = new Set<TableAxisAction>([
234
+ 'insertRowAbove',
235
+ 'insertRowBelow',
236
+ 'moveRowUp',
237
+ 'moveRowDown',
238
+ 'insertColumnLeft',
239
+ 'insertColumnRight',
240
+ 'moveColumnLeft',
241
+ 'moveColumnRight'
242
+ ]);
243
+ const isGrouped = (action: TableAxisAction): boolean => GROUPED.has(action);
244
+
143
245
  // 'none' renders identically to 'left', so the left segment reads active for both.
144
- // Visible text stays L/C/R; the accessible name carries the full word.
246
+ // The glyph is the visible label; the accessible name carries the full word.
145
247
  const alignmentSegments = [
146
- { value: 'left', label: 'L', name: ALIGN_LEFT },
147
- { value: 'center', label: 'C', name: ALIGN_CENTER },
148
- { value: 'right', label: 'R', name: ALIGN_RIGHT }
248
+ { value: 'left', icon: 'align-left', name: ALIGN_LEFT },
249
+ { value: 'center', icon: 'align-center', name: ALIGN_CENTER },
250
+ { value: 'right', icon: 'align-right', name: ALIGN_RIGHT }
149
251
  ] as const;
150
252
  </script>
151
253
 
152
254
  <div
153
255
  bind:this={menuEl}
154
- class="table-action-menu"
256
+ class="md-menu table-action-menu"
155
257
  role="menu"
156
258
  aria-label={TABLE_ACTIONS}
157
259
  tabindex="-1"
158
- style:left="{clamped ? clamped.x : x}px"
159
- style:top="{clamped ? clamped.y : y}px"
260
+ style:left="{left}px"
261
+ style:top="{top}px"
160
262
  onkeydown={onMenuKeyDown}
161
263
  >
162
264
  {#each items as item, i (i)}
163
265
  {#if item.kind === 'action' || item.kind === 'clipboard'}
164
- {@const activate =
165
- item.kind === 'action'
166
- ? () => onaction(item.action, item.index)
167
- : () => onclipboard(item.action)}
168
- <button
169
- type="button"
170
- role="menuitem"
171
- tabindex="-1"
172
- class="table-action-menu-item"
173
- disabled={!item.enabled}
174
- aria-disabled={!item.enabled}
175
- onclick={activate}
176
- >
177
- {item.label}
178
- </button>
266
+ {@render actionRow(item)}
267
+ {:else if item.kind === 'group'}
268
+ <div class="table-action-menu-group" role="presentation">
269
+ <button
270
+ type="button"
271
+ role="menuitem"
272
+ tabindex="-1"
273
+ class="md-menu-item table-action-menu-item"
274
+ data-group={item.id}
275
+ aria-haspopup="menu"
276
+ aria-expanded={openGroup === item.id}
277
+ onpointerenter={() => (openGroup = item.id)}
278
+ onclick={() => openFlyout(item.id)}
279
+ >
280
+ <span class="md-menu-icon"
281
+ ><MenuIcon name={item.id === 'row' ? 'rows-2' : 'columns-2'} /></span
282
+ >
283
+ <span class="table-action-menu-label">{item.label}</span>
284
+ <span class="md-menu-icon"><MenuIcon name="chevron-right" size={13} /></span>
285
+ </button>
286
+ {#if openGroup === item.id}
287
+ <div
288
+ class="md-menu table-action-menu-flyout"
289
+ role="menu"
290
+ aria-label={item.label}
291
+ {@attach keepFlyoutOnScreen}
292
+ >
293
+ {#each item.items as sub, j (j)}
294
+ {#if sub.kind === 'action' || sub.kind === 'clipboard'}
295
+ {@render actionRow(sub)}
296
+ {/if}
297
+ {/each}
298
+ </div>
299
+ {/if}
300
+ </div>
179
301
  {:else if item.kind === 'separator'}
180
- <div class="table-action-menu-separator" role="separator"></div>
302
+ <div class="md-menu-divider table-action-menu-separator" role="separator"></div>
181
303
  {:else}
182
304
  <div class="table-action-menu-alignment" role="group" aria-label={COLUMN_ALIGNMENT}>
183
305
  {#each alignmentSegments as seg (seg.value)}
@@ -190,7 +312,8 @@
190
312
  tabindex="-1"
191
313
  aria-label={seg.name}
192
314
  aria-pressed={active}
193
- onclick={() => onalign(seg.value)}>{seg.label}</button
315
+ title={seg.name}
316
+ onclick={() => onalign(seg.value)}><MenuIcon name={seg.icon} /></button
194
317
  >
195
318
  {/each}
196
319
  </div>
@@ -198,70 +321,71 @@
198
321
  {/each}
199
322
  </div>
200
323
 
324
+ {#snippet actionRow(item: Extract<TableMenuItem, { kind: 'action' | 'clipboard' }>)}
325
+ {@const activate =
326
+ item.kind === 'action'
327
+ ? () => onaction(item.action, item.index)
328
+ : () => onclipboard(item.action)}
329
+ <button
330
+ type="button"
331
+ role="menuitem"
332
+ tabindex="-1"
333
+ class="md-menu-item table-action-menu-item"
334
+ disabled={!item.enabled}
335
+ aria-disabled={!item.enabled}
336
+ onpointerenter={() => {
337
+ if (!(item.kind === 'action' && isGrouped(item.action))) openGroup = null;
338
+ }}
339
+ onclick={activate}
340
+ >
341
+ <span class="md-menu-icon"><MenuIcon name={ICONS[item.action]} /></span>
342
+ <span class="table-action-menu-label">{item.label}</span>
343
+ </button>
344
+ {/snippet}
345
+
201
346
  <style>
202
- .table-action-menu {
203
- position: fixed;
204
- z-index: 30;
205
- min-width: 11rem;
206
- padding: 0.25rem;
347
+ /* The surface, rows, icons and dividers are the shared `.md-menu` family (editor.css); only
348
+ the alignment trio is this menu's own — three glyph buttons on one row, the active one
349
+ lifted like a hover. */
350
+ .table-action-menu-alignment {
207
351
  display: flex;
208
- flex-direction: column;
209
- background: var(--color-surface, #fff);
210
- border: 1px solid var(--color-ui-muted, #a4a4a4);
211
- border-radius: 6px;
212
- box-shadow: 0 4px 16px rgba(0, 0, 0, 0.14);
213
- font-size: 0.9em;
214
- user-select: none;
215
- }
216
-
217
- .table-action-menu-item {
218
- display: block;
219
- width: 100%;
220
- padding: 0.3rem 0.55rem;
221
- border: 0;
222
- border-radius: 4px;
223
- background: transparent;
224
- text-align: left;
225
- font: inherit;
226
- color: inherit;
227
- cursor: pointer;
352
+ gap: 2px;
353
+ padding: 4px 6px;
228
354
  }
229
- .table-action-menu-item:hover:not(:disabled) {
230
- background: var(--color-ui-faint, rgba(255, 255, 255, 0.07));
231
- }
232
- .table-action-menu-item:disabled {
233
- opacity: 0.4;
234
- cursor: default;
355
+ .table-action-menu-label {
356
+ flex: 1;
235
357
  }
236
-
237
- .table-action-menu-separator {
238
- height: 1px;
239
- margin: 0.25rem 0.3rem;
240
- background: var(--color-ui-muted, #a4a4a4);
241
- opacity: 0.5;
358
+ .table-action-menu-group {
359
+ position: relative;
242
360
  }
243
-
244
- .table-action-menu-alignment {
245
- display: flex;
246
- gap: 0.2rem;
247
- padding: 0.3rem 0.55rem;
361
+ /* limestone's submenu: a second surface hung off the row's right edge. */
362
+ .table-action-menu-flyout {
363
+ position: absolute;
364
+ left: 100%;
365
+ top: -4px;
366
+ margin-left: 4px;
367
+ white-space: nowrap;
248
368
  }
249
369
  .alignment-segment {
250
370
  flex: 1;
251
- padding: 0.1rem 0;
252
- text-align: center;
253
- border: 1px solid var(--color-ui-muted, #a4a4a4);
254
- border-radius: 4px;
371
+ display: inline-flex;
372
+ align-items: center;
373
+ justify-content: center;
374
+ height: 26px;
375
+ padding: 0;
376
+ border: 0;
377
+ border-radius: 5px;
255
378
  background: transparent;
256
- font: inherit;
257
- color: var(--color-ui-muted, #a4a4a4);
379
+ color: var(--color-ui-muted, #8f8f89);
258
380
  cursor: pointer;
259
381
  }
260
- .alignment-segment:hover {
261
- background: var(--color-ui-faint, rgba(255, 255, 255, 0.07));
382
+ .alignment-segment:hover,
383
+ .alignment-segment:focus-visible,
384
+ .alignment-segment.active {
385
+ background: var(--menu-item-hover, rgba(255, 255, 255, 0.07));
386
+ outline: none;
262
387
  }
263
388
  .alignment-segment.active {
264
- color: var(--color-accent, #567b67);
265
- border-color: var(--color-accent, #567b67);
389
+ color: var(--color-text-primary, #e8e8e5);
266
390
  }
267
391
  </style>
@@ -4,6 +4,11 @@ type $$ComponentProps = {
4
4
  items: TableMenuItem[];
5
5
  x: number;
6
6
  y: number;
7
+ /** Where the open point is NOW, re-read on scroll and resize so the menu stays on it. */
8
+ anchor?: () => {
9
+ x: number;
10
+ y: number;
11
+ } | null;
7
12
  onaction: (action: TableAxisAction, index: number) => void;
8
13
  onclipboard: (action: ClipboardAction) => void;
9
14
  onalign: (alignment: 'left' | 'center' | 'right') => void;