@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.
- package/README.md +12 -22
- package/THIRD-PARTY-NOTICES.md +25 -0
- package/dist/a11y-strings.d.ts +18 -0
- package/dist/a11y-strings.js +18 -0
- package/dist/action-contracts.d.ts +7 -1
- package/dist/ambient/ambient-dom.js +5 -1
- package/dist/block-component.d.ts +14 -0
- package/dist/components/BlockDragHandle.svelte +35 -24
- package/dist/components/BlockHost.svelte +16 -9
- package/dist/components/Editor.svelte +383 -199
- package/dist/components/Editor.svelte.d.ts +1 -1
- package/dist/components/SelectionOverlay.svelte +17 -3
- package/dist/components/TailInsert.svelte +107 -0
- package/dist/components/TailInsert.svelte.d.ts +17 -0
- package/dist/components/block-content-selector.d.ts +6 -2
- package/dist/components/block-content-selector.js +6 -2
- package/dist/components/blocks/ThematicBreakBlock.svelte +19 -6
- package/dist/components/blocks/ThematicBreakBlock.svelte.d.ts +1 -0
- package/dist/components/blocks/code/CodeBlock.svelte +211 -30
- package/dist/components/blocks/code/CodeBlockRail.svelte +686 -0
- package/dist/components/blocks/code/CodeBlockRail.svelte.d.ts +26 -0
- package/dist/components/blocks/code/code-bootstrap.js +4 -0
- package/dist/components/blocks/code/code-context-actions.d.ts +1 -0
- package/dist/components/blocks/code/code-context-actions.js +24 -0
- package/dist/components/blocks/code/code-fence-exit.d.ts +15 -0
- package/dist/components/blocks/code/code-fence-exit.js +26 -0
- package/dist/components/blocks/code/code-languages.d.ts +6 -0
- package/dist/components/blocks/code/code-languages.js +11 -0
- package/dist/components/blocks/code/code-renderer.js +11 -0
- package/dist/components/blocks/directive/DirectiveContainerBlock.svelte +1 -1
- package/dist/components/blocks/editable-leaf.d.ts +37 -6
- package/dist/components/blocks/editable-leaf.js +243 -29
- package/dist/components/blocks/editable-surface.d.ts +9 -0
- package/dist/components/blocks/editable-surface.js +22 -3
- package/dist/components/blocks/list/ListItemBlock.svelte +3 -3
- package/dist/components/blocks/list/task-checkbox.d.ts +2 -0
- package/dist/components/blocks/list/task-checkbox.js +11 -2
- package/dist/components/blocks/surface-wiring.svelte.d.ts +4 -0
- package/dist/components/blocks/surface-wiring.svelte.js +8 -1
- package/dist/components/blocks/table/TableActionMenu.svelte +210 -86
- package/dist/components/blocks/table/TableActionMenu.svelte.d.ts +5 -0
- package/dist/components/blocks/table/TableBlock.svelte +205 -186
- package/dist/components/blocks/table/TableBlock.svelte.d.ts +1 -0
- package/dist/components/blocks/table/TableCellBlock.svelte +87 -22
- package/dist/components/blocks/table/TableRowBlock.svelte +4 -18
- package/dist/components/blocks/table/TableRowBlock.svelte.d.ts +0 -5
- package/dist/components/blocks/table/cell-clipboard.d.ts +10 -0
- package/dist/components/blocks/table/cell-clipboard.js +34 -1
- package/dist/components/blocks/table/cell-keydown-plan.d.ts +1 -1
- package/dist/components/blocks/table/cell-keydown-plan.js +3 -1
- package/dist/components/blocks/table/table-cell-paste.js +2 -1
- package/dist/components/blocks/table/table-menu-model.d.ts +35 -8
- package/dist/components/blocks/table/table-menu-model.js +36 -17
- package/dist/components/blocks/text/TextEditableBlock.svelte +58 -11
- package/dist/components/blocks/text/delimiter-autopair.d.ts +71 -0
- package/dist/components/blocks/text/delimiter-autopair.js +216 -0
- package/dist/components/blocks/text/edge-policy-dispatch.d.ts +4 -0
- package/dist/components/blocks/text/edge-policy-dispatch.js +56 -2
- package/dist/components/blocks/text/live-selection-edit.js +27 -0
- package/dist/components/blocks/text/text-keydown.d.ts +7 -1
- package/dist/components/blocks/text/text-keydown.js +11 -1
- package/dist/components/blocks/text/text-render.d.ts +1 -1
- package/dist/components/blocks/text/text-render.js +3 -1
- package/dist/components/blocks/text/widget-interaction.d.ts +3 -0
- package/dist/components/blocks/text/widget-interaction.js +144 -22
- package/dist/components/drag-handle.d.ts +35 -0
- package/dist/components/drag-handle.js +126 -0
- package/dist/components/editor-root-focus.d.ts +19 -0
- package/dist/components/editor-root-focus.js +67 -0
- package/dist/components/editor-root-geometry.d.ts +39 -0
- package/dist/components/editor-root-geometry.js +91 -0
- package/dist/components/editor-root-keydown.d.ts +1 -1
- package/dist/components/editor-root-keydown.js +12 -3
- package/dist/components/editor-root-listeners.d.ts +10 -6
- package/dist/components/editor-root-listeners.js +23 -22
- package/dist/components/editor-root-mode-flip.d.ts +36 -0
- package/dist/components/editor-root-mode-flip.js +92 -0
- package/dist/components/image/ImageOverlayHost.svelte +14 -6
- package/dist/components/image/ImageProperties.svelte +512 -70
- package/dist/components/image/ImageProperties.svelte.d.ts +6 -1
- package/dist/components/image/ImageResizeHandles.svelte +59 -34
- package/dist/components/image/image-crop.d.ts +39 -0
- package/dist/components/image/image-crop.js +74 -0
- package/dist/components/image/image-edit-commit.d.ts +1 -0
- package/dist/components/image/image-edit-commit.js +21 -5
- package/dist/components/image/image-source-bytes.js +10 -3
- package/dist/components/image/image-widget-editing.js +1 -0
- package/dist/components/image/widget-dom.js +5 -1
- package/dist/components/link-card/link-card-commit.js +1 -1
- package/dist/components/lrd-map-gate.js +1 -1
- package/dist/components/menu/BlockMenu.svelte +315 -0
- package/dist/components/menu/BlockMenu.svelte.d.ts +34 -0
- package/dist/components/menu/MenuIcon.svelte +153 -0
- package/dist/components/menu/MenuIcon.svelte.d.ts +51 -0
- package/dist/components/menu/clipboard-actions.d.ts +12 -0
- package/dist/components/menu/clipboard-actions.js +42 -0
- package/dist/components/menu/default-context-actions.d.ts +15 -0
- package/dist/components/menu/default-context-actions.js +76 -0
- package/dist/components/menu/flyout-placement.d.ts +6 -0
- package/dist/components/menu/flyout-placement.js +25 -0
- package/dist/core/inline/format-toggle.d.ts +12 -4
- package/dist/core/inline/format-toggle.js +94 -40
- package/dist/core/inline/image-dimensions.d.ts +3 -0
- package/dist/core/inline/image-dimensions.js +46 -10
- package/dist/core/inline/inline-widgets.d.ts +11 -0
- package/dist/core/inline/scan/brackets.js +1 -0
- package/dist/core/inline/scan/plugin-syntax.d.ts +8 -0
- package/dist/core/inline/scan/plugin-syntax.js +15 -1
- package/dist/core/inline-render.d.ts +6 -0
- package/dist/core/inline-render.js +32 -0
- package/dist/core/nodes.d.ts +14 -0
- package/dist/cursor/edge-affinity.js +2 -1
- package/dist/cursor/overlay-remeasure.js +8 -0
- package/dist/cursor/reveal-source.js +7 -2
- package/dist/cursor/widget-offset.d.ts +6 -0
- package/dist/cursor/widget-offset.js +61 -4
- package/dist/debug/interaction-trace.d.ts +4 -0
- package/dist/debug/interaction-trace.js +15 -0
- package/dist/decorations/decoration-state.svelte.js +1 -1
- package/dist/editor-actions/ancestry-folds.d.ts +2 -2
- package/dist/editor-actions/ancestry-folds.js +1 -1
- package/dist/editor-actions/block-edit-scope.js +1 -1
- package/dist/editor-actions/commit/text-batch.d.ts +3 -2
- package/dist/editor-actions/commit/text-batch.js +1 -1
- package/dist/editor-actions/commit/undo-controller.js +4 -2
- package/dist/editor-actions/container-edit.js +2 -1
- package/dist/editor-actions/enter-completion.d.ts +2 -0
- package/dist/editor-actions/enter-completion.js +23 -2
- package/dist/editor-actions/inline-range-commit.js +1 -1
- package/dist/editor-actions/reorder-action.js +19 -10
- package/dist/editor-actions/reorder-drag.js +29 -1
- package/dist/editor-actions/replacement-focus.d.ts +1 -1
- package/dist/editor-actions/replacement-focus.js +1 -1
- package/dist/editor-actions/search-replace.js +1 -1
- package/dist/editor-actions/table-context.d.ts +4 -1
- package/dist/editor-actions/table-context.js +57 -1
- package/dist/editor-events.d.ts +3 -0
- package/dist/editor-keys.d.ts +33 -0
- package/dist/editor-props.d.ts +24 -13
- package/dist/index.d.ts +1 -1
- package/dist/plugin.d.ts +6 -0
- package/dist/plugin.js +11 -0
- package/dist/plugins/latex/BlockMath.svelte +264 -29
- package/dist/plugins/latex/BlockMath.svelte.d.ts +2 -0
- package/dist/plugins/latex/index.d.ts +2 -1
- package/dist/plugins/latex/latex-kind.js +36 -3
- package/dist/plugins/latex/math-completion.js +4 -1
- package/dist/plugins/latex/math-layout.d.ts +15 -0
- package/dist/plugins/latex/math-layout.js +13 -0
- package/dist/plugins/latex/math-source.d.ts +19 -0
- package/dist/plugins/latex/math-source.js +97 -0
- package/dist/plugins/latex/register.d.ts +11 -2
- package/dist/plugins/latex/register.js +3 -1
- package/dist/plugins/latex/renderer.d.ts +3 -3
- package/dist/plugins/latex/renderer.js +13 -6
- package/dist/plugins/parrot/ParrotBlock.svelte +1 -1
- package/dist/reactivity/list-windowing.svelte.d.ts +8 -8
- package/dist/reactivity/list-windowing.svelte.js +49 -24
- package/dist/schema/block-completions.d.ts +8 -0
- package/dist/schema/block-completions.js +11 -0
- package/dist/schema/context-actions.d.ts +31 -0
- package/dist/schema/context-actions.js +23 -0
- package/dist/schema/fenced-code-raw.js +31 -1
- package/dist/schema/operations.d.ts +8 -1
- package/dist/schema/reserved-chords.js +25 -4
- package/dist/schema/table-cell-raw.d.ts +1 -1
- package/dist/schema/table-cell-raw.js +1 -1
- package/dist/selection/block-hit-test.js +3 -2
- package/dist/selection/char-endpoint-snap.js +1 -1
- package/dist/selection/clipboard-text.js +6 -1
- package/dist/selection/covered-block.d.ts +10 -0
- package/dist/selection/covered-block.js +24 -0
- package/dist/selection/cross-block/dispatch.d.ts +3 -0
- package/dist/selection/cross-block/dispatch.js +13 -1
- package/dist/selection/cross-block/format-range.d.ts +1 -1
- package/dist/selection/cross-block/format-range.js +4 -15
- package/dist/selection/cross-block/format-toggle.js +1 -1
- package/dist/selection/cross-block/keydown.js +1 -1
- package/dist/selection/cross-block/ops.js +1 -1
- package/dist/selection/cross-block/paste.js +32 -30
- package/dist/selection/cross-block/type-replace.d.ts +3 -2
- package/dist/selection/cross-block/type-replace.js +62 -13
- package/dist/selection/dead-space-caret.d.ts +10 -0
- package/dist/selection/dead-space-caret.js +47 -1
- package/dist/selection/double-click-trim.d.ts +17 -0
- package/dist/selection/double-click-trim.js +57 -0
- package/dist/selection/drag-pointer.d.ts +7 -2
- package/dist/selection/drag-pointer.js +61 -2
- package/dist/selection/gap-caret.js +1 -1
- package/dist/selection/keyboard-extend.js +1 -1
- package/dist/selection/path-lookup.js +1 -1
- package/dist/selection/range-delete-ceremony.js +4 -2
- package/dist/selection/range-delete-chrome.js +2 -1
- package/dist/selection/range-delete-table-coverage.js +2 -1
- package/dist/selection/range-delete-table.js +3 -2
- package/dist/selection/range-delete.js +30 -2
- package/dist/selection/selection-restore.js +1 -1
- package/dist/selection/selection-state.svelte.d.ts +6 -0
- package/dist/selection/selection-state.svelte.js +36 -1
- package/dist/selection/table-endpoint-snap.js +1 -1
- package/dist/selection/table-rect-extend.js +1 -1
- package/dist/styles/editor-theme.css +57 -28
- package/dist/styles/editor.css +217 -18
- package/dist/testing/container-conformance.js +2 -2
- package/dist/testing/inline-conformance.js +2 -1
- package/dist/tree-operations/blockquote.js +1 -1
- package/dist/tree-operations/chain-rebuild.d.ts +63 -0
- package/dist/tree-operations/chain-rebuild.js +142 -0
- package/dist/tree-operations/children.d.ts +1 -1
- package/dist/tree-operations/children.js +1 -1
- package/dist/tree-operations/cleanup.js +1 -1
- package/dist/tree-operations/content-write.d.ts +50 -0
- package/dist/tree-operations/content-write.js +263 -0
- package/dist/tree-operations/index.d.ts +8 -3
- package/dist/tree-operations/index.js +6 -2
- package/dist/tree-operations/list/exit-replacement.js +1 -1
- package/dist/tree-operations/list/unwrap-merge.js +3 -3
- package/dist/tree-operations/node-ops.d.ts +17 -234
- package/dist/tree-operations/node-ops.js +47 -1113
- package/dist/tree-operations/node-primitives.d.ts +75 -0
- package/dist/tree-operations/node-primitives.js +117 -0
- package/dist/tree-operations/paste/apply.js +1 -1
- package/dist/tree-operations/paste/body-write.d.ts +1 -1
- package/dist/tree-operations/paste/body-write.js +2 -2
- package/dist/tree-operations/paste/container-match.js +4 -2
- package/dist/tree-operations/paste/dispatch.js +2 -1
- package/dist/tree-operations/paste/find-enclosing-list.js +1 -1
- package/dist/tree-operations/paste/focus-target.d.ts +1 -1
- package/dist/tree-operations/paste/list-absorb.js +1 -1
- package/dist/tree-operations/paste/list-break-out.js +1 -1
- package/dist/tree-operations/paste/parent-scope.js +1 -1
- package/dist/tree-operations/paste/paste-replacement.js +1 -1
- package/dist/tree-operations/paste/replace-block-at-parent.js +1 -1
- package/dist/tree-operations/path-mutate.d.ts +1 -1
- package/dist/tree-operations/path-mutate.js +2 -1
- package/dist/tree-operations/reorder-unit.js +1 -1
- package/dist/tree-operations/reorder.d.ts +5 -2
- package/dist/tree-operations/reorder.js +55 -2
- package/dist/tree-operations/settle.d.ts +105 -0
- package/dist/tree-operations/settle.js +660 -0
- package/dist/tree-operations/table-grid-clipboard.d.ts +21 -0
- package/dist/tree-operations/table-grid-clipboard.js +90 -0
- package/dist/tree-operations/unshare.d.ts +15 -77
- package/dist/tree-operations/unshare.js +15 -166
- package/docs/guide/consumer-guide.md +132 -99
- package/docs/guide/plugin-api.md +31 -3
- package/docs/guide/plugin-guide.md +31 -6
- package/package.json +4 -2
- package/dist/components/blocks/code/CodeLanguageChip.svelte +0 -127
- package/dist/components/blocks/code/CodeLanguageChip.svelte.d.ts +0 -14
- package/dist/components/blocks/table/TableGrip.svelte +0 -91
- package/dist/components/blocks/table/TableGrip.svelte.d.ts +0 -8
- package/dist/components/blocks/table/table-drop-target.d.ts +0 -1
- package/dist/components/blocks/table/table-drop-target.js +0 -16
- package/dist/components/blocks/table/table-reorder-drag.d.ts +0 -78
- 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,
|
|
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
|
-
<
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
}
|