@voithos-labs/aragonite 0.10.3 → 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/dist/a11y-strings.d.ts +1 -0
- package/dist/a11y-strings.js +1 -0
- package/dist/components/BlockDragHandle.svelte +2 -0
- package/dist/components/BlockHost.svelte +2 -2
- package/dist/components/Editor.svelte +56 -3
- package/dist/components/SelectionOverlay.svelte +17 -23
- package/dist/components/SelectionOverlay.svelte.d.ts +1 -1
- package/dist/components/blocks/code/CodeBlockRail.svelte +42 -24
- package/dist/components/blocks/code/code-bootstrap.js +12 -0
- package/dist/components/blocks/code/code-languages.d.ts +5 -5
- package/dist/components/blocks/code/code-languages.js +20 -12
- package/dist/components/blocks/editable-leaf.d.ts +3 -3
- package/dist/components/blocks/editable-leaf.js +6 -2
- package/dist/components/blocks/list/ListBlock.svelte +1 -0
- package/dist/components/blocks/list/ListItemBlock.svelte +12 -3
- package/dist/components/blocks/list/ListItemBlock.svelte.d.ts +2 -0
- 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/edge-policy-dispatch.js +33 -18
- package/dist/components/blocks/text/live-selection-edit.d.ts +5 -5
- package/dist/components/blocks/text/live-selection-edit.js +10 -7
- package/dist/components/blocks/text/text-clipboard.js +2 -2
- package/dist/components/blocks/text/widget-interaction.js +45 -23
- package/dist/components/drag-handle.d.ts +6 -0
- package/dist/components/drag-handle.js +8 -0
- package/dist/components/editor-root-geometry.d.ts +1 -1
- package/dist/components/editor-root-geometry.js +1 -1
- package/dist/components/editor-root-listeners.d.ts +0 -7
- package/dist/components/editor-root-listeners.js +0 -22
- package/dist/components/image/ImageResizeHandles.svelte +18 -10
- package/dist/components/menu/SelectionToolbar.svelte +385 -0
- package/dist/components/menu/SelectionToolbar.svelte.d.ts +16 -0
- package/dist/core/inline/inline-widgets.d.ts +12 -4
- package/dist/core/inline/inline-widgets.js +5 -0
- package/dist/core/inline/transparency.js +3 -3
- package/dist/cursor/height-oracle.d.ts +2 -3
- package/dist/cursor/height-oracle.js +0 -1
- 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 +3 -0
- package/dist/cursor/widget-offset.js +10 -0
- package/dist/decorations/reserved-attrs.js +1 -0
- 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/plugin/container.d.ts +7 -0
- package/dist/editor-actions/plugin/container.js +3 -1
- package/dist/editor-props.d.ts +3 -0
- package/dist/plugin.d.ts +2 -1
- package/dist/plugin.js +6 -1
- package/dist/plugins/latex/latex-kind.js +48 -31
- package/dist/plugins/latex/math-source.d.ts +3 -3
- package/dist/plugins/latex/math-source.js +32 -26
- package/dist/plugins/mermaid/MermaidBlock.svelte +19 -9
- package/dist/plugins/parrot/ParrotBlock.svelte +3 -1
- package/dist/reactivity/list-windowing.svelte.js +26 -6
- package/dist/schema/commands.d.ts +9 -5
- package/dist/schema/commands.js +12 -7
- package/dist/schema/operations.d.ts +1 -1
- package/dist/schema/reserved-chords.js +12 -0
- package/dist/selection/cross-block/keydown.js +2 -28
- package/dist/selection/cross-block/paste.js +7 -13
- package/dist/selection/cross-block/type-replace.js +5 -15
- package/dist/selection/dead-space-caret.d.ts +3 -0
- package/dist/selection/dead-space-caret.js +12 -2
- package/dist/selection/drag-pointer.d.ts +19 -0
- package/dist/selection/drag-pointer.js +42 -0
- package/dist/selection/keyboard-extend.d.ts +3 -2
- package/dist/selection/keyboard-extend.js +3 -2
- 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 +10 -3
- 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.d.ts +4 -0
- package/dist/selection/range-delete.js +1 -1
- package/dist/selection/selection-drop.d.ts +34 -0
- package/dist/selection/selection-drop.js +253 -0
- package/dist/styles/editor-theme.css +18 -3
- package/dist/styles/editor.css +64 -13
- package/dist/tree-operations/paste/replace-block-at-parent.d.ts +3 -1
- package/dist/tree-operations/paste/replace-block-at-parent.js +4 -1
- package/dist/tree-operations/paste/replacement-parse.d.ts +17 -0
- package/dist/tree-operations/paste/replacement-parse.js +22 -0
- package/docs/guide/consumer-guide.md +10 -8
- package/docs/guide/plugin-api.md +54 -38
- package/docs/guide/plugin-guide.md +31 -22
- package/package.json +7 -7
- package/dist/selection/double-click-trim.d.ts +0 -17
- package/dist/selection/double-click-trim.js +0 -57
|
@@ -18,7 +18,7 @@ import { createMarkerCompletion } from './marker-completion';
|
|
|
18
18
|
import { resolveEdgeDeletion } from './construct-edge-delete';
|
|
19
19
|
import { hidesStructuralSuffix } from './hidden-suffix';
|
|
20
20
|
import { resolveEdgeSeat } from './edge-seat';
|
|
21
|
-
import {
|
|
21
|
+
import { replaceRangeRaw } from './live-selection-edit';
|
|
22
22
|
import { resolveMarkedInsertion } from './pending-mark-insert';
|
|
23
23
|
import { widgetAtCursor } from './widget-adjacency';
|
|
24
24
|
import { resolveDelimiterAutoPair } from './delimiter-autopair';
|
|
@@ -112,6 +112,12 @@ export function createEdgePolicyDispatch(deps) {
|
|
|
112
112
|
const d = display();
|
|
113
113
|
writeDisplay(d.slice(0, start) + insert + d.slice(end), start + insert.length, source, caretBefore);
|
|
114
114
|
}
|
|
115
|
+
/** The range this surface holds, or null at a caret. A key aimed at one edits the RANGE: the
|
|
116
|
+
* caret the arms read is only its start. */
|
|
117
|
+
function heldRange() {
|
|
118
|
+
const selection = deps.getRawSelection();
|
|
119
|
+
return selection && selection.start < selection.end ? selection : null;
|
|
120
|
+
}
|
|
115
121
|
// ── The seams the arms below defer to ────────────────────────────────────
|
|
116
122
|
/**
|
|
117
123
|
* The rung a range rewrite can be asked about. Past the reading cut a surface revealing no
|
|
@@ -150,6 +156,20 @@ export function createEdgePolicyDispatch(deps) {
|
|
|
150
156
|
return null;
|
|
151
157
|
return resolveEdgeSeat(caret, inlinesOf(deps.node), deps.getEdgeAffinity(), deps.node.raw, screenVisibilityOf(el), typed);
|
|
152
158
|
}
|
|
159
|
+
/** A printable byte an arm claimed because the engine drops it at an element-level caret. Over
|
|
160
|
+
* a held range it REPLACES through the join seam, the road a mid-block range takes; at a caret
|
|
161
|
+
* the seat answers which side of an unpainted run it lands on. */
|
|
162
|
+
function writeTypedByte(el, caretOffset, typed, source) {
|
|
163
|
+
const range = heldRange();
|
|
164
|
+
if (range) {
|
|
165
|
+
const edit = replaceRangeRaw(deps.node, range, typed, joinSeamMode(el), deps.linkRef, deps.getAmbientPrefix?.() ?? '');
|
|
166
|
+
void deps.blockEdit.updateBlockContent(deps.index, edit.raw, range.start, edit.caret);
|
|
167
|
+
deps.setPendingCursor(edit.caret, source, edit.raw);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const seatedAt = typingSeatAt(el, caretOffset, typed)?.offset ?? caretOffset;
|
|
171
|
+
editDisplay(seatedAt, seatedAt, typed, source, caretOffset);
|
|
172
|
+
}
|
|
153
173
|
// ── CST inline widget ────────────────────────────────────────────────────
|
|
154
174
|
function handleCstWidget(e, caretOffset) {
|
|
155
175
|
if (deps.isRevealing())
|
|
@@ -163,9 +183,11 @@ export function createEdgePolicyDispatch(deps) {
|
|
|
163
183
|
const widgetAt = widgetAtCursor(caretOffset, inlinesOf(node), node.raw, direction);
|
|
164
184
|
if (!widgetAt)
|
|
165
185
|
return false;
|
|
166
|
-
//
|
|
167
|
-
|
|
168
|
-
|
|
186
|
+
// Below the bail: every keystroke in every prose block reaches the line above.
|
|
187
|
+
const range = heldRange();
|
|
188
|
+
// Unchorded, and at a CARET: a modifier makes the key a word-scoped platform command, and
|
|
189
|
+
// a held range is an edit of the range, not an entry into the construct beside its start.
|
|
190
|
+
const plainEdgeKey = !e.shiftKey && !hasModifier(e) && !range;
|
|
169
191
|
const enterFromRight = plainEdgeKey && widgetAt.atRight && (e.key === 'ArrowLeft' || e.key === 'Backspace');
|
|
170
192
|
const enterFromLeft = plainEdgeKey && !widgetAt.atRight && (e.key === 'ArrowRight' || e.key === 'Delete');
|
|
171
193
|
if (enterFromRight || enterFromLeft) {
|
|
@@ -200,14 +222,7 @@ export function createEdgePolicyDispatch(deps) {
|
|
|
200
222
|
!deps.isReading()) {
|
|
201
223
|
e.preventDefault();
|
|
202
224
|
deps.setSnapTarget(null);
|
|
203
|
-
|
|
204
|
-
// This branch owns only the fact that the engine drops the key beside a widget; which
|
|
205
|
-
// side of an unpainted run the byte belongs on is still the seat's answer.
|
|
206
|
-
const seatedAt = typingSeatAt(el, caretOffset, typed)?.offset ?? caretOffset;
|
|
207
|
-
const newRaw = node.raw.slice(0, seatedAt) + typed + node.raw.slice(seatedAt);
|
|
208
|
-
const postEdit = seatedAt + typed.length;
|
|
209
|
-
void deps.blockEdit.updateBlockContent(deps.index, newRaw, caretOffset, postEdit);
|
|
210
|
-
deps.setPendingCursor(postEdit, 'widget', newRaw);
|
|
225
|
+
writeTypedByte(el, caretOffset, e.key, 'widget');
|
|
211
226
|
return true;
|
|
212
227
|
}
|
|
213
228
|
return false;
|
|
@@ -254,8 +269,8 @@ export function createEdgePolicyDispatch(deps) {
|
|
|
254
269
|
// Second press: the native selection already wraps a replace island, so delete its
|
|
255
270
|
// whole hidden range through the CST as one undo entry.
|
|
256
271
|
if (isDestructive) {
|
|
257
|
-
const selection =
|
|
258
|
-
if (selection
|
|
272
|
+
const selection = heldRange();
|
|
273
|
+
if (selection) {
|
|
259
274
|
const selected = islands.find((i) => islandPolicy(i).onEdge === 'select' &&
|
|
260
275
|
i.start === selection.start &&
|
|
261
276
|
i.end === selection.end);
|
|
@@ -312,7 +327,7 @@ export function createEdgePolicyDispatch(deps) {
|
|
|
312
327
|
// branch in e2e, so it is unit-pinned instead.
|
|
313
328
|
if (isTyping && !caretIsInTextContent(el, window.getSelection())) {
|
|
314
329
|
e.preventDefault();
|
|
315
|
-
|
|
330
|
+
writeTypedByte(el, caretOffset, e.key, 'island');
|
|
316
331
|
return true;
|
|
317
332
|
}
|
|
318
333
|
return false;
|
|
@@ -337,11 +352,11 @@ export function createEdgePolicyDispatch(deps) {
|
|
|
337
352
|
if (!touchesAmbient)
|
|
338
353
|
return false;
|
|
339
354
|
e.preventDefault();
|
|
340
|
-
const range =
|
|
341
|
-
if (range
|
|
355
|
+
const range = heldRange();
|
|
356
|
+
if (range) {
|
|
342
357
|
// Consumed at KEYDOWN, so no `beforeinput` carries this range to the shared seam: the arm
|
|
343
358
|
// asks it here, or a literal splice prints the runs the cut stranded (live-mode.md § 4.5).
|
|
344
|
-
const edit =
|
|
359
|
+
const edit = replaceRangeRaw(deps.node, range, '', joinSeamMode(el), deps.linkRef, deps.getAmbientPrefix?.() ?? '');
|
|
345
360
|
void deps.blockEdit.updateBlockContent(deps.index, edit.raw, range.start, edit.caret);
|
|
346
361
|
deps.setPendingCursor(edit.caret, 'ambient-delete');
|
|
347
362
|
}
|
|
@@ -59,10 +59,10 @@ export declare function resolveSelectionEdit(node: NodeView, selection: {
|
|
|
59
59
|
start: number;
|
|
60
60
|
end: number;
|
|
61
61
|
}, typed: string, presentationMode: PresentationMode | undefined, linkRef: InlineResolverRef | undefined, ambientPrefix?: string): SelectionEdit | null;
|
|
62
|
-
/** The
|
|
63
|
-
*
|
|
64
|
-
* home, so no arm
|
|
65
|
-
export declare function
|
|
62
|
+
/** The rewrite of `[start, end)` to `typed` a gesture the surface consumed installs, where a
|
|
63
|
+
* delete is the empty one. The seam's cleaned bytes where it has them, else the literal display
|
|
64
|
+
* splice with the caret past the insert: the fallback's one home, so no arm can drift. */
|
|
65
|
+
export declare function replaceRangeRaw(node: NodeView, range: {
|
|
66
66
|
start: number;
|
|
67
67
|
end: number;
|
|
68
|
-
}, presentationMode: PresentationMode | undefined, linkRef: InlineResolverRef | undefined, ambientPrefix: string): SelectionEdit;
|
|
68
|
+
}, typed: string, presentationMode: PresentationMode | undefined, linkRef: InlineResolverRef | undefined, ambientPrefix: string): SelectionEdit;
|
|
@@ -102,17 +102,20 @@ export function resolveSelectionEdit(node, selection, typed, presentationMode, l
|
|
|
102
102
|
caret: joined.seam + typed.length
|
|
103
103
|
};
|
|
104
104
|
}
|
|
105
|
-
/** The
|
|
106
|
-
*
|
|
107
|
-
* home, so no arm
|
|
108
|
-
export function
|
|
109
|
-
const cleaned = resolveSelectionEdit(node, range,
|
|
105
|
+
/** The rewrite of `[start, end)` to `typed` a gesture the surface consumed installs, where a
|
|
106
|
+
* delete is the empty one. The seam's cleaned bytes where it has them, else the literal display
|
|
107
|
+
* splice with the caret past the insert: the fallback's one home, so no arm can drift. */
|
|
108
|
+
export function replaceRangeRaw(node, range, typed, presentationMode, linkRef, ambientPrefix) {
|
|
109
|
+
const cleaned = resolveSelectionEdit(node, range, typed, presentationMode, linkRef, ambientPrefix);
|
|
110
110
|
if (cleaned)
|
|
111
111
|
return cleaned;
|
|
112
112
|
const display = trimTrailingLineEnding(node.raw);
|
|
113
113
|
return {
|
|
114
|
-
raw: display.slice(0, range.start) +
|
|
115
|
-
|
|
114
|
+
raw: display.slice(0, range.start) +
|
|
115
|
+
typed +
|
|
116
|
+
display.slice(range.end) +
|
|
117
|
+
trailingLineEnding(node.raw),
|
|
118
|
+
caret: range.start + typed.length
|
|
116
119
|
};
|
|
117
120
|
}
|
|
118
121
|
// ── Reading the event ────────────────────────────────────────────────────────
|
|
@@ -6,7 +6,7 @@ import { resolvedInlineContent } from '../../../core/inline/inline-cache';
|
|
|
6
6
|
import { isInlineWidget } from '../../../core/inline/inline-widgets';
|
|
7
7
|
import { createClipboardHandlers } from '../editable-surface';
|
|
8
8
|
import { pasteDispatch } from '../../../tree-operations/paste/dispatch';
|
|
9
|
-
import {
|
|
9
|
+
import { replaceRangeRaw } from './live-selection-edit';
|
|
10
10
|
export function createTextClipboard(deps) {
|
|
11
11
|
function getSelectedTextFromRaw() {
|
|
12
12
|
const offsets = deps.cursor.getRawSelection();
|
|
@@ -77,7 +77,7 @@ export function createTextClipboard(deps) {
|
|
|
77
77
|
return;
|
|
78
78
|
// A cut is a delete, so it crosses the same join seam: in live the range can span
|
|
79
79
|
// delimiter runs the reader never saw, and a raw splice would print them.
|
|
80
|
-
const edit =
|
|
80
|
+
const edit = replaceRangeRaw(deps.node, selOffsets, '', deps.getPresentationMode(), deps.linkRef, deps.getAmbientPrefix());
|
|
81
81
|
void deps.blockEdit.updateBlockContent(deps.index, edit.raw, selOffsets.start);
|
|
82
82
|
deps.setPendingCursor(edit.caret);
|
|
83
83
|
},
|
|
@@ -5,15 +5,16 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { tick } from 'svelte';
|
|
7
7
|
import { resolvedInlineContent } from '../../../core/inline/inline-cache';
|
|
8
|
-
import { flattenInlineWidgets, getInlineWidgetEditing, isWidgetActivationClick } from '../../../core/inline/inline-widgets';
|
|
8
|
+
import { flattenInlineWidgets, getInlineWidgetEditing, isCharacterLikeWidget, isWidgetActivationClick } from '../../../core/inline/inline-widgets';
|
|
9
9
|
import { isVerticallyTransparentNode } from '../../../core/inline/transparency';
|
|
10
10
|
import { trimTrailingLineEnding, trailingLineEnding } from '../../../core/lines';
|
|
11
11
|
import { asRawOffset, toClampedRawOffset, toDomTextOffset } from '../../../cursor/coordinate-spaces';
|
|
12
12
|
import { domTextOffsetAtNode, createRangeAtDomTextOffsets, selectionFocusWalkOffset } from '../../../cursor/widget-offset';
|
|
13
13
|
import { createSourceReveal } from '../../../cursor/reveal-source';
|
|
14
|
+
import { nearestWidgetEdgeSeat } from '../../../cursor/widget-edge-snap';
|
|
14
15
|
import { traceRevealOpen, traceRevealFold } from '../../../debug/interaction-trace';
|
|
15
16
|
import { assertInvariant } from '../../../assert';
|
|
16
|
-
import { caretIsInTextContent, hasModifier, isPlainTypingKey } from './click-snap-guard';
|
|
17
|
+
import { caretIsInTextContent, hasModifier, isPlainTypingKey, surfaceHoldsRange } from './click-snap-guard';
|
|
17
18
|
import { findWidgetNodeByStart, findFirstEdgeWidget, findLastEdgeWidget, rawHasNoTextBefore, rawHasNoTextAfter, widgetElByStart } from './widget-adjacency';
|
|
18
19
|
/**
|
|
19
20
|
* The inline-code wash over a revealed source, painted through the CSS Custom Highlight API
|
|
@@ -323,16 +324,27 @@ export function createWidgetInteraction(deps) {
|
|
|
323
324
|
return inline ? { inline } : null;
|
|
324
325
|
}
|
|
325
326
|
/**
|
|
326
|
-
*
|
|
327
|
-
* its delimiters, so typing continues the construct rather
|
|
328
|
-
* span keeps the leading edge
|
|
329
|
-
* past its bracket.
|
|
327
|
+
* Where a click on a rendered widget seats the caret when its kind maps no point: the END of
|
|
328
|
+
* the content a kind names, INSIDE its delimiters, so typing continues the construct rather
|
|
329
|
+
* than escaping it. A kind naming no span keeps the leading edge — guessing an end from its
|
|
330
|
+
* parsed text put a footnote's caret past its bracket.
|
|
330
331
|
*/
|
|
331
332
|
function insideEndOffset(inline) {
|
|
332
333
|
const source = deps.node.raw.slice(inline.start, inline.end);
|
|
333
334
|
const span = getInlineWidgetEditing(inline.kind)?.revealContentSpan?.(source);
|
|
334
335
|
return span && span.end >= 0 && span.end <= source.length ? span.end : 0;
|
|
335
336
|
}
|
|
337
|
+
/** The offset a press names inside a widget's source. Read against the PRE-fold geometry:
|
|
338
|
+
* a fold reflows the line and the point stops meaning anything the moment it runs. */
|
|
339
|
+
function seatFromPoint(el, inline, x, y) {
|
|
340
|
+
const widget = widgetElByStart(el, inline.start);
|
|
341
|
+
const atPoint = getInlineWidgetEditing(inline.kind)?.revealOffsetAtPoint;
|
|
342
|
+
if (!widget || !atPoint)
|
|
343
|
+
return null;
|
|
344
|
+
const source = deps.node.raw.slice(inline.start, inline.end);
|
|
345
|
+
const seat = atPoint(widget, source, x, y);
|
|
346
|
+
return seat === null ? null : Math.max(0, Math.min(seat, source.length));
|
|
347
|
+
}
|
|
336
348
|
function hitTestRevealWidget(el, x, y) {
|
|
337
349
|
const painted = revealWidgetFromDom(el, x, y);
|
|
338
350
|
if (painted)
|
|
@@ -370,6 +382,7 @@ export function createWidgetInteraction(deps) {
|
|
|
370
382
|
const hit = hitTestRevealWidget(el, clickX, clickY);
|
|
371
383
|
if (!hit)
|
|
372
384
|
return;
|
|
385
|
+
const seat = seatFromPoint(el, hit.inline, clickX, clickY);
|
|
373
386
|
let targetStart = hit.inline.start;
|
|
374
387
|
if (revealState) {
|
|
375
388
|
const active = revealState;
|
|
@@ -391,9 +404,7 @@ export function createWidgetInteraction(deps) {
|
|
|
391
404
|
return;
|
|
392
405
|
el.focus();
|
|
393
406
|
revealOpenedByLastClick = true;
|
|
394
|
-
|
|
395
|
-
// than guessing: the caret sits inside the delimiters, ready to type.
|
|
396
|
-
void startReveal(target, target.start, insideEndOffset(target));
|
|
407
|
+
void startReveal(target, target.start, seat ?? insideEndOffset(target));
|
|
397
408
|
}
|
|
398
409
|
/**
|
|
399
410
|
* Take the whole revealed token; declines unless the double-click landed on it. Keyed off the
|
|
@@ -645,24 +656,35 @@ export function createWidgetInteraction(deps) {
|
|
|
645
656
|
// text and the browser's word rule takes `[` or `$` as a word of its own.
|
|
646
657
|
if (doubleClick && revealOpenedByLastClick && selectRevealedSource(clickX, clickY))
|
|
647
658
|
return;
|
|
648
|
-
//
|
|
649
|
-
|
|
659
|
+
// The snap below seats a CARET, so it stands down for a range this surface paints, which it
|
|
660
|
+
// would collapse; the rule `clampOutOfAmbient` already carries.
|
|
661
|
+
const live = window.getSelection();
|
|
662
|
+
if (surfaceHoldsRange(el, live))
|
|
650
663
|
return;
|
|
664
|
+
const seat = nearestWidgetEdgeSeat(measuredWidgets(el), clickX, clickY);
|
|
665
|
+
if (seat === null)
|
|
666
|
+
return;
|
|
667
|
+
// A press BESIDE an island stands down for a visible caret; a press ON one cannot, since the
|
|
668
|
+
// engine answers that hit test with a position in the neighbouring text.
|
|
669
|
+
if (!seat.inside && caretIsInTextContent(el, live))
|
|
670
|
+
return;
|
|
671
|
+
el.focus();
|
|
672
|
+
deps.cursor.setRaw(asRawOffset(seat.offset));
|
|
673
|
+
// `setRaw`'s walker may have landed in a trailing text node, where native renders.
|
|
674
|
+
if (!caretIsInTextContent(el, window.getSelection()))
|
|
675
|
+
deps.setSnapTarget(seat.offset);
|
|
676
|
+
}
|
|
677
|
+
function* measuredWidgets(el) {
|
|
651
678
|
for (const inline of widgetsOf()) {
|
|
652
679
|
const widget = widgetElByStart(el, inline.start);
|
|
653
|
-
if (
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
deps.cursor.setRaw(asRawOffset(snapTo));
|
|
661
|
-
// `setRaw`'s walker may have landed in a trailing text node, where native renders.
|
|
662
|
-
if (!caretIsInTextContent(el, window.getSelection())) {
|
|
663
|
-
deps.setSnapTarget(snapTo);
|
|
680
|
+
if (widget) {
|
|
681
|
+
yield {
|
|
682
|
+
start: inline.start,
|
|
683
|
+
end: inline.end,
|
|
684
|
+
rect: widget.getBoundingClientRect(),
|
|
685
|
+
seatsInside: isCharacterLikeWidget(inline.kind)
|
|
686
|
+
};
|
|
664
687
|
}
|
|
665
|
-
return;
|
|
666
688
|
}
|
|
667
689
|
}
|
|
668
690
|
function widgetExtensionTarget(key) {
|
|
@@ -16,6 +16,12 @@ export declare function isImageOnlyParagraph(node: NodeView): boolean;
|
|
|
16
16
|
* caller still gates reading mode, which shows no affordances at all.
|
|
17
17
|
*/
|
|
18
18
|
export declare function showsDragHandle(node: NodeView, handlesEnabled: boolean): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* A list item's grip reorders it among its SIBLINGS and nowhere else: the drag is scoped to
|
|
21
|
+
* the enclosing list, so a lone item has nothing to trade places with and its grip is a
|
|
22
|
+
* promise the drop can't keep. It reappears the moment a second item joins the list.
|
|
23
|
+
*/
|
|
24
|
+
export declare function showsListItemDragHandle(itemCount: number, handlesEnabled: boolean): boolean;
|
|
19
25
|
/**
|
|
20
26
|
* Vertical centre of the grip. A one-line block centres on its row. A taller one takes the
|
|
21
27
|
* first line-height of its OWN box — not its first line of text, since a card pads above that
|
|
@@ -41,6 +41,14 @@ export function showsDragHandle(node, handlesEnabled) {
|
|
|
41
41
|
return true;
|
|
42
42
|
return handlesEnabled && !GRIPLESS_KINDS.has(node.kind);
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* A list item's grip reorders it among its SIBLINGS and nowhere else: the drag is scoped to
|
|
46
|
+
* the enclosing list, so a lone item has nothing to trade places with and its grip is a
|
|
47
|
+
* promise the drop can't keep. It reappears the moment a second item joins the list.
|
|
48
|
+
*/
|
|
49
|
+
export function showsListItemDragHandle(itemCount, handlesEnabled) {
|
|
50
|
+
return handlesEnabled && itemCount > 1;
|
|
51
|
+
}
|
|
44
52
|
/** A box within this many line-heights holds one row of content, margins included. */
|
|
45
53
|
const SINGLE_LINE = 1.5;
|
|
46
54
|
/**
|
|
@@ -26,7 +26,7 @@ export interface TypeScaleProbeDeps {
|
|
|
26
26
|
export declare function installTypeScaleProbe(el: HTMLElement, deps: TypeScaleProbeDeps): () => void;
|
|
27
27
|
export interface HeaderSlotCompensationDeps {
|
|
28
28
|
el: HTMLElement;
|
|
29
|
-
port: Pick<Scrollport, 'scrollTop' | '
|
|
29
|
+
port: Pick<Scrollport, 'scrollTop' | 'scrollBy'>;
|
|
30
30
|
ownsScrollCorrection(): boolean;
|
|
31
31
|
revealHoldsScroll(): boolean;
|
|
32
32
|
}
|
|
@@ -84,7 +84,7 @@ export function installHeaderSlotCompensation(deps) {
|
|
|
84
84
|
if (delta === 0 || !deps.ownsScrollCorrection() || port.scrollTop() === 0)
|
|
85
85
|
return;
|
|
86
86
|
if (!deps.revealHoldsScroll())
|
|
87
|
-
port.
|
|
87
|
+
port.scrollBy(delta);
|
|
88
88
|
});
|
|
89
89
|
observer.observe(el);
|
|
90
90
|
return () => observer.disconnect();
|
|
@@ -39,10 +39,3 @@ export declare function installEditorBlurAnnouncer(deps: {
|
|
|
39
39
|
root: HTMLElement;
|
|
40
40
|
emit: () => void;
|
|
41
41
|
}): () => void;
|
|
42
|
-
/**
|
|
43
|
-
* Windows Chromium's double-click takes the space after the word, so the word is selected on
|
|
44
|
-
* the SECOND press, trimmed, with the native selection suppressed; the dblclick trim is the
|
|
45
|
-
* fallback. A press on an inline widget is that widget's own gesture (a footnote's double-click
|
|
46
|
-
* takes its whole token), so the root leaves it to the widget.
|
|
47
|
-
*/
|
|
48
|
-
export declare function installDoubleClickWordSelect(root: HTMLElement): () => void;
|
|
@@ -5,8 +5,6 @@
|
|
|
5
5
|
* call, returning the teardown. `onRoot`/`removeAll` capture that add/remove pair once.
|
|
6
6
|
*/
|
|
7
7
|
import { tick } from 'svelte';
|
|
8
|
-
import { isEditableEventTarget } from '../editor-actions/whole-block-focus-surface';
|
|
9
|
-
import { selectWordAtPoint, trimDoubleClickSelection } from '../selection/double-click-trim';
|
|
10
8
|
// ── Listener plumbing ───────────────────────────────────────────────
|
|
11
9
|
// Typed off `Event`, not the per-target event maps: those names are type-only,
|
|
12
10
|
// and `target` ranges over every EventTarget the root effects listen on.
|
|
@@ -91,23 +89,3 @@ export function installEditorBlurAnnouncer(deps) {
|
|
|
91
89
|
};
|
|
92
90
|
return onRoot(deps.root, 'focusout', handler);
|
|
93
91
|
}
|
|
94
|
-
/**
|
|
95
|
-
* Windows Chromium's double-click takes the space after the word, so the word is selected on
|
|
96
|
-
* the SECOND press, trimmed, with the native selection suppressed; the dblclick trim is the
|
|
97
|
-
* fallback. A press on an inline widget is that widget's own gesture (a footnote's double-click
|
|
98
|
-
* takes its whole token), so the root leaves it to the widget.
|
|
99
|
-
*/
|
|
100
|
-
export function installDoubleClickWordSelect(root) {
|
|
101
|
-
const onSecondPress = (e) => {
|
|
102
|
-
if (e.detail !== 2 || e.button !== 0 || e.shiftKey || e.ctrlKey || e.metaKey)
|
|
103
|
-
return;
|
|
104
|
-
if (!isEditableEventTarget(e.target) || pressesInlineWidget(e.target))
|
|
105
|
-
return;
|
|
106
|
-
if (selectWordAtPoint(root.ownerDocument, e.clientX, e.clientY))
|
|
107
|
-
e.preventDefault();
|
|
108
|
-
};
|
|
109
|
-
return removeAll(onRoot(root, 'mousedown', onSecondPress), onRoot(root, 'dblclick', () => trimDoubleClickSelection(root.ownerDocument)));
|
|
110
|
-
}
|
|
111
|
-
function pressesInlineWidget(target) {
|
|
112
|
-
return target instanceof Element && target.closest('[data-inline-widget]') !== null;
|
|
113
|
-
}
|
|
@@ -187,24 +187,32 @@
|
|
|
187
187
|
{/if}
|
|
188
188
|
|
|
189
189
|
<style>
|
|
190
|
-
/*
|
|
191
|
-
|
|
190
|
+
/* A neutral grip sitting inside the picture's right edge: the selection ring owns the edge
|
|
191
|
+
itself, and the accent is the ring's, so a second accent shape beside it reads as decoration
|
|
192
|
+
rather than as a control. White over a hairline rim so it carries on a dark picture and a
|
|
193
|
+
pale one alike. The hit strip is wider than the paint, so on a picture too narrow to hold
|
|
194
|
+
both the strip and a clickable middle (an icon-sized image) the grip steps back outside
|
|
195
|
+
the edge, where it stood before. */
|
|
192
196
|
.md-resize-handle {
|
|
193
197
|
position: absolute;
|
|
194
|
-
right: -3px;
|
|
198
|
+
right: clamp(-3px, calc(50% - 20px), 7px);
|
|
195
199
|
top: 50%;
|
|
196
|
-
width:
|
|
197
|
-
height:
|
|
198
|
-
max-height:
|
|
200
|
+
width: 4px;
|
|
201
|
+
height: 36px;
|
|
202
|
+
max-height: 40%;
|
|
199
203
|
transform: translateY(-50%);
|
|
200
|
-
border-radius:
|
|
201
|
-
background:
|
|
204
|
+
border-radius: 999px;
|
|
205
|
+
background: rgba(255, 255, 255, 0.92);
|
|
202
206
|
box-shadow:
|
|
203
|
-
0 0 0
|
|
204
|
-
0 1px 3px rgba(0, 0, 0, 0.
|
|
207
|
+
0 0 0 0.5px rgba(0, 0, 0, 0.35),
|
|
208
|
+
0 1px 3px rgba(0, 0, 0, 0.35);
|
|
209
|
+
opacity: 0.85;
|
|
205
210
|
z-index: 10;
|
|
206
211
|
cursor: ew-resize;
|
|
207
212
|
}
|
|
213
|
+
.md-resize-handle:hover {
|
|
214
|
+
opacity: 1;
|
|
215
|
+
}
|
|
208
216
|
.md-resize-handle::before {
|
|
209
217
|
content: '';
|
|
210
218
|
position: absolute;
|