@voithos-labs/aragonite 0.10.2 → 0.10.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -21
- 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 +381 -198
- 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 +21 -12
- 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/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 +126 -96
- package/docs/guide/plugin-api.md +31 -3
- package/docs/guide/plugin-guide.md +26 -3
- 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
|
@@ -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,73 @@ 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 next = text.slice(0, start) + insert + text.slice(end);
|
|
329
|
+
paintSource(el, next);
|
|
330
|
+
deps.onSourceEdit?.(next);
|
|
222
331
|
preEditOffset = start;
|
|
223
332
|
setCursorOffset(el, asDomTextOffset(start + insert.length));
|
|
333
|
+
// Armed once the edit has settled, so the window measures the gap the user leaves.
|
|
334
|
+
if (deps.renderSource && keystroke)
|
|
335
|
+
sourceBatch.armPause();
|
|
224
336
|
if (mode === 'plain')
|
|
225
337
|
editableSurface.onInput();
|
|
226
338
|
}
|
|
@@ -267,6 +379,44 @@ export function createEditableLeaf(deps) {
|
|
|
267
379
|
if (composing || !el)
|
|
268
380
|
return;
|
|
269
381
|
preEditOffset = getCursorOffset(el) ?? 0;
|
|
382
|
+
// Undo INSIDE an open painted reveal steps back through this session's own edits: the
|
|
383
|
+
// document's history sees the session as one entry written on blur, so until the local
|
|
384
|
+
// stack is spent the chord has nothing else to mean. Resolved through the keymap like every
|
|
385
|
+
// chord, so a host's rebinding or disable reaches it.
|
|
386
|
+
if (deps.renderSource && isRevealed()) {
|
|
387
|
+
const command = historyCommandFor(e);
|
|
388
|
+
if (command === 'history.undo' && sourceUndo.length > 0) {
|
|
389
|
+
e.preventDefault();
|
|
390
|
+
restoreSourceEntry(el, sourceUndo, sourceRedo);
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
if (command === 'history.redo' && sourceRedo.length > 0) {
|
|
394
|
+
e.preventDefault();
|
|
395
|
+
restoreSourceEntry(el, sourceRedo, sourceUndo);
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
// Backspace in a painted source that holds nothing but its own chrome deletes the block, as
|
|
400
|
+
// it does in a code block: an empty body has no byte the press could mean, and a seat that
|
|
401
|
+
// only steps out (or eats the one blank line) leaves a block the user just asked to be rid
|
|
402
|
+
// of. In every mode — the gate is the byte BEFORE the caret: whitespace, or nothing, so a
|
|
403
|
+
// press that sits right after a visible marker still edits that marker in source mode.
|
|
404
|
+
if (e.key === 'Backspace' && !e.shiftKey && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
|
405
|
+
const offset = deps.renderSource ? getCursorOffset(el) : null;
|
|
406
|
+
const text = el.textContent ?? '';
|
|
407
|
+
if (offset !== null &&
|
|
408
|
+
(offset === 0 || /\s/.test(text[offset - 1] ?? '')) &&
|
|
409
|
+
!hasSelectionIn(el) &&
|
|
410
|
+
chromeFreeText(el).trim() === '') {
|
|
411
|
+
e.preventDefault();
|
|
412
|
+
revealedBase = null;
|
|
413
|
+
deps.setRevealed?.(false);
|
|
414
|
+
const index = deps.getIndex();
|
|
415
|
+
await blockEdit.deleteBlock(index);
|
|
416
|
+
void focusActions.moveFocus(index - 1, 'end');
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
270
420
|
if ((await handleSharedKeydown(e, editableSurface.sharedCtx)) || editableSurface.isDetached())
|
|
271
421
|
return;
|
|
272
422
|
if (dispatchChord(e))
|
|
@@ -290,6 +440,56 @@ export function createEditableLeaf(deps) {
|
|
|
290
440
|
spliceSourceText(el, offset, offset, '\n');
|
|
291
441
|
}
|
|
292
442
|
}
|
|
443
|
+
/**
|
|
444
|
+
* A painted source takes plain text edits here, not from the engine: Chromium treats a lone
|
|
445
|
+
* `\n` text node as a placeholder and replaces it on insert (a fresh `$$` block lost the line
|
|
446
|
+
* before its closer), and a native delete has no notion of hidden chrome. The edit range is
|
|
447
|
+
* clamped to the landable span, so nothing reaches a fence line. Composition stays native.
|
|
448
|
+
*/
|
|
449
|
+
function onBeforeInput(e) {
|
|
450
|
+
if (!deps.renderSource || composing || e.isComposing)
|
|
451
|
+
return;
|
|
452
|
+
const el = deps.getEl();
|
|
453
|
+
if (!el)
|
|
454
|
+
return;
|
|
455
|
+
let insert;
|
|
456
|
+
switch (e.inputType) {
|
|
457
|
+
case 'insertText':
|
|
458
|
+
insert = e.data ?? '';
|
|
459
|
+
break;
|
|
460
|
+
case 'insertLineBreak':
|
|
461
|
+
case 'insertParagraph':
|
|
462
|
+
insert = '\n';
|
|
463
|
+
break;
|
|
464
|
+
case 'deleteContentBackward':
|
|
465
|
+
case 'deleteContentForward':
|
|
466
|
+
case 'deleteWordBackward':
|
|
467
|
+
case 'deleteWordForward':
|
|
468
|
+
insert = '';
|
|
469
|
+
break;
|
|
470
|
+
default:
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
const target = e.getTargetRanges()[0];
|
|
474
|
+
const range = target ? getRangeOffsets(el, target) : null;
|
|
475
|
+
if (!range)
|
|
476
|
+
return;
|
|
477
|
+
e.preventDefault();
|
|
478
|
+
const start = clampToLandableRaw(el, range.start, 0);
|
|
479
|
+
const end = Math.max(start, clampToLandableRaw(el, range.end, 0));
|
|
480
|
+
spliceSourceText(el, start, end, insert);
|
|
481
|
+
}
|
|
482
|
+
function historyCommandFor(e) {
|
|
483
|
+
const chord = eventToChord(e);
|
|
484
|
+
if (!chord)
|
|
485
|
+
return undefined;
|
|
486
|
+
return resolveBinding(chord, deps.getNode().kind, keybindingOverrides(), activePlugins)
|
|
487
|
+
?.command;
|
|
488
|
+
}
|
|
489
|
+
function hasSelectionIn(el) {
|
|
490
|
+
const sel = window.getSelection();
|
|
491
|
+
return Boolean(sel && !sel.isCollapsed && el.contains(sel.anchorNode));
|
|
492
|
+
}
|
|
293
493
|
function onPointerDown(e) {
|
|
294
494
|
void crossBlock.handlePointerDown(e);
|
|
295
495
|
}
|
|
@@ -303,17 +503,25 @@ export function createEditableLeaf(deps) {
|
|
|
303
503
|
const descriptor = tryGetBlockKindDescriptor(deps.getNode().kind);
|
|
304
504
|
return descriptor?.caretTargetAtPoint?.(host, e.clientX, e.clientY)?.offset ?? 0;
|
|
305
505
|
}
|
|
506
|
+
// The press only notes where it landed: revealing on the press swallowed every drag that
|
|
507
|
+
// began on a rendered equation. The editor's own drag runs from such a press (its root
|
|
508
|
+
// handler), and the CLICK — a release that did not move — is what reveals.
|
|
509
|
+
let renderPress = null;
|
|
306
510
|
function onRenderPointerDown(e) {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
511
|
+
renderPress = e.shiftKey || isReading() ? null : { x: e.clientX, y: e.clientY };
|
|
512
|
+
}
|
|
513
|
+
function onRenderClick(e) {
|
|
514
|
+
const press = renderPress;
|
|
515
|
+
renderPress = null;
|
|
516
|
+
if (!press || e.shiftKey || isReading())
|
|
517
|
+
return;
|
|
518
|
+
if (Math.abs(e.clientX - press.x) > 3 || Math.abs(e.clientY - press.y) > 3)
|
|
310
519
|
return;
|
|
311
|
-
e.preventDefault();
|
|
312
520
|
// The reveal lands a caret, so this owes the shared preamble. NOT through
|
|
313
521
|
// crossBlock.handlePointerDown: that hit-tests against the SOURCE text, which the
|
|
314
522
|
// rendered view is not.
|
|
315
|
-
resetForPointerDown(selection, stickyColumn, edgeAffinity,
|
|
316
|
-
void
|
|
523
|
+
resetForPointerDown(selection, stickyColumn, edgeAffinity, false);
|
|
524
|
+
void revealSource(revealOffsetAt(e));
|
|
317
525
|
}
|
|
318
526
|
// The revealed source owns the press and has already spent the chord, so a spread the fold
|
|
319
527
|
// does not unmount must not re-run either on the way up.
|
|
@@ -322,6 +530,10 @@ export function createEditableLeaf(deps) {
|
|
|
322
530
|
if (!isRevealed())
|
|
323
531
|
onRenderPointerDown(e);
|
|
324
532
|
},
|
|
533
|
+
onclick: (e) => {
|
|
534
|
+
if (!isRevealed())
|
|
535
|
+
onRenderClick(e);
|
|
536
|
+
},
|
|
325
537
|
onkeydown: (e) => {
|
|
326
538
|
if (!isRevealed())
|
|
327
539
|
void dispatchChord(e);
|
|
@@ -333,7 +545,8 @@ export function createEditableLeaf(deps) {
|
|
|
333
545
|
role: 'textbox',
|
|
334
546
|
spellcheck: 'false',
|
|
335
547
|
oninput: editableSurface.onInput,
|
|
336
|
-
|
|
548
|
+
onbeforeinput: onBeforeInput,
|
|
549
|
+
onkeydown: withKeydownVerdict(handleKeydown),
|
|
337
550
|
oncopy: clipboard.onCopy,
|
|
338
551
|
oncut: clipboard.onCut,
|
|
339
552
|
onpaste: clipboard.onPaste,
|
|
@@ -373,6 +586,7 @@ export function createEditableLeaf(deps) {
|
|
|
373
586
|
get sourceText() {
|
|
374
587
|
return sourceText();
|
|
375
588
|
},
|
|
589
|
+
repaintSource,
|
|
376
590
|
surfaceProps,
|
|
377
591
|
renderProps,
|
|
378
592
|
getPresentationMode,
|
|
@@ -403,7 +617,7 @@ export function createEditableLeaf(deps) {
|
|
|
403
617
|
reveal: (offset = 0) => {
|
|
404
618
|
if (mode !== 'render-primary')
|
|
405
619
|
return Promise.resolve(surface.focus(offset));
|
|
406
|
-
return isReading() ? Promise.resolve() :
|
|
620
|
+
return isReading() ? Promise.resolve() : revealSource(offset);
|
|
407
621
|
},
|
|
408
622
|
commitSource: (edited) => void commitSource(edited)
|
|
409
623
|
};
|
|
@@ -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();
|
|
@@ -260,6 +260,7 @@
|
|
|
260
260
|
<div
|
|
261
261
|
class="list-item-block"
|
|
262
262
|
class:reorder-host={dragHandles}
|
|
263
|
+
class:handle-host={dragHandles}
|
|
263
264
|
data-task-checked={taskCheckedAttr}
|
|
264
265
|
data-list-marker={presentationMarkerKind}
|
|
265
266
|
bind:this={boxEl}
|
|
@@ -289,7 +290,7 @@
|
|
|
289
290
|
align-items: flex-start;
|
|
290
291
|
}
|
|
291
292
|
|
|
292
|
-
/* Hover reveal is the shared global `.
|
|
293
|
+
/* Hover reveal is the shared global `.handle-host` rule in BlockHost; it reveals
|
|
293
294
|
only the innermost hovered unit, so a sub-item's hover never lights the parent. */
|
|
294
295
|
|
|
295
296
|
.list-item-content {
|
|
@@ -318,7 +319,6 @@
|
|
|
318
319
|
> :global(.block-list)
|
|
319
320
|
> :global(.block-host:first-child)
|
|
320
321
|
> :global(:not(.list-block)) {
|
|
321
|
-
text-
|
|
322
|
-
color: var(--syntax-task-done, rgba(128, 128, 128, 0.7));
|
|
322
|
+
color: var(--syntax-task-done, var(--color-text-muted, rgba(128, 128, 128, 0.7)));
|
|
323
323
|
}
|
|
324
324
|
</style>
|
|
@@ -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;
|
|
@@ -9,6 +9,7 @@ import { BLOCK_EDIT_KEY, EDITOR_DOC_KEY, EDITOR_POLICIES_KEY, EDITOR_SERVICES_KE
|
|
|
9
9
|
import { emitCommandError } from '../../editor-events';
|
|
10
10
|
import { eventToChord } from '../../schema/keybindings';
|
|
11
11
|
import { dispatchKeyCommand } from '../../schema/block-commands';
|
|
12
|
+
import { resolveBinding } from '../../schema/commands';
|
|
12
13
|
import { parkFocusOnEditorRoot } from '../../selection/native-bridge';
|
|
13
14
|
export function wireSurfaceContexts() {
|
|
14
15
|
const blockEdit = getContext(BLOCK_EDIT_KEY);
|
|
@@ -56,7 +57,13 @@ export function wireSurfaceContexts() {
|
|
|
56
57
|
e.preventDefault();
|
|
57
58
|
return true;
|
|
58
59
|
};
|
|
59
|
-
|
|
60
|
+
const resolveChord = (e, kind) => {
|
|
61
|
+
const chord = eventToChord(e);
|
|
62
|
+
if (!chord)
|
|
63
|
+
return null;
|
|
64
|
+
return resolveBinding(chord, kind, keybindingOverrides(), activePlugins)?.command ?? null;
|
|
65
|
+
};
|
|
66
|
+
return { deps, dispatchChord, resolveChord };
|
|
60
67
|
}
|
|
61
68
|
// Windowed out while focused: hand focus to the editor root so the next keystroke
|
|
62
69
|
// routes through its document-level listener instead of falling to `<body>`.
|