edytor 4.0.0 → 6.0.0
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 +2 -2
- package/dist/block/block.svelte.js +6 -21
- package/dist/block/inlineBlock.svelte.js +4 -6
- package/dist/clipboard/moveText.js +6 -8
- package/dist/cloudflare/DocumentRoom.d.ts +10 -3
- package/dist/cloudflare/index.d.ts +1 -1
- package/dist/cloudflare/room/access.d.ts +8 -3
- package/dist/cloudflare/room/access.js +44 -20
- package/dist/cloudflare/room/admission.js +28 -5
- package/dist/cloudflare/room/comments.js +28 -1
- package/dist/cloudflare/room/context.d.ts +106 -109
- package/dist/cloudflare/room/context.js +10 -3
- package/dist/cloudflare/room/forged.d.ts +14 -0
- package/dist/cloudflare/room/forged.js +30 -2
- package/dist/cloudflare/room/frames.d.ts +4 -0
- package/dist/cloudflare/room/frames.js +5 -1
- package/dist/cloudflare/room/purge.js +2 -1
- package/dist/cloudflare/room/replicas.d.ts +4 -2
- package/dist/cloudflare/room/replicas.js +8 -3
- package/dist/cloudflare/room/suggest.d.ts +7 -0
- package/dist/cloudflare/room/suggest.js +143 -0
- package/dist/cloudflare/routeDocumentSocket.d.ts +10 -1
- package/dist/cloudflare/routeDocumentSocket.js +5 -3
- package/dist/components/Content.svelte +9 -1
- package/dist/crdt/anchors.js +3 -2
- package/dist/crdt/attribution/attribution.d.ts +3 -0
- package/dist/crdt/attribution/attribution.js +3 -3
- package/dist/crdt/doc/funnel.d.ts +1 -1
- package/dist/crdt/doc/funnel.js +5 -2
- package/dist/crdt/doc/gate.d.ts +1 -1
- package/dist/crdt/doc/history.d.ts +6 -0
- package/dist/crdt/doc/history.js +79 -59
- package/dist/crdt/doc/ops.d.ts +81 -0
- package/dist/crdt/doc/ops.js +1898 -0
- package/dist/crdt/doc/reads.d.ts +2 -2
- package/dist/crdt/doc/reads.js +18 -28
- package/dist/crdt/doc/suggest.d.ts +167 -0
- package/dist/crdt/doc/suggest.js +579 -0
- package/dist/crdt/document.d.ts +10 -1
- package/dist/crdt/document.js +19 -11
- package/dist/crdt/edytor-doc.d.ts +294 -272
- package/dist/crdt/edytor-doc.js +123 -108
- package/dist/crdt/engine-api.d.ts +8 -2
- package/dist/crdt/engine.d.ts +2 -1
- package/dist/crdt/engine.js +3 -1
- package/dist/crdt/flow.d.ts +5 -47
- package/dist/crdt/flow.js +3 -269
- package/dist/crdt/index.d.ts +102 -108
- package/dist/crdt/index.js +3 -0
- package/dist/crdt/operations.d.ts +13 -4
- package/dist/crdt/parts.d.ts +43 -0
- package/dist/crdt/parts.js +44 -0
- package/dist/crdt/placement/display.d.ts +1 -1
- package/dist/crdt/placement/display.js +3 -3
- package/dist/crdt/placement/keys.d.ts +5 -0
- package/dist/crdt/placement/keys.js +5 -0
- package/dist/crdt/placement/resolve.d.ts +1 -1
- package/dist/crdt/placement/resolve.js +2 -1
- package/dist/crdt/protocol.d.ts +1 -1
- package/dist/crdt/protocol.js +1 -1
- package/dist/crdt/protocols/auth.d.ts +11 -1
- package/dist/crdt/protocols/auth.js +17 -1
- package/dist/crdt/protocols/awareness.js +46 -81
- package/dist/crdt/protocols/comments.d.ts +16 -1
- package/dist/crdt/protocols/comments.js +9 -1
- package/dist/crdt/providers/index.d.ts +12 -22
- package/dist/crdt/providers/index.js +3 -1
- package/dist/crdt/providers/indexeddb.d.ts +6 -10
- package/dist/crdt/providers/indexeddb.js +10 -16
- package/dist/crdt/providers/room.d.ts +11 -0
- package/dist/crdt/providers/room.js +26 -12
- package/dist/crdt/providers/websocket.d.ts +8 -12
- package/dist/crdt/providers/websocket.js +17 -19
- package/dist/crdt/purge.d.ts +7 -1
- package/dist/crdt/purge.js +32 -2
- package/dist/crdt/rangeDelete.d.ts +2 -47
- package/dist/crdt/rangeDelete.js +1 -169
- package/dist/crdt/restore.d.ts +2 -1
- package/dist/crdt/restore.js +9 -0
- package/dist/crdt/schema.d.ts +9 -1
- package/dist/crdt/schema.js +13 -3
- package/dist/crdt/structs.d.ts +2 -0
- package/dist/crdt/structs.js +2 -0
- package/dist/crdt/text/index/build.d.ts +125 -0
- package/dist/crdt/text/index/build.js +2895 -0
- package/dist/crdt/text/index/shared.d.ts +2 -6
- package/dist/crdt/text/index/shared.js +19 -19
- package/dist/crdt/text/model.d.ts +10 -0
- package/dist/crdt/text/model.js +10 -4
- package/dist/crdt/text/runs.d.ts +43 -42
- package/dist/crdt/text/runs.js +2 -165
- package/dist/crdt/vendor/yjs/UPSTREAM.md +83 -0
- package/dist/crdt/vendor/yjs/dts/index.d.ts +1 -1
- package/dist/crdt/vendor/yjs/dts/structs/Item.d.ts +6 -3
- package/dist/crdt/vendor/yjs/dts/ynode.d.ts +13 -2
- package/dist/crdt/vendor/yjs/src/index.js +1 -1
- package/dist/crdt/vendor/yjs/src/structs/Item.js +44 -5
- package/dist/crdt/vendor/yjs/src/ynode.js +29 -7
- package/dist/edytor.svelte.d.ts +1 -1
- package/dist/edytor.svelte.js +100 -75
- package/dist/events/beforeInputCommands.d.ts +2 -2
- package/dist/events/domTextOffset.d.ts +2 -0
- package/dist/events/domTextOffset.js +8 -0
- package/dist/events/nativeInteractiveControl.d.ts +11 -1
- package/dist/events/nativeInteractiveControl.js +41 -1
- package/dist/events/onBeforeInput.js +3 -3
- package/dist/events/onFocus.js +9 -11
- package/dist/export/forms.d.ts +75 -75
- package/dist/index.d.ts +4 -2
- package/dist/index.js +1 -1
- package/dist/labels.d.ts +8 -0
- package/dist/labels.js +6 -1
- package/dist/plugins/comments/CommentsController.svelte.d.ts +5 -1
- package/dist/plugins/comments/CommentsController.svelte.js +14 -4
- package/dist/plugins/find/search.js +2 -2
- package/dist/plugins/index.d.ts +1 -0
- package/dist/plugins/index.js +1 -0
- package/dist/plugins/richtext/RichTextPlugin.svelte +63 -61
- package/dist/plugins/richtext/richTextOperations.js +26 -32
- package/dist/plugins/suggestions/SuggestionBars.svelte +40 -23
- package/dist/plugins/suggestions/SuggestionBars.svelte.d.ts +3 -1
- package/dist/plugins/suggestions/SuggestionRuns.svelte +77 -0
- package/dist/plugins/suggestions/SuggestionRuns.svelte.d.ts +9 -0
- package/dist/plugins/suggestions/shared.svelte.d.ts +58 -0
- package/dist/plugins/suggestions/shared.svelte.js +421 -0
- package/dist/plugins/suggestions/suggestionsPlugin.d.ts +15 -4
- package/dist/selection/domSelection.d.ts +2 -0
- package/dist/selection/domSelection.js +6 -2
- package/dist/selection/pointer.svelte.js +75 -48
- package/dist/selection/replaceSelection.js +2 -6
- package/dist/selection/selection.svelte.d.ts +3 -3
- package/dist/selection/selection.svelte.js +39 -32
- package/dist/selection/selection.utils.d.ts +14 -0
- package/dist/selection/selection.utils.js +33 -11
- package/dist/session/commands.js +21 -11
- package/dist/session/composition.svelte.d.ts +5 -3
- package/dist/session/composition.svelte.js +21 -17
- package/dist/session/handles.d.ts +4 -17
- package/dist/session/handles.js +14 -44
- package/dist/session/navigation.js +6 -9
- package/dist/session/selection.d.ts +11 -5
- package/dist/session/selection.js +14 -48
- package/dist/session/suggestions.svelte.d.ts +38 -9
- package/dist/session/suggestions.svelte.js +37 -18
- package/dist/session/virtual.d.ts +1 -1
- package/dist/surface/attributes.d.ts +5 -0
- package/dist/surface/attributes.js +2 -2
- package/dist/surface/cells.d.ts +2 -26
- package/dist/surface/cells.js +2 -33
- package/dist/surface/observer.svelte.d.ts +6 -1
- package/dist/surface/observer.svelte.js +143 -111
- package/dist/surface/pin.svelte.d.ts +9 -9
- package/dist/surface/pin.svelte.js +15 -22
- package/dist/surface/projector.svelte.js +10 -16
- package/dist/text/text.svelte.js +14 -19
- package/dist/themes/notion.css +29 -0
- package/dist/utils.d.ts +5 -0
- package/dist/utils.js +7 -0
- package/package.json +3 -1
- package/dist/crdt/doc/capability.d.ts +0 -56
- package/dist/crdt/doc/capability.js +0 -211
- package/dist/crdt/doc/content.d.ts +0 -16
- package/dist/crdt/doc/content.js +0 -77
- package/dist/crdt/doc/delete.d.ts +0 -15
- package/dist/crdt/doc/delete.js +0 -84
- package/dist/crdt/doc/layout.d.ts +0 -9
- package/dist/crdt/doc/layout.js +0 -178
- package/dist/crdt/doc/meta.d.ts +0 -19
- package/dist/crdt/doc/meta.js +0 -199
- package/dist/crdt/doc/moves.d.ts +0 -21
- package/dist/crdt/doc/moves.js +0 -274
- package/dist/crdt/doc/split-merge.d.ts +0 -10
- package/dist/crdt/doc/split-merge.js +0 -179
- package/dist/crdt/doc/steps.d.ts +0 -65
- package/dist/crdt/doc/steps.js +0 -311
- package/dist/crdt/text/index/cache.d.ts +0 -16
- package/dist/crdt/text/index/cache.js +0 -175
- package/dist/crdt/text/index/claims.d.ts +0 -18
- package/dist/crdt/text/index/claims.js +0 -225
- package/dist/crdt/text/index/fold.d.ts +0 -21
- package/dist/crdt/text/index/fold.js +0 -656
- package/dist/crdt/text/index/layout.d.ts +0 -24
- package/dist/crdt/text/index/layout.js +0 -271
- package/dist/crdt/text/index/placement.d.ts +0 -47
- package/dist/crdt/text/index/placement.js +0 -741
- package/dist/crdt/text/index/records.d.ts +0 -13
- package/dist/crdt/text/index/records.js +0 -139
- package/dist/crdt/text/index/report.d.ts +0 -18
- package/dist/crdt/text/index/report.js +0 -337
- package/dist/crdt/text/index/state.d.ts +0 -197
- package/dist/crdt/text/index/state.js +0 -116
- package/dist/crdt/text/index/streams.d.ts +0 -25
- package/dist/crdt/text/index/streams.js +0 -248
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
ContentType,
|
|
13
13
|
ContentHold, // YP20
|
|
14
14
|
ContentMove, // YP22
|
|
15
|
+
hideIds, // YP32
|
|
15
16
|
readFormatValue, // YP25
|
|
16
17
|
YXmlFragmentRefID,
|
|
17
18
|
YXmlElementRefID,
|
|
@@ -327,19 +328,24 @@ const holdHelper = (transaction, list, ids) => {
|
|
|
327
328
|
|
|
328
329
|
/**
|
|
329
330
|
* With `held`, the live units are not deleted: their ids go to `held`
|
|
330
|
-
* (`client, clock, len`), except those `fresh` names (YP20).
|
|
331
|
+
* (`client, clock, len`), except those `fresh` names (YP20). YP32: with
|
|
332
|
+
* `sort` (a suggestion's delete), `sort`'s `1` goes to `held`, `2` to
|
|
333
|
+
* `proposed` and `hidden`, `3` to `hidden`.
|
|
331
334
|
*
|
|
332
335
|
* @param {Transaction} transaction
|
|
333
336
|
* @param {ItemTextListPosition} currPos
|
|
334
337
|
* @param {number} length
|
|
335
338
|
* @param {Array<number>|null} [held]
|
|
336
339
|
* @param {import('./utils/ids.js').IdSet|null} [fresh]
|
|
340
|
+
* @param {((client: number, clock: number) => number)|null} [sort] YP32: a suggestion's delete sorts each unit
|
|
341
|
+
* @param {Array<number>|null} [proposed] YP32: the units it proposes
|
|
342
|
+
* @param {Array<number>|null} [hidden] YP32: the units it hides until `unhide`
|
|
337
343
|
* @return {ItemTextListPosition}
|
|
338
344
|
*
|
|
339
345
|
* @private
|
|
340
346
|
* @function
|
|
341
347
|
*/
|
|
342
|
-
export const deleteText = (transaction, currPos, length, held = null, fresh = null) => {
|
|
348
|
+
export const deleteText = (transaction, currPos, length, held = null, fresh = null, sort = null, proposed = null, hidden = null) => {
|
|
343
349
|
const startLength = length
|
|
344
350
|
let kept = 0 // YP20
|
|
345
351
|
while (length > 0 && currPos.right !== null) {
|
|
@@ -347,15 +353,19 @@ export const deleteText = (transaction, currPos, length, held = null, fresh = nu
|
|
|
347
353
|
if (!item.deleted && item.countable) {
|
|
348
354
|
const { client, clock } = item.id
|
|
349
355
|
// YP20: a held run ends where the open step's inserts start or end
|
|
350
|
-
|
|
356
|
+
let n = fresh === null ? math.min(length, item.length) : runEnd(fresh, client, clock, clock + math.min(length, item.length)) - clock
|
|
357
|
+
// YP32: a suggestion's delete sorts each unit — held (`1`), proposed (`2`) or stepped over (`3`)
|
|
358
|
+
const kind = held === null || (fresh !== null && fresh.has(client, clock)) ? 0 : sort === null ? 1 : sort(client, clock)
|
|
359
|
+
if (kind !== 0 && sort !== null) for (let k = 1; k < n; k++) if (sort(client, clock + k) !== kind) n = k
|
|
351
360
|
if (n < item.length) {
|
|
352
361
|
getItemCleanStart(transaction, createID(client, clock + n))
|
|
353
362
|
}
|
|
354
363
|
length -= item.length
|
|
355
|
-
if (
|
|
364
|
+
if (kind === 0) {
|
|
356
365
|
item.delete(transaction)
|
|
357
366
|
} else {
|
|
358
|
-
|
|
367
|
+
if (kind === 2) /** @type {Array<number>} */ (proposed).push(client, clock, item.length)
|
|
368
|
+
;/** @type {Array<number>} */ (kind === 1 ? held : hidden).push(client, clock, item.length)
|
|
359
369
|
kept += item.length
|
|
360
370
|
}
|
|
361
371
|
}
|
|
@@ -993,12 +1003,20 @@ export class YNode {
|
|
|
993
1003
|
* which an undo of the record takes back), except those `fresh` names (the
|
|
994
1004
|
* open undo step's inserts: removed, so that step's undo changes nothing).
|
|
995
1005
|
*
|
|
1006
|
+
* YP32: with `propose` (a suggestion's delete) each unit is sorted: held
|
|
1007
|
+
* by the record in `into` (`1`: its author's own), proposed (`2`: an id
|
|
1008
|
+
* list appended to `propose.into`, a suggestion's record, holding nothing)
|
|
1009
|
+
* or stepped over (`3`: already proposed); proposed and stepped-over units
|
|
1010
|
+
* read gone until `unhide`, so the rest of the plan reads the offsets it
|
|
1011
|
+
* was prepared with.
|
|
1012
|
+
*
|
|
996
1013
|
* @param {number} index Index at which to start deleting elements
|
|
997
1014
|
* @param {number} length The number of elements to remove. Defaults to 1.
|
|
998
1015
|
* @param {YNode|null} [into]
|
|
999
1016
|
* @param {import('./utils/ids.js').IdSet|null} [fresh]
|
|
1017
|
+
* @param {{ into: YNode, sort: (client: number, clock: number) => number }|null} [propose]
|
|
1000
1018
|
*/
|
|
1001
|
-
delete (index, length = 1, into = null, fresh = null) {
|
|
1019
|
+
delete (index, length = 1, into = null, fresh = null, propose = null) {
|
|
1002
1020
|
if (!(length > 0)) return
|
|
1003
1021
|
if (this.doc == null) {
|
|
1004
1022
|
const runs = prelimOf(this).runs
|
|
@@ -1015,13 +1033,17 @@ export class YNode {
|
|
|
1015
1033
|
transact(this.doc, transaction => {
|
|
1016
1034
|
const currPos = cursorAt(transaction, this, index)
|
|
1017
1035
|
const held = into === null ? null : [] // YP20
|
|
1018
|
-
|
|
1036
|
+
const [proposed, hidden] = [[], []] // YP32
|
|
1037
|
+
deleteText(transaction, currPos, length, held, fresh, propose && propose.sort, proposed, hidden)
|
|
1019
1038
|
if (held !== null && held.length > 0) {
|
|
1020
1039
|
// YP20: `deleteText` moved the markers as for a delete; the hold keeps them
|
|
1021
1040
|
const ms = this._searchMarker === null ? null : this._searchMarker.slice()
|
|
1022
1041
|
holdHelper(transaction, /** @type {YNode} */ (into), held)
|
|
1023
1042
|
if (ms !== null && this._searchMarker !== null) this._searchMarker.splice(0, this._searchMarker.length, ...ms)
|
|
1024
1043
|
}
|
|
1044
|
+
// YP32: the proposal, then what it hides until `unhide`
|
|
1045
|
+
if (proposed.length > 0) holdHelper(transaction, /** @type {any} */ (propose).into, proposed)
|
|
1046
|
+
if (hidden.length > 0) hideIds(transaction, hidden)
|
|
1025
1047
|
plantMarker(this, currPos)
|
|
1026
1048
|
})
|
|
1027
1049
|
}
|
package/dist/edytor.svelte.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { Handles } from './session/handles.js';
|
|
|
11
11
|
import { type Awareness, type DocChange, type DocumentActor, type EdytorDocument, type OrderPolicy, type YDoc } from './crdt/index.js';
|
|
12
12
|
import { PresenceWriter, type PresenceOptions } from './collaboration/awarenessSelection.js';
|
|
13
13
|
import { type BlockMoveRequest } from './session/moves.js';
|
|
14
|
-
import {
|
|
14
|
+
import type { DocumentWrites } from './crdt/operations.js';
|
|
15
15
|
import type { Plugin, BlockDefinition, MarkDefinition, InlineBlockDefinition, EditorCommand, Placeholder } from './plugins.js';
|
|
16
16
|
import { type HotKey, type HotKeyCombination } from './session/keymap.js';
|
|
17
17
|
import { Dispatcher } from './session/commands.js';
|
package/dist/edytor.svelte.js
CHANGED
|
@@ -38,7 +38,8 @@ import { attachDocument, SemanticConflictError } from './crdt/index.js';
|
|
|
38
38
|
import { whenDocumentReady } from './collaboration/documentSync.js';
|
|
39
39
|
import { mintPresenceKey, PresenceWriter } from './collaboration/awarenessSelection.js';
|
|
40
40
|
import { canMove, move } from './session/moves.js';
|
|
41
|
-
import {
|
|
41
|
+
import { SUGGEST_ROOT } from './crdt/schema.js';
|
|
42
|
+
import { asEngineDoc } from './crdt/structs.js';
|
|
42
43
|
import { on } from 'svelte/events';
|
|
43
44
|
import { Keymap } from './session/keymap.js';
|
|
44
45
|
import { TRANSACTION } from './constants.js';
|
|
@@ -46,7 +47,7 @@ import { Dispatcher } from './session/commands.js';
|
|
|
46
47
|
import { History } from './session/history.js';
|
|
47
48
|
import { DataWrites } from './session/props.js';
|
|
48
49
|
import { kindCatalogue, kindCommand, UNKNOWN_KIND } from './kinds.js';
|
|
49
|
-
import { clearDomSelection, getActiveElement, getDomSelection, getDomSelectionSnapshot } from './selection/domSelection.js';
|
|
50
|
+
import { clearDomSelection, focusIn, getActiveElement, getDomSelection, getDomSelectionSnapshot } from './selection/domSelection.js';
|
|
50
51
|
/** A consumer that is set (a prop or a plugin hook left out is `undefined`). */
|
|
51
52
|
const isListener = (listener) => listener !== undefined;
|
|
52
53
|
/**
|
|
@@ -87,10 +88,6 @@ export class Edytor {
|
|
|
87
88
|
/** The view's handles: one id-only `Block` per live id, texts and atoms by position and id. */
|
|
88
89
|
idToBlock = new Handles(this);
|
|
89
90
|
/** @internal */
|
|
90
|
-
nodeToInlineBlock = new Map();
|
|
91
|
-
/** @internal */
|
|
92
|
-
nodeToText = new Map();
|
|
93
|
-
/** @internal */
|
|
94
91
|
transaction = new TRANSACTION();
|
|
95
92
|
/** @internal The view's keymap: consumer bindings, then the extensions', then the built-in rows. */
|
|
96
93
|
keymap;
|
|
@@ -113,8 +110,8 @@ export class Edytor {
|
|
|
113
110
|
surface: {
|
|
114
111
|
park: (text, offset) => this.projector.park(text, offset),
|
|
115
112
|
flush: () => void this.surface.flush(),
|
|
116
|
-
placed:
|
|
117
|
-
parked:
|
|
113
|
+
placed: this.projector.placed,
|
|
114
|
+
parked: this.projector.parked,
|
|
118
115
|
clear: () => clearDomSelection(this.node),
|
|
119
116
|
pointAt: (node, offset) => textPointAt.call(this.selection, node, offset),
|
|
120
117
|
sameLine: (a, b) => onOneLine(this.selection.textAt(a), this.selection.textAt(b))
|
|
@@ -211,7 +208,7 @@ export class Edytor {
|
|
|
211
208
|
*/
|
|
212
209
|
get facade() {
|
|
213
210
|
return (this.lens ??= virtualLens(this.document.facade, () => this.document.ready, () => this.document.defaultChild(null), (kind) => ({
|
|
214
|
-
|
|
211
|
+
childless: this.definitionOf(kind).void === true,
|
|
215
212
|
rendersContent: this.document.rendersContent(kind)
|
|
216
213
|
})));
|
|
217
214
|
}
|
|
@@ -298,10 +295,6 @@ export class Edytor {
|
|
|
298
295
|
};
|
|
299
296
|
constructor(options) {
|
|
300
297
|
const { readonly, hotkeys, plugins, document, actor, requireHydration, presence, sync, value, onSelectionChange, labels, onChange, onDocChange, allowNesting } = options;
|
|
301
|
-
for (const name of DOCUMENT_WRITES)
|
|
302
|
-
Object.defineProperty(this, name, {
|
|
303
|
-
value: (...args) => this.dispatcher.operate(name, args)
|
|
304
|
-
});
|
|
305
298
|
if (labels)
|
|
306
299
|
this.labels = labels;
|
|
307
300
|
this.allowNesting = allowNesting === true;
|
|
@@ -315,6 +308,11 @@ export class Edytor {
|
|
|
315
308
|
this.document = attachDocument(new Y.Doc(), { actor, requireHydration });
|
|
316
309
|
this.#owned = true;
|
|
317
310
|
}
|
|
311
|
+
// The writes (`DocumentWrites`): every op the document prepares, dispatched.
|
|
312
|
+
for (const name in this.document.facade.prepare)
|
|
313
|
+
Object.defineProperty(this, name, {
|
|
314
|
+
value: (...args) => this.dispatcher.operate(name, args)
|
|
315
|
+
});
|
|
318
316
|
this.readonly = readonly || false;
|
|
319
317
|
this.onChange = onChange;
|
|
320
318
|
this.onDocChange = onDocChange;
|
|
@@ -489,6 +487,18 @@ export class Edytor {
|
|
|
489
487
|
this.cells = createCells(this.facade, this.surface.patched);
|
|
490
488
|
this.root = this.idToBlock.root;
|
|
491
489
|
this.#offCommit = this.facade.onChange(this.#onCommit);
|
|
490
|
+
// Without its plugin, a view reads a document's shared suggestions as plain content.
|
|
491
|
+
if (DEV && !this.suggestions.shared) {
|
|
492
|
+
const warn = () => {
|
|
493
|
+
if (asEngineDoc(this.document.doc).get(SUGGEST_ROOT).attrKeys().next().done)
|
|
494
|
+
return;
|
|
495
|
+
console.warn('[edytor] this document holds shared suggestions: without createSharedSuggestionsPlugin() the view shows their text as plain content and exports it');
|
|
496
|
+
};
|
|
497
|
+
if (this.document.ready)
|
|
498
|
+
warn();
|
|
499
|
+
else
|
|
500
|
+
this.document.onReady(warn);
|
|
501
|
+
}
|
|
492
502
|
// A document that is already empty shows its virtual paragraph with the caret in it.
|
|
493
503
|
this.selection?.restoreDeadSelectionEndpoints();
|
|
494
504
|
};
|
|
@@ -533,11 +543,11 @@ export class Edytor {
|
|
|
533
543
|
if (values.length > 0)
|
|
534
544
|
callEach('[edytor] onChange', values, this.value);
|
|
535
545
|
};
|
|
536
|
-
/** The text each range `replace` suggestion removes when accepted (`Ranges`). */
|
|
546
|
+
/** The text each range `replace` suggestion removes when accepted (`Ranges`); a shared one's are its runs. */
|
|
537
547
|
#replaced = () => {
|
|
538
548
|
void this.suggestions.revision;
|
|
539
|
-
return this.suggestions.list.flatMap(({ at }) => {
|
|
540
|
-
if (!('replace' in at) || Array.isArray(at.replace))
|
|
549
|
+
return this.suggestions.list.flatMap(({ at, shared }) => {
|
|
550
|
+
if (shared || !('replace' in at) || Array.isArray(at.replace))
|
|
541
551
|
return [];
|
|
542
552
|
const { anchor, focus } = at.replace;
|
|
543
553
|
const [start, end] = [anchor, focus].map(this.selection.resolveTextAnchor);
|
|
@@ -580,15 +590,14 @@ export class Edytor {
|
|
|
580
590
|
* affordances long after the gesture ended.
|
|
581
591
|
*/
|
|
582
592
|
#userInputHandlingDepth = 0;
|
|
583
|
-
#withUserInput = (handler,
|
|
593
|
+
#withUserInput = (handler, bumpSerial = true) => {
|
|
584
594
|
return (...args) => {
|
|
585
595
|
// Depth-counted: a synchronous wrapped listener landing inside an
|
|
586
596
|
// awaited handler's hold window must not end the window the outer
|
|
587
597
|
// handler still needs.
|
|
588
598
|
this.#userInputHandlingDepth++;
|
|
589
|
-
if (
|
|
599
|
+
if (bumpSerial)
|
|
590
600
|
this.markUserGesture();
|
|
591
|
-
}
|
|
592
601
|
const clear = () => {
|
|
593
602
|
// A display the window held back gets its pass now.
|
|
594
603
|
if (--this.#userInputHandlingDepth === 0)
|
|
@@ -637,12 +646,13 @@ export class Edytor {
|
|
|
637
646
|
* fires (element already focused).
|
|
638
647
|
*/
|
|
639
648
|
#expectInternalFocusArmed = false;
|
|
640
|
-
/** @internal */
|
|
641
|
-
expectInternalFocus = () => {
|
|
649
|
+
/** @internal Armed, and `element` (the host, an editing host in it) focused without scrolling. */
|
|
650
|
+
expectInternalFocus = (element) => {
|
|
642
651
|
this.#expectInternalFocusArmed = true;
|
|
643
652
|
queueMicrotask(() => {
|
|
644
653
|
this.#expectInternalFocusArmed = false;
|
|
645
654
|
});
|
|
655
|
+
element?.focus({ preventScroll: true });
|
|
646
656
|
};
|
|
647
657
|
/** @internal A `focusin` consumes the armed flag: `true` when the editor focused itself. */
|
|
648
658
|
consumeInternalFocus = () => {
|
|
@@ -670,7 +680,7 @@ export class Edytor {
|
|
|
670
680
|
*/
|
|
671
681
|
lastUserGestureOutsideEditor = false;
|
|
672
682
|
/** @internal Run `body` as part of the user's input (deferred work of an occurrence). */
|
|
673
|
-
userInput = (body) => this.#withUserInput(body,
|
|
683
|
+
userInput = (body) => this.#withUserInput(body, false)();
|
|
674
684
|
/**
|
|
675
685
|
* A commit's caret: select it (the projector displays it once the pin's
|
|
676
686
|
* render lands) and arm the IME post-commit jump rule — a move right after
|
|
@@ -681,13 +691,12 @@ export class Edytor {
|
|
|
681
691
|
if (this.readonly || this.isComposing || !text.isInDocument)
|
|
682
692
|
return;
|
|
683
693
|
const active = getActiveElement(this.node);
|
|
684
|
-
const selection = getDomSelection(this.node);
|
|
685
694
|
const ours = (node) => Boolean(node && this.node?.contains(node));
|
|
686
695
|
// Focus left for a foreign element with no selection of ours, or moved
|
|
687
696
|
// into a nested editable island (its own caret): the user owns it.
|
|
688
697
|
if ((active &&
|
|
689
|
-
!
|
|
690
|
-
!ours(
|
|
698
|
+
!focusIn(this.node) &&
|
|
699
|
+
!ours(getDomSelection(this.node)?.anchorNode) &&
|
|
691
700
|
!ours(this.selection.textRange().startText?.node)) ||
|
|
692
701
|
isKindControl(this.node, active))
|
|
693
702
|
return;
|
|
@@ -770,21 +779,50 @@ export class Edytor {
|
|
|
770
779
|
this.lastUserGestureOutsideEditor = false;
|
|
771
780
|
this.node = node;
|
|
772
781
|
this.selection.init();
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
782
|
+
// The engine's transaction events the projector and the observer read.
|
|
783
|
+
const engine = [
|
|
784
|
+
['beforeAllTransactions', this.projector.opening],
|
|
785
|
+
['beforeTransaction', this.surface.pending],
|
|
786
|
+
['beforeTransaction', this.projector.before],
|
|
787
|
+
['afterTransaction', this.projector.after]
|
|
788
|
+
];
|
|
789
|
+
for (const [name, listener] of engine)
|
|
790
|
+
this.doc.on(name, listener);
|
|
777
791
|
this.off.push(() => {
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
this.doc.off('beforeTransaction', this.projector.before);
|
|
781
|
-
this.doc.off('afterTransaction', this.projector.after);
|
|
792
|
+
for (const [name, listener] of engine)
|
|
793
|
+
this.doc.off(name, listener);
|
|
782
794
|
});
|
|
783
795
|
// Keydown serial bumps happen INSIDE onKeyDown, after the
|
|
784
796
|
// composition-phantom swallow — a swallowed trailing Enter/Backspace
|
|
785
797
|
// is a browser artifact, not a gesture, and must not disarm pending
|
|
786
798
|
// composition caret restores.
|
|
787
|
-
const
|
|
799
|
+
const user = this.#withUserInput;
|
|
800
|
+
const keydown = user(onKeyDown.bind(this), false);
|
|
801
|
+
const { pointer } = this.selection;
|
|
802
|
+
const drop = (event) => preventUnsupportedDrop(event, this);
|
|
803
|
+
/** The host's listeners; the user-input ones run inside its window (`isHandlingUserInput`). */
|
|
804
|
+
const host = [
|
|
805
|
+
['mousedown', pointer.preventTripleClick],
|
|
806
|
+
['click', pointer.tripleClick],
|
|
807
|
+
// Settle queued mutation repairs before the native menu opens —
|
|
808
|
+
// spellcheck suggestions are computed against the DOM at this
|
|
809
|
+
// moment (PM flushes its DOM observer on contextmenu for the
|
|
810
|
+
// same reason).
|
|
811
|
+
['contextmenu', () => void this.surface.flush()],
|
|
812
|
+
['beforeinput', user(this.onBeforeInput)],
|
|
813
|
+
// An `input` records what the browser did; it says nothing new about
|
|
814
|
+
// where the user wants the selection (a display still to land wins).
|
|
815
|
+
['input', user(this.onInput, false)],
|
|
816
|
+
['copy', this.onCopy],
|
|
817
|
+
['cut', user(this.onCut)],
|
|
818
|
+
['paste', user(this.onPaste)],
|
|
819
|
+
['dragstart', (event) => onTextDragStart(this, event)],
|
|
820
|
+
['dragend', () => onTextDragEnd(this)],
|
|
821
|
+
['dragover', drop],
|
|
822
|
+
['drop', user(drop)],
|
|
823
|
+
['compositionstart', this.onCompositionStart],
|
|
824
|
+
['compositionend', this.onCompositionEnd]
|
|
825
|
+
];
|
|
788
826
|
const detachSurface = this.surface.attach(node);
|
|
789
827
|
this.off.push(
|
|
790
828
|
// One handler per keyboard occurrence: keys inside the editor at
|
|
@@ -797,48 +835,43 @@ export class Edytor {
|
|
|
797
835
|
// Pointer and focus ownership, and the focus-time caret restore.
|
|
798
836
|
...attachFocus(this, node),
|
|
799
837
|
// The page leaving for good ends the session's history.
|
|
800
|
-
attachPageHide(this),
|
|
801
|
-
//
|
|
802
|
-
//
|
|
803
|
-
//
|
|
804
|
-
//
|
|
805
|
-
on(node, 'contextmenu', () => void this.surface.flush()), on(node, 'beforeinput', this.#withUserInput(this.onBeforeInput)),
|
|
806
|
-
// An `input` records what the browser did; it says nothing new about
|
|
807
|
-
// where the user wants the selection (a display still to land wins).
|
|
808
|
-
on(node, 'input', this.#withUserInput(this.onInput, { bumpSerial: false })), on(node, 'copy', this.onCopy), on(node, 'cut', this.#withUserInput(this.onCut)), on(node, 'paste', this.#withUserInput(this.onPaste)), on(node, 'dragstart', (event) => onTextDragStart(this, event)), on(node, 'dragend', () => onTextDragEnd(this)), on(node, 'dragover', (event) => preventUnsupportedDrop(event, this)), on(node, 'drop', this.#withUserInput((event) => preventUnsupportedDrop(event, this))), on(node, 'compositionstart', this.onCompositionStart), on(node, 'compositionend', this.onCompositionEnd), detachSurface);
|
|
809
|
-
// Chrome lives in the overlay, outside the host: remote carets, then the plugins'.
|
|
838
|
+
attachPageHide(this), ...host.map(([type, listener]) => on(node, type, listener)), detachSurface);
|
|
839
|
+
// Chrome lives in the overlay, outside the host: remote carets, the
|
|
840
|
+
// text a `replace` suggestion removes, marked beside the host, the
|
|
841
|
+
// polite live region (what this view's block moves and deletes did);
|
|
842
|
+
// the plugins' after them.
|
|
810
843
|
const detachOverlay = this.overlay.attach(node);
|
|
811
|
-
const
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
props: { edytor: this, name: 'suggestion-range', z: 19, ranges: this.#replaced }
|
|
819
|
-
});
|
|
820
|
-
// The polite live region: what this view's block moves and deletes did.
|
|
821
|
-
const live = mount(Announcer, {
|
|
822
|
-
target: this.overlay.layer,
|
|
823
|
-
props: { announcer: this.announcer }
|
|
824
|
-
});
|
|
825
|
-
this.off.push(() => unmount(presence), () => unmount(ranges), () => unmount(live), detachOverlay);
|
|
844
|
+
const target = this.overlay.layer;
|
|
845
|
+
const mounted = [
|
|
846
|
+
[RemoteSelections, { edytor: this }],
|
|
847
|
+
[Ranges, { edytor: this, name: 'suggestion-range', z: 19, ranges: this.#replaced }],
|
|
848
|
+
[Announcer, { announcer: this.announcer }]
|
|
849
|
+
].map(([component, props]) => mount(component, { target, props: props }));
|
|
850
|
+
this.off.push(() => mounted.forEach((instance) => void unmount(instance)), detachOverlay);
|
|
826
851
|
this.off.push(...cleanups(this.plugins, (plugin) => plugin.onEdytorAttached?.({ node })));
|
|
827
852
|
return {
|
|
828
853
|
destroy: () => {
|
|
829
854
|
if (selectionIsInside(node))
|
|
830
855
|
clearDomSelection(node);
|
|
831
|
-
const active =
|
|
832
|
-
if (active instanceof HTMLElement
|
|
856
|
+
const active = focusIn(node);
|
|
857
|
+
if (active instanceof HTMLElement)
|
|
833
858
|
active.blur();
|
|
834
|
-
this
|
|
835
|
-
this.attempts.clear();
|
|
836
|
-
this.composition.reset();
|
|
837
|
-
// Drain AND clear: `destroy()` runs the same batch again.
|
|
838
|
-
this.off.splice(0).forEach((off) => off());
|
|
859
|
+
this.#detach();
|
|
839
860
|
}
|
|
840
861
|
};
|
|
841
862
|
};
|
|
863
|
+
/**
|
|
864
|
+
* Release what an attach holds: the selection's listeners, the attempts,
|
|
865
|
+
* the composition, then the attach-lifetime batch (DOM listeners, the
|
|
866
|
+
* mutation observer, plugin actions), drained AND cleared (`destroy()`
|
|
867
|
+
* runs it again).
|
|
868
|
+
*/
|
|
869
|
+
#detach = () => {
|
|
870
|
+
this.selection.destroy();
|
|
871
|
+
this.attempts.clear();
|
|
872
|
+
this.composition.reset();
|
|
873
|
+
this.off.splice(0).forEach((off) => off());
|
|
874
|
+
};
|
|
842
875
|
/** @internal */
|
|
843
876
|
destroyed = false;
|
|
844
877
|
/** @internal The key of this view's presence entry — minted here, written only by this view. */
|
|
@@ -869,21 +902,13 @@ export class Edytor {
|
|
|
869
902
|
// A pending readiness binding must not resurrect a dead view.
|
|
870
903
|
this.#readinessRelease?.();
|
|
871
904
|
this.#readinessRelease = undefined;
|
|
872
|
-
|
|
873
|
-
// observer, plugin actions — and empties the array.
|
|
874
|
-
this.off.splice(0).forEach((off) => off());
|
|
905
|
+
this.#detach();
|
|
875
906
|
this.#offCommit?.();
|
|
876
907
|
this.history.unbind();
|
|
877
|
-
// `selectionchange` listener + the published remote caret.
|
|
878
|
-
this.selection.destroy();
|
|
879
|
-
this.attempts.clear();
|
|
880
|
-
this.composition.reset();
|
|
881
908
|
this.cells?.dispose();
|
|
882
909
|
// Wrapper maps — drop every id/node→wrapper edge a shared doc or a
|
|
883
910
|
// stray mutation could still reach.
|
|
884
911
|
this.idToBlock.clear();
|
|
885
|
-
this.nodeToInlineBlock.clear();
|
|
886
|
-
this.nodeToText.clear();
|
|
887
912
|
this.root = undefined;
|
|
888
913
|
this.node = undefined;
|
|
889
914
|
// Release this view's local-edit origin from the document's
|
|
@@ -9,6 +9,6 @@ import { type Attempt } from '../session/attempt.js';
|
|
|
9
9
|
*/
|
|
10
10
|
export declare const insertLineBreak: (edytor: Edytor, snapshot: Attempt, caret?: "after" | "before") => void;
|
|
11
11
|
/** The model command for a `beforeinput`, run as one user command (undo policy, prevention scope). */
|
|
12
|
-
export declare const runBeforeInputCommand: (edytor: Edytor, snapshot: Attempt) => boolean | void |
|
|
12
|
+
export declare const runBeforeInputCommand: (edytor: Edytor, snapshot: Attempt) => boolean | void | Promise<undefined> | Text | null | undefined;
|
|
13
13
|
/** An editing intent at the current selection (a key binding's command): no event to fabricate. */
|
|
14
|
-
export declare const runIntent: (edytor: Edytor, inputType: string) => boolean | void |
|
|
14
|
+
export declare const runIntent: (edytor: Edytor, inputType: string) => boolean | void | Promise<undefined> | Text | null | undefined;
|
|
@@ -10,3 +10,5 @@
|
|
|
10
10
|
* through its child index); a disconnected one keeps its offset.
|
|
11
11
|
*/
|
|
12
12
|
export declare const getTextContentOffsetAtPoint: (textNode: Node, node: Node, offset: number) => number;
|
|
13
|
+
/** `element`'s text nodes in document order: the leaves the offsets above count. */
|
|
14
|
+
export declare const textLeaves: (element: Node) => CharacterData[];
|
|
@@ -22,3 +22,11 @@ export const getTextContentOffsetAtPoint = (textNode, node, offset) => {
|
|
|
22
22
|
range.setEnd(node, at);
|
|
23
23
|
return range.toString().length;
|
|
24
24
|
};
|
|
25
|
+
/** `element`'s text nodes in document order: the leaves the offsets above count. */
|
|
26
|
+
export const textLeaves = (element) => {
|
|
27
|
+
const walker = (element.ownerDocument ?? document).createTreeWalker(element, NodeFilter.SHOW_TEXT);
|
|
28
|
+
const leaves = [];
|
|
29
|
+
while (walker.nextNode())
|
|
30
|
+
leaves.push(walker.currentNode);
|
|
31
|
+
return leaves;
|
|
32
|
+
};
|
|
@@ -2,9 +2,19 @@ export declare const isNativeInteractiveControl: (target: EventTarget | null) =>
|
|
|
2
2
|
export declare const isNativeInteractiveEvent: (event: Event) => boolean;
|
|
3
3
|
export declare const isNativeTextControl: (target: EventTarget | null) => boolean;
|
|
4
4
|
export declare const isNativeTextControlEvent: (event: Event) => boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Whether a press inside the host, and its whole gesture (its clicks and
|
|
7
|
+
* multi-clicks, its `selectstart`, its drag), is its target's own
|
|
8
|
+
* (`sel.press.void`): a control of a block's chrome, a link there (a
|
|
9
|
+
* bookmark's card, a file's row: its click opens it), or an element that
|
|
10
|
+
* keeps the selection (`data-edytor-keep-selection`). The one rule every
|
|
11
|
+
* press handler asks.
|
|
12
|
+
*/
|
|
13
|
+
export declare const ownsPress: (editorRoot: Element | null | undefined, event: Event) => boolean;
|
|
5
14
|
/**
|
|
6
15
|
* A kind's own control: a form control (`input` of any type, `textarea`,
|
|
7
|
-
* `select`, `button`)
|
|
16
|
+
* `select`, `button`), a control of a block's chrome (an interactive role,
|
|
17
|
+
* a focusable element, `data-edytor-control`) or a nested editable island inside the host (a plugin
|
|
8
18
|
* chrome editor, an embedded code field: it owns its input and composition
|
|
9
19
|
* lifecycle), never the root nor the editor's own text elements (they carry
|
|
10
20
|
* their own `contentEditable="true"`). It owns its focus, selection and
|
|
@@ -32,13 +32,52 @@ export const isNativeInteractiveControl = (target) => Boolean(findClosestElement
|
|
|
32
32
|
export const isNativeInteractiveEvent = (event) => Boolean(findNativeControlInEvent(event, NATIVE_INTERACTIVE_SELECTOR));
|
|
33
33
|
export const isNativeTextControl = (target) => Boolean(findClosestElement(target, NATIVE_TEXT_SELECTOR));
|
|
34
34
|
export const isNativeTextControlEvent = (event) => Boolean(findNativeControlInEvent(event, NATIVE_TEXT_SELECTOR));
|
|
35
|
+
/**
|
|
36
|
+
* The controls of a block's chrome (`sel.press.void`): a native form
|
|
37
|
+
* control, an element whose role says it is interactive or that takes the
|
|
38
|
+
* focus, and one the app marks as its own (`data-edytor-control`, for any
|
|
39
|
+
* other element). Their press and its whole gesture, their keys and their
|
|
40
|
+
* input are theirs: the editor does nothing with them.
|
|
41
|
+
*/
|
|
42
|
+
const CHROME_CONTROL_SELECTOR = [
|
|
43
|
+
'input, textarea, select, button',
|
|
44
|
+
'[role="button"], [role="link"], [role="checkbox"], [role="switch"], [role="menuitem"]',
|
|
45
|
+
'[role="tab"], [role="option"], [role="slider"], [role="textbox"], [role="combobox"]',
|
|
46
|
+
'[tabindex]:not([tabindex^="-"])',
|
|
47
|
+
'[data-edytor-control]'
|
|
48
|
+
].join(', ');
|
|
49
|
+
/**
|
|
50
|
+
* The control of a block's chrome holding `target` inside the host, by
|
|
51
|
+
* `selector`: never the host, nor an element inside the editor's text or an
|
|
52
|
+
* inline atom (a link mark, a mention: the text's own presses).
|
|
53
|
+
*/
|
|
54
|
+
const chromeControl = (editorRoot, target, selector = CHROME_CONTROL_SELECTOR) => {
|
|
55
|
+
const control = getElementFromTarget(target)?.closest(selector);
|
|
56
|
+
return control &&
|
|
57
|
+
editorRoot &&
|
|
58
|
+
control !== editorRoot &&
|
|
59
|
+
editorRoot.contains(control) &&
|
|
60
|
+
!control.closest('[data-edytor-text], [data-edytor-inline-block]')
|
|
61
|
+
? control
|
|
62
|
+
: null;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Whether a press inside the host, and its whole gesture (its clicks and
|
|
66
|
+
* multi-clicks, its `selectstart`, its drag), is its target's own
|
|
67
|
+
* (`sel.press.void`): a control of a block's chrome, a link there (a
|
|
68
|
+
* bookmark's card, a file's row: its click opens it), or an element that
|
|
69
|
+
* keeps the selection (`data-edytor-keep-selection`). The one rule every
|
|
70
|
+
* press handler asks.
|
|
71
|
+
*/
|
|
72
|
+
export const ownsPress = (editorRoot, event) => Boolean(chromeControl(editorRoot, event.target, `${CHROME_CONTROL_SELECTOR}, [data-edytor-keep-selection], a[href]`));
|
|
35
73
|
// Any non-"false" contenteditable value — attribute selectors are
|
|
36
74
|
// case-sensitive, and "TRUE"/"Plaintext-Only"/"inherit"/garbage values
|
|
37
75
|
// all still establish a nested editable context the guard must catch.
|
|
38
76
|
const EDITABLE_TARGET_SELECTOR = '[contenteditable]:not([contenteditable="false" i])';
|
|
39
77
|
/**
|
|
40
78
|
* A kind's own control: a form control (`input` of any type, `textarea`,
|
|
41
|
-
* `select`, `button`)
|
|
79
|
+
* `select`, `button`), a control of a block's chrome (an interactive role,
|
|
80
|
+
* a focusable element, `data-edytor-control`) or a nested editable island inside the host (a plugin
|
|
42
81
|
* chrome editor, an embedded code field: it owns its input and composition
|
|
43
82
|
* lifecycle), never the root nor the editor's own text elements (they carry
|
|
44
83
|
* their own `contentEditable="true"`). It owns its focus, selection and
|
|
@@ -48,6 +87,7 @@ const EDITABLE_TARGET_SELECTOR = '[contenteditable]:not([contenteditable="false"
|
|
|
48
87
|
export const isKindControl = (editorRoot, target) => {
|
|
49
88
|
const editable = findClosestElement(target, EDITABLE_TARGET_SELECTOR);
|
|
50
89
|
const control = findClosestElement(target, NATIVE_ACTIVE_ELEMENT_SELECTOR) ??
|
|
90
|
+
chromeControl(editorRoot, target) ??
|
|
51
91
|
(editable?.hasAttribute('data-edytor-text') ? null : editable);
|
|
52
92
|
return Boolean(control && editorRoot && control !== editorRoot && editorRoot.contains(control));
|
|
53
93
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { vetoable } from '../utils.js';
|
|
2
|
-
import { createDomRange, createDomSelectionSnapshotFromRange, getActiveElement, getDomSelection } from '../selection/domSelection.js';
|
|
2
|
+
import { createDomRange, createDomSelectionSnapshotFromRange, focusIn, getActiveElement, getDomSelection } from '../selection/domSelection.js';
|
|
3
3
|
import { getYIndex } from '../selection/selection.utils.js';
|
|
4
4
|
import { sameValue } from '../session/selection.js';
|
|
5
5
|
import { INTENTS, attemptOf, intentOf, kindOf, reproject, targetless } from '../session/attempt.js';
|
|
@@ -87,9 +87,9 @@ const isForeignHistory = (edytor) => {
|
|
|
87
87
|
const node = edytor.node;
|
|
88
88
|
if (!node)
|
|
89
89
|
return true;
|
|
90
|
-
|
|
91
|
-
if (active && node.contains(active))
|
|
90
|
+
if (focusIn(node))
|
|
92
91
|
return false;
|
|
92
|
+
const active = getActiveElement(node);
|
|
93
93
|
const { body, documentElement } = node.ownerDocument;
|
|
94
94
|
const anchor = getDomSelection(node)?.anchorNode;
|
|
95
95
|
return ((!!active &&
|
package/dist/events/onFocus.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* programmatic focus returns to. Answers the listeners' teardown.
|
|
7
7
|
*/
|
|
8
8
|
import { on } from 'svelte/events';
|
|
9
|
-
import { clearDomSelection, getActiveElement, getDomSelection, getDomSelectionSnapshot } from '../selection/domSelection.js';
|
|
9
|
+
import { clearDomSelection, focusIn, getActiveElement, getDomSelection, getDomSelectionSnapshot } from '../selection/domSelection.js';
|
|
10
10
|
import { getYIndex } from '../selection/selection.utils.js';
|
|
11
11
|
import { noSelection } from '../session/selection.js';
|
|
12
12
|
import { observeInternalDragSources } from './onDrop.js';
|
|
@@ -38,8 +38,7 @@ export const takeKeys = (edytor) => {
|
|
|
38
38
|
return;
|
|
39
39
|
if (getActiveElement(node) === node)
|
|
40
40
|
return;
|
|
41
|
-
edytor.expectInternalFocus();
|
|
42
|
-
node.focus({ preventScroll: true });
|
|
41
|
+
edytor.expectInternalFocus(node);
|
|
43
42
|
edytor.projector.parked();
|
|
44
43
|
edytor.selection.display();
|
|
45
44
|
};
|
|
@@ -125,6 +124,9 @@ export const attachFocus = (edytor, node) => {
|
|
|
125
124
|
*/
|
|
126
125
|
const placingPress = (event) => {
|
|
127
126
|
const { pointer } = edytor.selection;
|
|
127
|
+
// A control of a block's chrome owns its whole press (`sel.press.void`).
|
|
128
|
+
if (pointer.ownPress(event))
|
|
129
|
+
return void (pointer.claimed = false);
|
|
128
130
|
if (pointer.marginPress(event))
|
|
129
131
|
return;
|
|
130
132
|
if (!pointer.belowPress(event))
|
|
@@ -136,8 +138,9 @@ export const attachFocus = (edytor, node) => {
|
|
|
136
138
|
edytor.composition.abandon();
|
|
137
139
|
lastPointerDownInsideEditorAt = event.timeStamp;
|
|
138
140
|
const { pointer } = edytor.selection;
|
|
139
|
-
// The margin gesture's press places no caret and starts no text drag
|
|
140
|
-
|
|
141
|
+
// The margin gesture's press places no caret and starts no text drag; a
|
|
142
|
+
// control's press is the control's (`sel.press.void`).
|
|
143
|
+
if (pointer.claimed || pointer.ownPress(event))
|
|
141
144
|
return;
|
|
142
145
|
edytor.projector.pressed();
|
|
143
146
|
pointer.capture(event);
|
|
@@ -145,13 +148,8 @@ export const attachFocus = (edytor, node) => {
|
|
|
145
148
|
pointer.collapseBlocksAt(event);
|
|
146
149
|
};
|
|
147
150
|
const clearNativeSelectionAfterExternalFocus = () => {
|
|
148
|
-
|
|
149
|
-
if (activeElement instanceof Node && node.contains(activeElement)) {
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
if (selectionIsInside(node)) {
|
|
151
|
+
if (!focusIn(node) && selectionIsInside(node))
|
|
153
152
|
clearDomSelection(node);
|
|
154
|
-
}
|
|
155
153
|
};
|
|
156
154
|
/**
|
|
157
155
|
* A keyboard or programmatic focus returns to the model's selection: the
|