@voithos-labs/aragonite 0.10.2 → 0.10.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -21
- package/THIRD-PARTY-NOTICES.md +25 -0
- package/dist/a11y-strings.d.ts +19 -0
- package/dist/a11y-strings.js +19 -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 +37 -24
- package/dist/components/BlockHost.svelte +18 -11
- package/dist/components/Editor.svelte +434 -198
- package/dist/components/Editor.svelte.d.ts +1 -1
- package/dist/components/SelectionOverlay.svelte +25 -17
- package/dist/components/SelectionOverlay.svelte.d.ts +1 -1
- 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 +704 -0
- package/dist/components/blocks/code/CodeBlockRail.svelte.d.ts +26 -0
- package/dist/components/blocks/code/code-bootstrap.js +16 -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 +22 -3
- 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 +248 -30
- package/dist/components/blocks/editable-surface.d.ts +9 -0
- package/dist/components/blocks/editable-surface.js +22 -3
- package/dist/components/blocks/list/ListBlock.svelte +1 -0
- package/dist/components/blocks/list/ListItemBlock.svelte +14 -5
- package/dist/components/blocks/list/ListItemBlock.svelte.d.ts +2 -0
- 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/click-snap-guard.d.ts +3 -0
- package/dist/components/blocks/text/click-snap-guard.js +8 -0
- 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 +89 -20
- package/dist/components/blocks/text/live-selection-edit.d.ts +5 -5
- package/dist/components/blocks/text/live-selection-edit.js +37 -7
- package/dist/components/blocks/text/text-clipboard.js +2 -2
- 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 +181 -37
- package/dist/components/drag-handle.d.ts +41 -0
- package/dist/components/drag-handle.js +134 -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 +3 -6
- package/dist/components/editor-root-listeners.js +3 -24
- 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 +67 -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/SelectionToolbar.svelte +385 -0
- package/dist/components/menu/SelectionToolbar.svelte.d.ts +16 -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 +19 -0
- package/dist/core/inline/inline-widgets.js +5 -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/transparency.js +3 -3
- 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/height-oracle.d.ts +2 -3
- package/dist/cursor/height-oracle.js +0 -1
- package/dist/cursor/overlay-remeasure.js +8 -0
- package/dist/cursor/reveal-source.js +7 -2
- package/dist/cursor/scroll-hold.d.ts +10 -0
- package/dist/cursor/scroll-hold.js +22 -0
- package/dist/cursor/scrollport.d.ts +9 -0
- package/dist/cursor/scrollport.js +30 -1
- package/dist/cursor/visual-lines.d.ts +5 -4
- package/dist/cursor/visual-lines.js +56 -11
- package/dist/cursor/widget-edge-snap.d.ts +28 -0
- package/dist/cursor/widget-edge-snap.js +38 -0
- package/dist/cursor/widget-offset.d.ts +9 -0
- package/dist/cursor/widget-offset.js +71 -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/decorations/reserved-attrs.js +1 -0
- 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/focus/focus-dispatch.js +7 -4
- package/dist/editor-actions/focus/focus-landing.d.ts +10 -1
- package/dist/editor-actions/focus/focus-landing.js +20 -6
- package/dist/editor-actions/inline-range-commit.js +1 -1
- package/dist/editor-actions/plugin/container.d.ts +7 -0
- package/dist/editor-actions/plugin/container.js +3 -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 -12
- package/dist/index.d.ts +1 -1
- package/dist/plugin.d.ts +7 -0
- package/dist/plugin.js +16 -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 +60 -10
- 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 +103 -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/mermaid/MermaidBlock.svelte +19 -9
- package/dist/plugins/parrot/ParrotBlock.svelte +3 -1
- package/dist/reactivity/list-windowing.svelte.d.ts +8 -8
- package/dist/reactivity/list-windowing.svelte.js +75 -30
- package/dist/schema/block-completions.d.ts +8 -0
- package/dist/schema/block-completions.js +11 -0
- package/dist/schema/commands.d.ts +9 -5
- package/dist/schema/commands.js +12 -7
- 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 +37 -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 +3 -29
- package/dist/selection/cross-block/ops.js +1 -1
- package/dist/selection/cross-block/paste.js +31 -35
- package/dist/selection/cross-block/type-replace.d.ts +3 -2
- package/dist/selection/cross-block/type-replace.js +52 -13
- package/dist/selection/dead-space-caret.d.ts +13 -0
- package/dist/selection/dead-space-caret.js +57 -1
- package/dist/selection/drag-pointer.d.ts +26 -2
- package/dist/selection/drag-pointer.js +103 -2
- package/dist/selection/gap-caret.js +1 -1
- package/dist/selection/keyboard-extend.d.ts +3 -2
- package/dist/selection/keyboard-extend.js +4 -3
- package/dist/selection/multi-click.d.ts +42 -0
- package/dist/selection/multi-click.js +203 -0
- package/dist/selection/native-bridge.d.ts +3 -0
- package/dist/selection/native-bridge.js +31 -2
- package/dist/selection/path-lookup.d.ts +10 -2
- package/dist/selection/path-lookup.js +11 -4
- package/dist/selection/pointer-gesture.d.ts +9 -0
- package/dist/selection/pointer-gesture.js +11 -0
- package/dist/selection/primitives.d.ts +7 -0
- package/dist/selection/primitives.js +19 -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.d.ts +4 -0
- package/dist/selection/range-delete.js +31 -3
- package/dist/selection/selection-drop.d.ts +34 -0
- package/dist/selection/selection-drop.js +253 -0
- 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 +75 -31
- package/dist/styles/editor.css +268 -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.d.ts +3 -1
- package/dist/tree-operations/paste/replace-block-at-parent.js +5 -2
- package/dist/tree-operations/paste/replacement-parse.d.ts +17 -0
- package/dist/tree-operations/paste/replacement-parse.js +22 -0
- 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 +136 -104
- package/docs/guide/plugin-api.md +73 -29
- package/docs/guide/plugin-guide.md +55 -23
- package/package.json +9 -7
- 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
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The editable-leaf seam a plugin block component builds on:
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* The editable-leaf seam a plugin block component builds on: native caret/IME/undo/selection
|
|
3
|
+
* parity in one factory, so a plugin never touches an editor context key. `plain` commits per
|
|
4
|
+
* keystroke; `render-primary` reveals its source and commits on blur. A painted source
|
|
5
|
+
* (`renderSource`) edits only its own DOM: every byte still enters the CST through
|
|
6
|
+
* `commitReveal`'s one `updateBlockContent`. Call synchronously during init. Contract:
|
|
7
|
+
* plugin-guide § The editable leaf.
|
|
7
8
|
*/
|
|
8
9
|
import { getContext } from 'svelte';
|
|
9
10
|
import { createAttachmentKey } from 'svelte/attachments';
|
|
10
11
|
import { EDITOR_POLICIES_KEY, EDITOR_SERVICES_KEY } from '../../editor-keys';
|
|
11
12
|
import { asDomTextOffset } from '../../cursor/coordinate-spaces';
|
|
12
|
-
import { setCursorOffset, getCursorOffset, getSelectionOffsets } from '../../cursor/content-offsets';
|
|
13
|
+
import { setCursorOffset, getCursorOffset, getSelectionOffsets, getRangeOffsets } from '../../cursor/content-offsets';
|
|
13
14
|
import { handleSharedKeydown } from '../../selection/shared-keydown';
|
|
14
|
-
import { createEditableSurface, createClipboardHandlers, consumePendingRestore } from './editable-surface';
|
|
15
|
+
import { createEditableSurface, createClipboardHandlers, consumePendingRestore, withKeydownVerdict } from './editable-surface';
|
|
15
16
|
import { wireSurfaceContexts } from './surface-wiring.svelte';
|
|
16
17
|
import { createContentOffsetBackend, anchorTrailingNewline } from './plain-text-backend';
|
|
18
|
+
import { CONTENT_EMPTY_ATTR, chromeFreeText, clampToLandableRaw, holdsOnlyMarkerChrome } from '../../cursor/widget-offset';
|
|
17
19
|
import { parkFocusOnEditorRoot } from '../../selection/native-bridge';
|
|
20
|
+
import { assertInvariant } from '../../assert';
|
|
21
|
+
import { checkRenderedTextFidelity } from '../../invariants/render-fidelity';
|
|
22
|
+
import { resolveBinding } from '../../schema/commands';
|
|
23
|
+
import { eventToChord } from '../../schema/keybindings';
|
|
18
24
|
import { resetForPointerDown } from '../../selection/cross-block/pointer';
|
|
19
25
|
import { placeCaret } from '../../selection/caret-doors';
|
|
20
26
|
import { createSourceReveal } from '../../cursor/reveal-source';
|
|
@@ -25,6 +31,7 @@ import {} from '../../schema/commands';
|
|
|
25
31
|
import {} from '../../schema/block-commands';
|
|
26
32
|
import { owningPluginEditor } from '../../schema/plugin-install';
|
|
27
33
|
import { reorderRunCommand } from '../../editor-actions/reorder-action';
|
|
34
|
+
import { createTextBatch } from '../../editor-actions/commit/text-batch';
|
|
28
35
|
/**
|
|
29
36
|
* The node → metadata bridge (plus `commandHooks`) a minted block command resolves
|
|
30
37
|
* against on the leaf tier — the container factory's `buildContainerKindTarget` sibling.
|
|
@@ -48,9 +55,9 @@ export function createEditableLeaf(deps) {
|
|
|
48
55
|
// Plain mode's source is always the editable view.
|
|
49
56
|
const isRevealed = mode === 'render-primary' ? deps.isRevealed : () => true;
|
|
50
57
|
const wiring = wireSurfaceContexts();
|
|
51
|
-
const { blockEdit, stickyColumn, edgeAffinity, selection, getDoc, getBlockElByPath, getEditorRoot, pluginEditor, events: editorEvents } = wiring.deps;
|
|
58
|
+
const { blockEdit, focusActions, stickyColumn, edgeAffinity, selection, getDoc, getBlockElByPath, getEditorRoot, pluginEditor, activePlugins, events: editorEvents } = wiring.deps;
|
|
52
59
|
const { reorder } = getContext(EDITOR_SERVICES_KEY);
|
|
53
|
-
const { presentationMode: getPresentationModeCtx, theme: getThemeCtx, onPasteImage } = getContext(EDITOR_POLICIES_KEY);
|
|
60
|
+
const { presentationMode: getPresentationModeCtx, theme: getThemeCtx, keybindingOverrides, onPasteImage } = getContext(EDITOR_POLICIES_KEY);
|
|
54
61
|
const getPresentationMode = () => getPresentationModeCtx?.() ?? 'source';
|
|
55
62
|
const getTheme = () => getThemeCtx?.() ?? 'dark';
|
|
56
63
|
// Resolved by the kind's recorded owner, like the command context's `editor`.
|
|
@@ -123,13 +130,29 @@ export function createEditableLeaf(deps) {
|
|
|
123
130
|
showSource: () => {
|
|
124
131
|
traceRevealOpen('leaf');
|
|
125
132
|
revealedBase = sourceText();
|
|
133
|
+
clearSourceHistory();
|
|
126
134
|
deps.setRevealed?.(true);
|
|
127
135
|
},
|
|
128
136
|
showRendered: () => {
|
|
129
137
|
revealedBase = null;
|
|
138
|
+
clearSourceHistory();
|
|
130
139
|
deps.setRevealed?.(false);
|
|
131
140
|
}
|
|
132
141
|
});
|
|
142
|
+
// Every open goes through here rather than the kernel directly, so a chrome-only source is
|
|
143
|
+
// completed (see `EditableLeafDeps.completeBareSource`) whatever door revealed it.
|
|
144
|
+
async function revealSource(atSourceOffset = 0) {
|
|
145
|
+
await revealKernel.reveal(atSourceOffset);
|
|
146
|
+
const el = deps.getEl();
|
|
147
|
+
if (!el || !deps.completeBareSource || !isRevealed() || isReading())
|
|
148
|
+
return;
|
|
149
|
+
const completed = deps.completeBareSource(el.textContent ?? '');
|
|
150
|
+
if (!completed)
|
|
151
|
+
return;
|
|
152
|
+
paintSource(el, completed.text);
|
|
153
|
+
deps.onSourceEdit?.(completed.text);
|
|
154
|
+
setCursorOffset(el, asDomTextOffset(completed.caret));
|
|
155
|
+
}
|
|
133
156
|
// ── Commit ─────────────────────────────────────────────────────────────────
|
|
134
157
|
// Returns the commit's own promise, so a caller that has to act on the committed bytes
|
|
135
158
|
// (a single-line Enter's split) can wait for the write to land.
|
|
@@ -138,14 +161,29 @@ export function createEditableLeaf(deps) {
|
|
|
138
161
|
// caret follows the edit position.
|
|
139
162
|
return Promise.resolve(blockEdit.updateBlockContent(deps.getIndex(), edited + trailingLineEnding(deps.getNode().raw), preEditOffset, edited.length));
|
|
140
163
|
}
|
|
141
|
-
|
|
164
|
+
// A blur that arrives with a cross-block range live is the release of a drag that began in
|
|
165
|
+
// this source and left it: the fold waits one frame, so the range's rects measured real text
|
|
166
|
+
// under the pointer, then folds unless focus came back — else the source stays open with
|
|
167
|
+
// nothing focused in it and no further blur to close it.
|
|
168
|
+
let foldFrame = 0;
|
|
169
|
+
function foldAfterRange() {
|
|
170
|
+
if (foldFrame)
|
|
171
|
+
return;
|
|
172
|
+
foldFrame = requestAnimationFrame(() => {
|
|
173
|
+
foldFrame = 0;
|
|
174
|
+
const el = deps.getEl();
|
|
175
|
+
if (!isRevealed() || (el && el.contains(document.activeElement)))
|
|
176
|
+
return;
|
|
177
|
+
void commitReveal(true);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
async function commitReveal(force = false) {
|
|
142
181
|
if (mode !== 'render-primary' || !isRevealed())
|
|
143
182
|
return;
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
// source view until the user focuses it and leaves again.
|
|
147
|
-
if (selection.isCrossBlock)
|
|
183
|
+
if (selection.isCrossBlock && !force) {
|
|
184
|
+
foldAfterRange();
|
|
148
185
|
return;
|
|
186
|
+
}
|
|
149
187
|
// Only wired to onFocusOut — the block leaf folds on blur, never Escape-cancel.
|
|
150
188
|
traceRevealFold('blur');
|
|
151
189
|
const edited = deps.getEl()?.textContent ?? sourceText();
|
|
@@ -169,7 +207,7 @@ export function createEditableLeaf(deps) {
|
|
|
169
207
|
// and block-level traversal passes over.
|
|
170
208
|
if (isReading())
|
|
171
209
|
return;
|
|
172
|
-
void
|
|
210
|
+
void revealSource(offset);
|
|
173
211
|
return;
|
|
174
212
|
}
|
|
175
213
|
surface.parkCaret(offset);
|
|
@@ -184,7 +222,7 @@ export function createEditableLeaf(deps) {
|
|
|
184
222
|
return;
|
|
185
223
|
// Through the kernel like every other open, so this entry gets the same trace pair,
|
|
186
224
|
// length assert and reveal base; it seats a caret at 0, which the column re-seats.
|
|
187
|
-
await
|
|
225
|
+
await revealSource();
|
|
188
226
|
}
|
|
189
227
|
if (!deps.getEl())
|
|
190
228
|
return;
|
|
@@ -196,6 +234,30 @@ export function createEditableLeaf(deps) {
|
|
|
196
234
|
}
|
|
197
235
|
const getCommandContext = () => buildLeafCommandContext(deps, blockEdit, pluginEditor);
|
|
198
236
|
// ── View sync ──────────────────────────────────────────────────────────────
|
|
237
|
+
// The one place source bytes become DOM, so G1.28 is asserted here for every painter rather
|
|
238
|
+
// than trusted per plugin. A painter's chrome-only case (a fence with no body line) takes the
|
|
239
|
+
// same stamp a code block does, so its markers paint while focused.
|
|
240
|
+
function paintSource(el, text) {
|
|
241
|
+
if (deps.renderSource) {
|
|
242
|
+
const painted = deps.renderSource(text);
|
|
243
|
+
assertInvariant('rendered-text-fidelity', () => checkRenderedTextFidelity(painted.textContent ?? '', text));
|
|
244
|
+
el.replaceChildren(painted);
|
|
245
|
+
el.toggleAttribute(CONTENT_EMPTY_ATTR, holdsOnlyMarkerChrome(el));
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
el.textContent = text;
|
|
249
|
+
}
|
|
250
|
+
anchorTrailingNewline(el);
|
|
251
|
+
}
|
|
252
|
+
function repaintSource() {
|
|
253
|
+
const el = deps.getEl();
|
|
254
|
+
if (!el || !deps.renderSource || composing)
|
|
255
|
+
return;
|
|
256
|
+
const offset = getCursorOffset(el);
|
|
257
|
+
paintSource(el, el.textContent ?? '');
|
|
258
|
+
if (offset !== null)
|
|
259
|
+
setCursorOffset(el, asDomTextOffset(offset));
|
|
260
|
+
}
|
|
199
261
|
function syncSource() {
|
|
200
262
|
const text = sourceText();
|
|
201
263
|
const el = deps.getEl();
|
|
@@ -204,23 +266,77 @@ export function createEditableLeaf(deps) {
|
|
|
204
266
|
const pending = pendingCursor;
|
|
205
267
|
pendingCursor = null;
|
|
206
268
|
if ((el.textContent ?? '') !== text) {
|
|
207
|
-
el
|
|
208
|
-
|
|
269
|
+
paintSource(el, text);
|
|
270
|
+
// The local entries were taken against bytes an external rewrite just replaced.
|
|
271
|
+
clearSourceHistory();
|
|
209
272
|
// Restore only under a live caret — an external rewrite (undo, structural
|
|
210
273
|
// replace) must not steal focus.
|
|
211
274
|
consumePendingRestore(el, pending, (offset) => setCursorOffset(el, asDomTextOffset(offset)));
|
|
212
275
|
}
|
|
213
276
|
}
|
|
214
|
-
|
|
277
|
+
let sourceUndo = [];
|
|
278
|
+
let sourceRedo = [];
|
|
279
|
+
// The document's own keystroke batch, not a second timer: a burst of typing inside the
|
|
280
|
+
// reveal takes one entry there, so it takes one here.
|
|
281
|
+
let batchBaseText = '';
|
|
282
|
+
const sourceBatch = createTextBatch({
|
|
283
|
+
pushSnapshot: (_leafPath, offset) => {
|
|
284
|
+
sourceUndo.push({ text: batchBaseText, caret: offset });
|
|
285
|
+
sourceRedo = [];
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
function clearSourceHistory() {
|
|
289
|
+
sourceBatch.interrupt();
|
|
290
|
+
sourceUndo = [];
|
|
291
|
+
sourceRedo = [];
|
|
292
|
+
}
|
|
293
|
+
function restoreSourceEntry(el, from, to) {
|
|
294
|
+
const entry = from.pop();
|
|
295
|
+
if (!entry)
|
|
296
|
+
return;
|
|
297
|
+
// The restored text is what the next keystroke must snapshot, so it opens its own batch.
|
|
298
|
+
sourceBatch.interrupt();
|
|
299
|
+
to.push({ text: el.textContent ?? '', caret: getCursorOffset(el) ?? 0 });
|
|
300
|
+
paintSource(el, entry.text);
|
|
301
|
+
setCursorOffset(el, asDomTextOffset(entry.caret));
|
|
302
|
+
deps.onSourceEdit?.(entry.text);
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* The one entry every reveal edit crosses, so the batching rule lives here rather than at
|
|
306
|
+
* each gesture: one character replaced by at most one non-newline character is the shape a
|
|
307
|
+
* keystroke has, and nothing else coalesces. Enter, a paste, a cut and a selection replace
|
|
308
|
+
* each take an entry of their own and end the burst before them.
|
|
309
|
+
*/
|
|
310
|
+
function recordSourceEdit(text, caret, keystroke) {
|
|
311
|
+
if (!keystroke) {
|
|
312
|
+
sourceBatch.interrupt();
|
|
313
|
+
sourceUndo.push({ text, caret });
|
|
314
|
+
sourceRedo = [];
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
batchBaseText = text;
|
|
318
|
+
sourceBatch.keystroke(deps.getPath(), caret);
|
|
319
|
+
}
|
|
215
320
|
// Splice `insert` over the source text node's [start, end) and reseat the caret. A
|
|
216
321
|
// DOM-text mutation keeps the offset walk exact where a native Enter/cut/paste would
|
|
217
322
|
// inject <div>/<br> that vanish from textContent.
|
|
218
323
|
function spliceSourceText(el, start, end, insert) {
|
|
219
324
|
const text = el.textContent ?? '';
|
|
220
|
-
|
|
221
|
-
|
|
325
|
+
const keystroke = end - start <= 1 && insert.length <= 1 && insert !== '\n';
|
|
326
|
+
if (deps.renderSource)
|
|
327
|
+
recordSourceEdit(text, getCursorOffset(el) ?? start, keystroke);
|
|
328
|
+
const spliced = text.slice(0, start) + insert + text.slice(end);
|
|
329
|
+
// An edit that empties the body leaves the same chrome-only source a bare block arrives
|
|
330
|
+
// as, so the edit door owes the completion the reveal door applies.
|
|
331
|
+
const completed = deps.completeBareSource?.(spliced) ?? null;
|
|
332
|
+
const next = completed?.text ?? spliced;
|
|
333
|
+
paintSource(el, next);
|
|
334
|
+
deps.onSourceEdit?.(next);
|
|
222
335
|
preEditOffset = start;
|
|
223
|
-
setCursorOffset(el, asDomTextOffset(start + insert.length));
|
|
336
|
+
setCursorOffset(el, asDomTextOffset(completed?.caret ?? start + insert.length));
|
|
337
|
+
// Armed once the edit has settled, so the window measures the gap the user leaves.
|
|
338
|
+
if (deps.renderSource && keystroke)
|
|
339
|
+
sourceBatch.armPause();
|
|
224
340
|
if (mode === 'plain')
|
|
225
341
|
editableSurface.onInput();
|
|
226
342
|
}
|
|
@@ -267,6 +383,44 @@ export function createEditableLeaf(deps) {
|
|
|
267
383
|
if (composing || !el)
|
|
268
384
|
return;
|
|
269
385
|
preEditOffset = getCursorOffset(el) ?? 0;
|
|
386
|
+
// Undo INSIDE an open painted reveal steps back through this session's own edits: the
|
|
387
|
+
// document's history sees the session as one entry written on blur, so until the local
|
|
388
|
+
// stack is spent the chord has nothing else to mean. Resolved through the keymap like every
|
|
389
|
+
// chord, so a host's rebinding or disable reaches it.
|
|
390
|
+
if (deps.renderSource && isRevealed()) {
|
|
391
|
+
const command = historyCommandFor(e);
|
|
392
|
+
if (command === 'history.undo' && sourceUndo.length > 0) {
|
|
393
|
+
e.preventDefault();
|
|
394
|
+
restoreSourceEntry(el, sourceUndo, sourceRedo);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
if (command === 'history.redo' && sourceRedo.length > 0) {
|
|
398
|
+
e.preventDefault();
|
|
399
|
+
restoreSourceEntry(el, sourceRedo, sourceUndo);
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
// Backspace in a painted source that holds nothing but its own chrome deletes the block, as
|
|
404
|
+
// it does in a code block: an empty body has no byte the press could mean, and a seat that
|
|
405
|
+
// only steps out (or eats the one blank line) leaves a block the user just asked to be rid
|
|
406
|
+
// of. In every mode — the gate is the byte BEFORE the caret: whitespace, or nothing, so a
|
|
407
|
+
// press that sits right after a visible marker still edits that marker in source mode.
|
|
408
|
+
if (e.key === 'Backspace' && !e.shiftKey && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
|
409
|
+
const offset = deps.renderSource ? getCursorOffset(el) : null;
|
|
410
|
+
const text = el.textContent ?? '';
|
|
411
|
+
if (offset !== null &&
|
|
412
|
+
(offset === 0 || /\s/.test(text[offset - 1] ?? '')) &&
|
|
413
|
+
!hasSelectionIn(el) &&
|
|
414
|
+
chromeFreeText(el).trim() === '') {
|
|
415
|
+
e.preventDefault();
|
|
416
|
+
revealedBase = null;
|
|
417
|
+
deps.setRevealed?.(false);
|
|
418
|
+
const index = deps.getIndex();
|
|
419
|
+
await blockEdit.deleteBlock(index);
|
|
420
|
+
void focusActions.moveFocus(index - 1, 'end');
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
270
424
|
if ((await handleSharedKeydown(e, editableSurface.sharedCtx)) || editableSurface.isDetached())
|
|
271
425
|
return;
|
|
272
426
|
if (dispatchChord(e))
|
|
@@ -290,6 +444,56 @@ export function createEditableLeaf(deps) {
|
|
|
290
444
|
spliceSourceText(el, offset, offset, '\n');
|
|
291
445
|
}
|
|
292
446
|
}
|
|
447
|
+
/**
|
|
448
|
+
* A painted source takes plain text edits here, not from the engine: Chromium treats a lone
|
|
449
|
+
* `\n` text node as a placeholder and replaces it on insert (a fresh `$$` block lost the line
|
|
450
|
+
* before its closer), and a native delete has no notion of hidden chrome. The edit range is
|
|
451
|
+
* clamped to the landable span, so nothing reaches a fence line. Composition stays native.
|
|
452
|
+
*/
|
|
453
|
+
function onBeforeInput(e) {
|
|
454
|
+
if (!deps.renderSource || composing || e.isComposing)
|
|
455
|
+
return;
|
|
456
|
+
const el = deps.getEl();
|
|
457
|
+
if (!el)
|
|
458
|
+
return;
|
|
459
|
+
let insert;
|
|
460
|
+
switch (e.inputType) {
|
|
461
|
+
case 'insertText':
|
|
462
|
+
insert = e.data ?? '';
|
|
463
|
+
break;
|
|
464
|
+
case 'insertLineBreak':
|
|
465
|
+
case 'insertParagraph':
|
|
466
|
+
insert = '\n';
|
|
467
|
+
break;
|
|
468
|
+
case 'deleteContentBackward':
|
|
469
|
+
case 'deleteContentForward':
|
|
470
|
+
case 'deleteWordBackward':
|
|
471
|
+
case 'deleteWordForward':
|
|
472
|
+
insert = '';
|
|
473
|
+
break;
|
|
474
|
+
default:
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
const target = e.getTargetRanges()[0];
|
|
478
|
+
const range = target ? getRangeOffsets(el, target) : null;
|
|
479
|
+
if (!range)
|
|
480
|
+
return;
|
|
481
|
+
e.preventDefault();
|
|
482
|
+
const start = clampToLandableRaw(el, range.start, 0);
|
|
483
|
+
const end = Math.max(start, clampToLandableRaw(el, range.end, 0));
|
|
484
|
+
spliceSourceText(el, start, end, insert);
|
|
485
|
+
}
|
|
486
|
+
function historyCommandFor(e) {
|
|
487
|
+
const chord = eventToChord(e);
|
|
488
|
+
if (!chord)
|
|
489
|
+
return undefined;
|
|
490
|
+
return resolveBinding(chord, deps.getNode().kind, keybindingOverrides(), activePlugins)
|
|
491
|
+
?.command;
|
|
492
|
+
}
|
|
493
|
+
function hasSelectionIn(el) {
|
|
494
|
+
const sel = window.getSelection();
|
|
495
|
+
return Boolean(sel && !sel.isCollapsed && el.contains(sel.anchorNode));
|
|
496
|
+
}
|
|
293
497
|
function onPointerDown(e) {
|
|
294
498
|
void crossBlock.handlePointerDown(e);
|
|
295
499
|
}
|
|
@@ -303,17 +507,25 @@ export function createEditableLeaf(deps) {
|
|
|
303
507
|
const descriptor = tryGetBlockKindDescriptor(deps.getNode().kind);
|
|
304
508
|
return descriptor?.caretTargetAtPoint?.(host, e.clientX, e.clientY)?.offset ?? 0;
|
|
305
509
|
}
|
|
510
|
+
// The press only notes where it landed: revealing on the press swallowed every drag that
|
|
511
|
+
// began on a rendered equation. The editor's own drag runs from such a press (its root
|
|
512
|
+
// handler), and the CLICK — a release that did not move — is what reveals.
|
|
513
|
+
let renderPress = null;
|
|
306
514
|
function onRenderPointerDown(e) {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
515
|
+
renderPress = e.shiftKey || isReading() ? null : { x: e.clientX, y: e.clientY };
|
|
516
|
+
}
|
|
517
|
+
function onRenderClick(e) {
|
|
518
|
+
const press = renderPress;
|
|
519
|
+
renderPress = null;
|
|
520
|
+
if (!press || e.shiftKey || isReading())
|
|
521
|
+
return;
|
|
522
|
+
if (Math.abs(e.clientX - press.x) > 3 || Math.abs(e.clientY - press.y) > 3)
|
|
310
523
|
return;
|
|
311
|
-
e.preventDefault();
|
|
312
524
|
// The reveal lands a caret, so this owes the shared preamble. NOT through
|
|
313
525
|
// crossBlock.handlePointerDown: that hit-tests against the SOURCE text, which the
|
|
314
526
|
// rendered view is not.
|
|
315
|
-
resetForPointerDown(selection, stickyColumn, edgeAffinity,
|
|
316
|
-
void
|
|
527
|
+
resetForPointerDown(selection, stickyColumn, edgeAffinity, false);
|
|
528
|
+
void revealSource(revealOffsetAt(e));
|
|
317
529
|
}
|
|
318
530
|
// The revealed source owns the press and has already spent the chord, so a spread the fold
|
|
319
531
|
// does not unmount must not re-run either on the way up.
|
|
@@ -322,6 +534,10 @@ export function createEditableLeaf(deps) {
|
|
|
322
534
|
if (!isRevealed())
|
|
323
535
|
onRenderPointerDown(e);
|
|
324
536
|
},
|
|
537
|
+
onclick: (e) => {
|
|
538
|
+
if (!isRevealed())
|
|
539
|
+
onRenderClick(e);
|
|
540
|
+
},
|
|
325
541
|
onkeydown: (e) => {
|
|
326
542
|
if (!isRevealed())
|
|
327
543
|
void dispatchChord(e);
|
|
@@ -333,7 +549,8 @@ export function createEditableLeaf(deps) {
|
|
|
333
549
|
role: 'textbox',
|
|
334
550
|
spellcheck: 'false',
|
|
335
551
|
oninput: editableSurface.onInput,
|
|
336
|
-
|
|
552
|
+
onbeforeinput: onBeforeInput,
|
|
553
|
+
onkeydown: withKeydownVerdict(handleKeydown),
|
|
337
554
|
oncopy: clipboard.onCopy,
|
|
338
555
|
oncut: clipboard.onCut,
|
|
339
556
|
onpaste: clipboard.onPaste,
|
|
@@ -373,6 +590,7 @@ export function createEditableLeaf(deps) {
|
|
|
373
590
|
get sourceText() {
|
|
374
591
|
return sourceText();
|
|
375
592
|
},
|
|
593
|
+
repaintSource,
|
|
376
594
|
surfaceProps,
|
|
377
595
|
renderProps,
|
|
378
596
|
getPresentationMode,
|
|
@@ -403,7 +621,7 @@ export function createEditableLeaf(deps) {
|
|
|
403
621
|
reveal: (offset = 0) => {
|
|
404
622
|
if (mode !== 'render-primary')
|
|
405
623
|
return Promise.resolve(surface.focus(offset));
|
|
406
|
-
return isReading() ? Promise.resolve() :
|
|
624
|
+
return isReading() ? Promise.resolve() : revealSource(offset);
|
|
407
625
|
},
|
|
408
626
|
commitSource: (edited) => void commitSource(edited)
|
|
409
627
|
};
|
|
@@ -22,6 +22,12 @@ import type { SelectionState } from '../../selection/selection-state.svelte';
|
|
|
22
22
|
import { type RawOffset } from '../../cursor/coordinate-spaces';
|
|
23
23
|
import { type CrossBlockHandlers } from '../../selection/cross-block/dispatch';
|
|
24
24
|
import type { SharedKeydownContext } from '../../selection/shared-keydown';
|
|
25
|
+
/**
|
|
26
|
+
* Binds a surface's keydown handler so the interaction trace records ONE verdict per event,
|
|
27
|
+
* after the handler's own await chain settles. That record is the e2e harness's only positive
|
|
28
|
+
* signal that a gesture which must change nothing has finished. Disabled, one boolean read.
|
|
29
|
+
*/
|
|
30
|
+
export declare function withKeydownVerdict(handle: (e: KeyboardEvent) => Promise<void>): (e: KeyboardEvent) => void;
|
|
25
31
|
/**
|
|
26
32
|
* Per-surface cursor I/O in raw-content coordinates (ambient marker excluded).
|
|
27
33
|
* `buildRange` maps a raw-offset span to a DOM Range for selection writes.
|
|
@@ -186,6 +192,9 @@ export interface ClipboardSurfaceDeps {
|
|
|
186
192
|
copyPreHook?: (e: ClipboardEvent) => boolean;
|
|
187
193
|
/** Pre-cross-block cut arm (selected-widget splice, intra-table rect cut). */
|
|
188
194
|
cutPreHook?: (e: ClipboardEvent) => boolean | Promise<boolean>;
|
|
195
|
+
/** Pre-cross-block paste arm, handed the normalized text while a live rectangle is still
|
|
196
|
+
* readable (a grid into a table's cells). True when it consumed the paste. */
|
|
197
|
+
pastePreHook?: (text: string) => boolean | Promise<boolean>;
|
|
189
198
|
/** The intra-block copy payload; owns its preventDefault. Omit to write the
|
|
190
199
|
* visible selection string (code, leaf); text and the cell slice their raw. */
|
|
191
200
|
copyTail?: (e: ClipboardEvent) => void;
|
|
@@ -17,9 +17,24 @@ import { createCrossBlockHandlers } from '../../selection/cross-block/dispatch';
|
|
|
17
17
|
import { writeCrossBlockCopy, writeCrossBlockCut } from '../../selection/cross-block/clipboard';
|
|
18
18
|
import { createImagePasteArm } from '../paste-image-arm';
|
|
19
19
|
import { clampToLandableRaw, revealsNoMarkers } from '../../cursor/widget-offset';
|
|
20
|
-
import {
|
|
20
|
+
import { isInteractionTraceEnabled, traceCompositionEnd, traceCompositionStart, traceKeydownVerdict } from '../../debug/interaction-trace';
|
|
21
21
|
import { assertInvariant } from '../../assert';
|
|
22
22
|
import { checkCompositionEndPaired } from '../../invariants/inline-transitions';
|
|
23
|
+
// ── Keydown verdict ─────────────────────────────────────────────────────────
|
|
24
|
+
/**
|
|
25
|
+
* Binds a surface's keydown handler so the interaction trace records ONE verdict per event,
|
|
26
|
+
* after the handler's own await chain settles. That record is the e2e harness's only positive
|
|
27
|
+
* signal that a gesture which must change nothing has finished. Disabled, one boolean read.
|
|
28
|
+
*/
|
|
29
|
+
export function withKeydownVerdict(handle) {
|
|
30
|
+
return (e) => {
|
|
31
|
+
if (!isInteractionTraceEnabled()) {
|
|
32
|
+
void handle(e);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
void handle(e).then(() => traceKeydownVerdict(e.key, e.defaultPrevented));
|
|
36
|
+
};
|
|
37
|
+
}
|
|
23
38
|
/**
|
|
24
39
|
* Guarded pending-caret restore. Applies only while `el` still holds focus, so a blur
|
|
25
40
|
* between arming the restore and the render drops it instead of yanking the global
|
|
@@ -92,7 +107,9 @@ export function createEditableSurface(deps) {
|
|
|
92
107
|
const el = deps.getEl();
|
|
93
108
|
if (!el)
|
|
94
109
|
return;
|
|
95
|
-
|
|
110
|
+
// `preventScroll`: seating a caret must not move the page — the reveal path scrolls when a
|
|
111
|
+
// target is off-screen; an implicit focus scroll jumped the document on every table edit.
|
|
112
|
+
el.focus({ preventScroll: true });
|
|
96
113
|
if (offset === CURSOR_EXACT_START) {
|
|
97
114
|
deps.backend.setRaw(asRawOffset(0));
|
|
98
115
|
return;
|
|
@@ -107,7 +124,7 @@ export function createEditableSurface(deps) {
|
|
|
107
124
|
const el = deps.getEl();
|
|
108
125
|
if (!el)
|
|
109
126
|
return;
|
|
110
|
-
el.focus();
|
|
127
|
+
el.focus({ preventScroll: true });
|
|
111
128
|
const ambientLength = deps.getAmbientLength();
|
|
112
129
|
// minOffset = the walk position of raw 0 keeps the scan out of the marker region.
|
|
113
130
|
const minOffset = toDomTextOffset(asRawOffset(0), ambientLength);
|
|
@@ -283,6 +300,8 @@ export function createClipboardHandlers(deps) {
|
|
|
283
300
|
async function insertPastedText(text, e) {
|
|
284
301
|
const fold = deps.foldReveal?.() ?? null;
|
|
285
302
|
await fold?.settled;
|
|
303
|
+
if (text && deps.pastePreHook && (await deps.pastePreHook(text)))
|
|
304
|
+
return;
|
|
286
305
|
if (await deps.crossBlock.handlePaste(e, text))
|
|
287
306
|
return;
|
|
288
307
|
deps.stickyColumn.reset();
|
|
@@ -39,16 +39,20 @@
|
|
|
39
39
|
import { dispatchKindCommand } from '../../../schema/block-commands';
|
|
40
40
|
import type { AnyCommandId } from '../../../schema/command-id';
|
|
41
41
|
import BlockDragHandle from '../../BlockDragHandle.svelte';
|
|
42
|
+
import { showsListItemDragHandle } from '../../drag-handle';
|
|
42
43
|
|
|
43
44
|
let {
|
|
44
45
|
node,
|
|
45
46
|
index,
|
|
46
47
|
myPath = [],
|
|
48
|
+
itemCount,
|
|
47
49
|
slots
|
|
48
50
|
}: {
|
|
49
51
|
node: NodeView;
|
|
50
52
|
index: number;
|
|
51
53
|
myPath?: number[];
|
|
54
|
+
/** How many items the enclosing list holds; a lone item has no sibling to reorder past. */
|
|
55
|
+
itemCount: number;
|
|
52
56
|
slots?: RefSlots<BlockComponent>;
|
|
53
57
|
} = $props();
|
|
54
58
|
|
|
@@ -67,6 +71,10 @@
|
|
|
67
71
|
// $derived, not a mount-time snapshot: a runtime prop toggle must reach blocks
|
|
68
72
|
// that window in and out after the change, not just those mounted at mount.
|
|
69
73
|
const dragHandles = $derived(getDragHandles?.() ?? false);
|
|
74
|
+
// The grip is the only pointer road into a reorder, and a reorder needs a sibling: a lone
|
|
75
|
+
// item stays a reorder host (a dragged sibling never arrives, but the class costs nothing)
|
|
76
|
+
// and simply shows nothing to grab.
|
|
77
|
+
const showsHandle = $derived(showsListItemDragHandle(itemCount, dragHandles));
|
|
70
78
|
const presentationMode = $derived(getPresentationMode?.() ?? 'source');
|
|
71
79
|
const readOnly = $derived(presentationMode === 'reading');
|
|
72
80
|
|
|
@@ -260,6 +268,7 @@
|
|
|
260
268
|
<div
|
|
261
269
|
class="list-item-block"
|
|
262
270
|
class:reorder-host={dragHandles}
|
|
271
|
+
class:handle-host={showsHandle}
|
|
263
272
|
data-task-checked={taskCheckedAttr}
|
|
264
273
|
data-list-marker={presentationMarkerKind}
|
|
265
274
|
bind:this={boxEl}
|
|
@@ -276,8 +285,9 @@
|
|
|
276
285
|
/>
|
|
277
286
|
</div>
|
|
278
287
|
<!-- A list item IS a reorder unit; its inner content BlockList passes the
|
|
279
|
-
default reorderable={false}, so the paragraph inside gets no handle.
|
|
280
|
-
|
|
288
|
+
default reorderable={false}, so the paragraph inside gets no handle. A lone item
|
|
289
|
+
renders none either: its drag is list-scoped and there is no sibling to pass. -->
|
|
290
|
+
{#if showsHandle}
|
|
281
291
|
<BlockDragHandle />
|
|
282
292
|
{/if}
|
|
283
293
|
</div>
|
|
@@ -289,7 +299,7 @@
|
|
|
289
299
|
align-items: flex-start;
|
|
290
300
|
}
|
|
291
301
|
|
|
292
|
-
/* Hover reveal is the shared global `.
|
|
302
|
+
/* Hover reveal is the shared global `.handle-host` rule in BlockHost; it reveals
|
|
293
303
|
only the innermost hovered unit, so a sub-item's hover never lights the parent. */
|
|
294
304
|
|
|
295
305
|
.list-item-content {
|
|
@@ -318,7 +328,6 @@
|
|
|
318
328
|
> :global(.block-list)
|
|
319
329
|
> :global(.block-host:first-child)
|
|
320
330
|
> :global(:not(.list-block)) {
|
|
321
|
-
text-
|
|
322
|
-
color: var(--syntax-task-done, rgba(128, 128, 128, 0.7));
|
|
331
|
+
color: var(--syntax-task-done, var(--color-text-muted, rgba(128, 128, 128, 0.7)));
|
|
323
332
|
}
|
|
324
333
|
</style>
|
|
@@ -5,6 +5,8 @@ type $$ComponentProps = {
|
|
|
5
5
|
node: NodeView;
|
|
6
6
|
index: number;
|
|
7
7
|
myPath?: number[];
|
|
8
|
+
/** How many items the enclosing list holds; a lone item has no sibling to reorder past. */
|
|
9
|
+
itemCount: number;
|
|
8
10
|
slots?: RefSlots<BlockComponent>;
|
|
9
11
|
};
|
|
10
12
|
declare const ListItemBlock: import("svelte").Component<$$ComponentProps, {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import type { AmbientPrefix } from '../../../block-component';
|
|
2
2
|
import type { ListItemMetadata } from '../../../core/nodes';
|
|
3
|
+
/** Matches the `.task-checkbox` slot and gap in editor.css; the two move together. */
|
|
4
|
+
export declare const TASK_HANGING_INDENT = "2.1em";
|
|
3
5
|
export declare function buildTaskItemAmbient(metadata: ListItemMetadata | undefined, onToggle: () => void): AmbientPrefix;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { devWarn } from '../../../dev-warn';
|
|
2
|
+
/** Matches the `.task-checkbox` slot and gap in editor.css; the two move together. */
|
|
3
|
+
export const TASK_HANGING_INDENT = '2.1em';
|
|
2
4
|
function isTaskMarkerChecked(taskMarker) {
|
|
3
5
|
const c = taskMarker[1];
|
|
4
6
|
return c === 'x' || c === 'X';
|
|
@@ -26,8 +28,15 @@ export function buildTaskItemAmbient(metadata, onToggle) {
|
|
|
26
28
|
role: 'checkbox',
|
|
27
29
|
// single source of truth: derive from the keyed marker (in the render memo key), not parallel taskChecked
|
|
28
30
|
ariaChecked: isTaskMarkerChecked(metadata.taskMarker),
|
|
31
|
+
// The painted box is taller than the text beside it; the grip centres on the box.
|
|
32
|
+
dragAnchor: true,
|
|
29
33
|
onClick: onToggle
|
|
30
|
-
}
|
|
31
|
-
|
|
34
|
+
},
|
|
35
|
+
// The list marker gets a span of its own so the rendered modes can collapse its width:
|
|
36
|
+
// GitHub draws the box at the margin, not indented under a bullet that is not there.
|
|
37
|
+
{ start: 0, end: boxStart, className: 'task-list-marker', onClick: () => { } }
|
|
38
|
+
],
|
|
39
|
+
// The painted box (editor.css): slot + gap + the marker's trailing space.
|
|
40
|
+
indent: TASK_HANGING_INDENT
|
|
32
41
|
};
|
|
33
42
|
}
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* `createEditableSurface` itself stays context-free (the jsdom harness constructs it bare).
|
|
6
6
|
*/
|
|
7
7
|
import { type KindCommandTarget } from '../../schema/block-commands';
|
|
8
|
+
import type { AnyBlockKind } from '../../core/nodes';
|
|
9
|
+
import type { AnyCommandId } from '../../schema/command-id';
|
|
8
10
|
import type { EditableSurfaceDeps } from './editable-surface';
|
|
9
11
|
/** The context-threaded half of `EditableSurfaceDeps` — the fields every surface passes verbatim. */
|
|
10
12
|
export type SharedSurfaceDeps = Pick<EditableSurfaceDeps, 'selection' | 'getDoc' | 'getBlockElByPath' | 'focusActions' | 'getEditorRoot' | 'getScrollHost' | 'getEditorLifetime' | 'stickyColumn' | 'edgeAffinity' | 'blockEdit' | 'controller' | 'history' | 'pluginEditor' | 'getKeybindingOverrides' | 'pasteCoordinator' | 'grammar' | 'activePlugins' | 'events' | 'linkRef' | 'onCommandError' | 'crossBlockCommands'>;
|
|
@@ -13,6 +15,8 @@ export interface SurfaceWiring {
|
|
|
13
15
|
deps: SharedSurfaceDeps;
|
|
14
16
|
/** Resolve a chord at `target` through the shared gates; consumes the event when spent. */
|
|
15
17
|
dispatchChord(e: KeyboardEvent, target: KindCommandTarget): boolean;
|
|
18
|
+
/** The command a press names at `kind`, overrides included, without running it. */
|
|
19
|
+
resolveChord(e: KeyboardEvent, kind: AnyBlockKind): AnyCommandId | null;
|
|
16
20
|
}
|
|
17
21
|
export declare function wireSurfaceContexts(): SurfaceWiring;
|
|
18
22
|
export declare function useParkFocusOnUnmount(getEl: () => HTMLElement | null, getEditorRoot: () => HTMLElement | null): void;
|