@codemirror/view 6.27.0 → 6.28.1
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/CHANGELOG.md +18 -0
- package/dist/index.cjs +235 -36
- package/dist/index.js +235 -36
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
## 6.28.1 (2024-06-12)
|
|
2
|
+
|
|
3
|
+
### Bug fixes
|
|
4
|
+
|
|
5
|
+
Disable `EditContext` by default again, to work around a regression where Chrome's implementation doesn't support inverted selections.
|
|
6
|
+
|
|
7
|
+
Make sure `EditorView.editable` is respected when `EditContext` is used.
|
|
8
|
+
|
|
9
|
+
## 6.28.0 (2024-06-10)
|
|
10
|
+
|
|
11
|
+
### Bug fixes
|
|
12
|
+
|
|
13
|
+
Fix an issue where long lines broken up by block widgets were sometimes only partially rendered.
|
|
14
|
+
|
|
15
|
+
### New features
|
|
16
|
+
|
|
17
|
+
The editor will now, when available (which is only on Chrome for the foreseeable future) use the [`EditContext`](https://developer.mozilla.org/en-US/docs/Web/API/EditContext) API to capture text input.
|
|
18
|
+
|
|
1
19
|
## 6.27.0 (2024-06-04)
|
|
2
20
|
|
|
3
21
|
### New features
|
package/dist/index.cjs
CHANGED
|
@@ -2368,6 +2368,7 @@ class ScrollTarget {
|
|
|
2368
2368
|
}
|
|
2369
2369
|
}
|
|
2370
2370
|
const scrollIntoView = state.StateEffect.define({ map: (t, ch) => t.map(ch) });
|
|
2371
|
+
const setEditContextFormatting = state.StateEffect.define();
|
|
2371
2372
|
/**
|
|
2372
2373
|
Log or report an unhandled exception in client code. Should
|
|
2373
2374
|
probably only be used by extension code that allows client code to
|
|
@@ -2704,10 +2705,11 @@ class DocView extends ContentView {
|
|
|
2704
2705
|
super();
|
|
2705
2706
|
this.view = view;
|
|
2706
2707
|
this.decorations = [];
|
|
2707
|
-
this.dynamicDecorationMap = [];
|
|
2708
|
+
this.dynamicDecorationMap = [false];
|
|
2708
2709
|
this.domChanged = null;
|
|
2709
2710
|
this.hasComposition = null;
|
|
2710
2711
|
this.markedForComposition = new Set;
|
|
2712
|
+
this.editContextFormatting = Decoration.none;
|
|
2711
2713
|
this.lastCompositionAfterCursor = false;
|
|
2712
2714
|
// Track a minimum width for the editor. When measuring sizes in
|
|
2713
2715
|
// measureVisibleLineHeights, this is updated to point at the width
|
|
@@ -2746,8 +2748,9 @@ class DocView extends ContentView {
|
|
|
2746
2748
|
this.minWidthTo = update.changes.mapPos(this.minWidthTo, 1);
|
|
2747
2749
|
}
|
|
2748
2750
|
}
|
|
2751
|
+
this.updateEditContextFormatting(update);
|
|
2749
2752
|
let readCompositionAt = -1;
|
|
2750
|
-
if (this.view.inputState.composing >= 0) {
|
|
2753
|
+
if (this.view.inputState.composing >= 0 && !this.view.observer.editContext) {
|
|
2751
2754
|
if ((_a = this.domChanged) === null || _a === void 0 ? void 0 : _a.newSel)
|
|
2752
2755
|
readCompositionAt = this.domChanged.newSel.head;
|
|
2753
2756
|
else if (!touchesComposition(update.changes, this.hasComposition) && !update.selectionSet)
|
|
@@ -2855,6 +2858,14 @@ class DocView extends ContentView {
|
|
|
2855
2858
|
if (composition)
|
|
2856
2859
|
this.fixCompositionDOM(composition);
|
|
2857
2860
|
}
|
|
2861
|
+
updateEditContextFormatting(update) {
|
|
2862
|
+
this.editContextFormatting = this.editContextFormatting.map(update.changes);
|
|
2863
|
+
for (let tr of update.transactions)
|
|
2864
|
+
for (let effect of tr.effects)
|
|
2865
|
+
if (effect.is(setEditContextFormatting)) {
|
|
2866
|
+
this.editContextFormatting = effect.value;
|
|
2867
|
+
}
|
|
2868
|
+
}
|
|
2858
2869
|
compositionView(composition) {
|
|
2859
2870
|
let cur = new TextView(composition.text.nodeValue);
|
|
2860
2871
|
cur.flags |= 8 /* ViewFlag.Composition */;
|
|
@@ -3186,7 +3197,7 @@ class DocView extends ContentView {
|
|
|
3186
3197
|
return Decoration.set(deco);
|
|
3187
3198
|
}
|
|
3188
3199
|
updateDeco() {
|
|
3189
|
-
let i =
|
|
3200
|
+
let i = 1;
|
|
3190
3201
|
let allDeco = this.view.state.facet(decorations).map(d => {
|
|
3191
3202
|
let dynamic = this.dynamicDecorationMap[i++] = typeof d == "function";
|
|
3192
3203
|
return dynamic ? d(this.view) : d;
|
|
@@ -3202,6 +3213,7 @@ class DocView extends ContentView {
|
|
|
3202
3213
|
allDeco.push(state.RangeSet.join(outerDeco));
|
|
3203
3214
|
}
|
|
3204
3215
|
this.decorations = [
|
|
3216
|
+
this.editContextFormatting,
|
|
3205
3217
|
...allDeco,
|
|
3206
3218
|
this.computeBlockGapDeco(),
|
|
3207
3219
|
this.view.viewState.lineGapDeco
|
|
@@ -3895,6 +3907,7 @@ class InputState {
|
|
|
3895
3907
|
this.mouseSelection = mouseSelection;
|
|
3896
3908
|
}
|
|
3897
3909
|
update(update) {
|
|
3910
|
+
this.view.observer.update(update);
|
|
3898
3911
|
if (this.mouseSelection)
|
|
3899
3912
|
this.mouseSelection.update(update);
|
|
3900
3913
|
if (this.draggedContent && update.docChanged)
|
|
@@ -4492,6 +4505,8 @@ observers.blur = view => {
|
|
|
4492
4505
|
updateForFocusChange(view);
|
|
4493
4506
|
};
|
|
4494
4507
|
observers.compositionstart = observers.compositionupdate = view => {
|
|
4508
|
+
if (view.observer.editContext)
|
|
4509
|
+
return; // Composition handled by edit context
|
|
4495
4510
|
if (view.inputState.compositionFirstChange == null)
|
|
4496
4511
|
view.inputState.compositionFirstChange = true;
|
|
4497
4512
|
if (view.inputState.composing < 0) {
|
|
@@ -4500,6 +4515,8 @@ observers.compositionstart = observers.compositionupdate = view => {
|
|
|
4500
4515
|
}
|
|
4501
4516
|
};
|
|
4502
4517
|
observers.compositionend = view => {
|
|
4518
|
+
if (view.observer.editContext)
|
|
4519
|
+
return; // Composition handled by edit context
|
|
4503
4520
|
view.inputState.composing = -1;
|
|
4504
4521
|
view.inputState.compositionEndedAt = Date.now();
|
|
4505
4522
|
view.inputState.compositionPendingKey = true;
|
|
@@ -5693,12 +5710,12 @@ class ViewState {
|
|
|
5693
5710
|
}
|
|
5694
5711
|
gaps.push(gap);
|
|
5695
5712
|
};
|
|
5696
|
-
|
|
5697
|
-
if (line.length < doubleMargin)
|
|
5698
|
-
|
|
5713
|
+
let checkLine = (line) => {
|
|
5714
|
+
if (line.length < doubleMargin || line.type != exports.BlockType.Text)
|
|
5715
|
+
return;
|
|
5699
5716
|
let structure = lineStructure(line.from, line.to, this.stateDeco);
|
|
5700
5717
|
if (structure.total < doubleMargin)
|
|
5701
|
-
|
|
5718
|
+
return;
|
|
5702
5719
|
let target = this.scrollTarget ? this.scrollTarget.range.head : null;
|
|
5703
5720
|
let viewFrom, viewTo;
|
|
5704
5721
|
if (wrapping) {
|
|
@@ -5738,6 +5755,12 @@ class ViewState {
|
|
|
5738
5755
|
addGap(line.from, viewFrom, line, structure);
|
|
5739
5756
|
if (viewTo < line.to)
|
|
5740
5757
|
addGap(viewTo, line.to, line, structure);
|
|
5758
|
+
};
|
|
5759
|
+
for (let line of this.viewportLines) {
|
|
5760
|
+
if (Array.isArray(line.type))
|
|
5761
|
+
line.type.forEach(checkLine);
|
|
5762
|
+
else
|
|
5763
|
+
checkLine(line);
|
|
5741
5764
|
}
|
|
5742
5765
|
return gaps;
|
|
5743
5766
|
}
|
|
@@ -6397,35 +6420,7 @@ function applyDOMChange(view, domChange) {
|
|
|
6397
6420
|
change = { from: sel.from, to: sel.to, insert: state.Text.of([" "]) };
|
|
6398
6421
|
}
|
|
6399
6422
|
if (change) {
|
|
6400
|
-
|
|
6401
|
-
return true;
|
|
6402
|
-
// Android browsers don't fire reasonable key events for enter,
|
|
6403
|
-
// backspace, or delete. So this detects changes that look like
|
|
6404
|
-
// they're caused by those keys, and reinterprets them as key
|
|
6405
|
-
// events. (Some of these keys are also handled by beforeinput
|
|
6406
|
-
// events and the pendingAndroidKey mechanism, but that's not
|
|
6407
|
-
// reliable in all situations.)
|
|
6408
|
-
if (browser.android &&
|
|
6409
|
-
((change.to == sel.to &&
|
|
6410
|
-
// GBoard will sometimes remove a space it just inserted
|
|
6411
|
-
// after a completion when you press enter
|
|
6412
|
-
(change.from == sel.from || change.from == sel.from - 1 && view.state.sliceDoc(change.from, sel.from) == " ") &&
|
|
6413
|
-
change.insert.length == 1 && change.insert.lines == 2 &&
|
|
6414
|
-
dispatchKey(view.contentDOM, "Enter", 13)) ||
|
|
6415
|
-
((change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 ||
|
|
6416
|
-
lastKey == 8 && change.insert.length < change.to - change.from && change.to > sel.head) &&
|
|
6417
|
-
dispatchKey(view.contentDOM, "Backspace", 8)) ||
|
|
6418
|
-
(change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
|
|
6419
|
-
dispatchKey(view.contentDOM, "Delete", 46))))
|
|
6420
|
-
return true;
|
|
6421
|
-
let text = change.insert.toString();
|
|
6422
|
-
if (view.inputState.composing >= 0)
|
|
6423
|
-
view.inputState.composing++;
|
|
6424
|
-
let defaultTr;
|
|
6425
|
-
let defaultInsert = () => defaultTr || (defaultTr = applyDefaultInsert(view, change, newSel));
|
|
6426
|
-
if (!view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text, defaultInsert)))
|
|
6427
|
-
view.dispatch(defaultInsert());
|
|
6428
|
-
return true;
|
|
6423
|
+
return applyDOMChangeInner(view, change, newSel, lastKey);
|
|
6429
6424
|
}
|
|
6430
6425
|
else if (newSel && !newSel.main.eq(sel)) {
|
|
6431
6426
|
let scrollIntoView = false, userEvent = "select";
|
|
@@ -6441,6 +6436,38 @@ function applyDOMChange(view, domChange) {
|
|
|
6441
6436
|
return false;
|
|
6442
6437
|
}
|
|
6443
6438
|
}
|
|
6439
|
+
function applyDOMChangeInner(view, change, newSel, lastKey = -1) {
|
|
6440
|
+
if (browser.ios && view.inputState.flushIOSKey(change))
|
|
6441
|
+
return true;
|
|
6442
|
+
let sel = view.state.selection.main;
|
|
6443
|
+
// Android browsers don't fire reasonable key events for enter,
|
|
6444
|
+
// backspace, or delete. So this detects changes that look like
|
|
6445
|
+
// they're caused by those keys, and reinterprets them as key
|
|
6446
|
+
// events. (Some of these keys are also handled by beforeinput
|
|
6447
|
+
// events and the pendingAndroidKey mechanism, but that's not
|
|
6448
|
+
// reliable in all situations.)
|
|
6449
|
+
if (browser.android &&
|
|
6450
|
+
((change.to == sel.to &&
|
|
6451
|
+
// GBoard will sometimes remove a space it just inserted
|
|
6452
|
+
// after a completion when you press enter
|
|
6453
|
+
(change.from == sel.from || change.from == sel.from - 1 && view.state.sliceDoc(change.from, sel.from) == " ") &&
|
|
6454
|
+
change.insert.length == 1 && change.insert.lines == 2 &&
|
|
6455
|
+
dispatchKey(view.contentDOM, "Enter", 13)) ||
|
|
6456
|
+
((change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 ||
|
|
6457
|
+
lastKey == 8 && change.insert.length < change.to - change.from && change.to > sel.head) &&
|
|
6458
|
+
dispatchKey(view.contentDOM, "Backspace", 8)) ||
|
|
6459
|
+
(change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
|
|
6460
|
+
dispatchKey(view.contentDOM, "Delete", 46))))
|
|
6461
|
+
return true;
|
|
6462
|
+
let text = change.insert.toString();
|
|
6463
|
+
if (view.inputState.composing >= 0)
|
|
6464
|
+
view.inputState.composing++;
|
|
6465
|
+
let defaultTr;
|
|
6466
|
+
let defaultInsert = () => defaultTr || (defaultTr = applyDefaultInsert(view, change, newSel));
|
|
6467
|
+
if (!view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text, defaultInsert)))
|
|
6468
|
+
view.dispatch(defaultInsert());
|
|
6469
|
+
return true;
|
|
6470
|
+
}
|
|
6444
6471
|
function applyDefaultInsert(view, change, newSel) {
|
|
6445
6472
|
let tr, startState = view.state, sel = startState.selection.main;
|
|
6446
6473
|
if (change.from >= sel.from && change.to <= sel.to && change.to - change.from >= (sel.to - sel.from) / 3 &&
|
|
@@ -6567,6 +6594,7 @@ class DOMObserver {
|
|
|
6567
6594
|
constructor(view) {
|
|
6568
6595
|
this.view = view;
|
|
6569
6596
|
this.active = false;
|
|
6597
|
+
this.editContext = null;
|
|
6570
6598
|
// The known selection. Kept in our own object, as opposed to just
|
|
6571
6599
|
// directly accessing the selection because:
|
|
6572
6600
|
// - Safari doesn't report the right selection in shadow DOM
|
|
@@ -6611,6 +6639,11 @@ class DOMObserver {
|
|
|
6611
6639
|
else
|
|
6612
6640
|
this.flush();
|
|
6613
6641
|
});
|
|
6642
|
+
if (window.EditContext && view.constructor.EDIT_CONTEXT === true) {
|
|
6643
|
+
this.editContext = new EditContextManager(view);
|
|
6644
|
+
if (view.state.facet(editable))
|
|
6645
|
+
view.contentDOM.editContext = this.editContext.editContext;
|
|
6646
|
+
}
|
|
6614
6647
|
if (useCharData)
|
|
6615
6648
|
this.onCharData = (event) => {
|
|
6616
6649
|
this.queue.push({ target: event.target,
|
|
@@ -6661,6 +6694,8 @@ class DOMObserver {
|
|
|
6661
6694
|
onScroll(e) {
|
|
6662
6695
|
if (this.intersecting)
|
|
6663
6696
|
this.flush(false);
|
|
6697
|
+
if (this.editContext)
|
|
6698
|
+
this.view.requestMeasure(this.editContext.measureReq);
|
|
6664
6699
|
this.onScrollChanged(e);
|
|
6665
6700
|
}
|
|
6666
6701
|
onResize() {
|
|
@@ -6970,6 +7005,13 @@ class DOMObserver {
|
|
|
6970
7005
|
win.removeEventListener("beforeprint", this.onPrint);
|
|
6971
7006
|
win.document.removeEventListener("selectionchange", this.onSelectionChange);
|
|
6972
7007
|
}
|
|
7008
|
+
update(update) {
|
|
7009
|
+
if (this.editContext) {
|
|
7010
|
+
this.editContext.update(update);
|
|
7011
|
+
if (update.startState.facet(editable) != update.state.facet(editable))
|
|
7012
|
+
update.view.contentDOM.editContext = update.state.facet(editable) ? this.editContext.editContext : null;
|
|
7013
|
+
}
|
|
7014
|
+
}
|
|
6973
7015
|
destroy() {
|
|
6974
7016
|
var _a, _b, _c;
|
|
6975
7017
|
this.stop();
|
|
@@ -7029,6 +7071,161 @@ function safariSelectionRangeHack(view, selection) {
|
|
|
7029
7071
|
view.contentDOM.removeEventListener("beforeinput", read, true);
|
|
7030
7072
|
return found ? buildSelectionRangeFromRange(view, found) : null;
|
|
7031
7073
|
}
|
|
7074
|
+
class EditContextManager {
|
|
7075
|
+
constructor(view) {
|
|
7076
|
+
// The document window for which the text in the context is
|
|
7077
|
+
// maintained. For large documents, this may be smaller than the
|
|
7078
|
+
// editor document. This window always includes the selection head.
|
|
7079
|
+
this.from = 0;
|
|
7080
|
+
this.to = 0;
|
|
7081
|
+
// When applying a transaction, this is used to compare the change
|
|
7082
|
+
// made to the context content to the change in the transaction in
|
|
7083
|
+
// order to make the minimal changes to the context (since touching
|
|
7084
|
+
// that sometimes breaks series of multiple edits made for a single
|
|
7085
|
+
// user action on some Android keyboards)
|
|
7086
|
+
this.pendingContextChange = null;
|
|
7087
|
+
this.resetRange(view.state);
|
|
7088
|
+
let context = this.editContext = new window.EditContext({
|
|
7089
|
+
text: view.state.doc.sliceString(this.from, this.to),
|
|
7090
|
+
selectionStart: this.toContextPos(Math.max(this.from, Math.min(this.to, view.state.selection.main.anchor))),
|
|
7091
|
+
selectionEnd: this.toContextPos(view.state.selection.main.head)
|
|
7092
|
+
});
|
|
7093
|
+
context.addEventListener("textupdate", e => {
|
|
7094
|
+
let { anchor } = view.state.selection.main;
|
|
7095
|
+
let change = { from: this.toEditorPos(e.updateRangeStart),
|
|
7096
|
+
to: this.toEditorPos(e.updateRangeEnd),
|
|
7097
|
+
insert: state.Text.of(e.text.split("\n")) };
|
|
7098
|
+
// If the window doesn't include the anchor, assume changes
|
|
7099
|
+
// adjacent to a side go up to the anchor.
|
|
7100
|
+
if (change.from == this.from && anchor < this.from)
|
|
7101
|
+
change.from = anchor;
|
|
7102
|
+
else if (change.to == this.to && anchor > this.to)
|
|
7103
|
+
change.to = anchor;
|
|
7104
|
+
// Edit context sometimes fire empty changes
|
|
7105
|
+
if (change.from == change.to && !change.insert.length)
|
|
7106
|
+
return;
|
|
7107
|
+
this.pendingContextChange = change;
|
|
7108
|
+
applyDOMChangeInner(view, change, state.EditorSelection.single(this.toEditorPos(e.selectionStart), this.toEditorPos(e.selectionEnd)));
|
|
7109
|
+
// If the transaction didn't flush our change, revert it so
|
|
7110
|
+
// that the context is in sync with the editor state again.
|
|
7111
|
+
if (this.pendingContextChange)
|
|
7112
|
+
this.revertPending(view.state);
|
|
7113
|
+
});
|
|
7114
|
+
context.addEventListener("characterboundsupdate", e => {
|
|
7115
|
+
let rects = [], prev = null;
|
|
7116
|
+
for (let i = this.toEditorPos(e.rangeStart), end = this.toEditorPos(e.rangeEnd); i < end; i++) {
|
|
7117
|
+
let rect = view.coordsForChar(i);
|
|
7118
|
+
prev = (rect && new DOMRect(rect.left, rect.right, rect.right - rect.left, rect.bottom - rect.top))
|
|
7119
|
+
|| prev || new DOMRect;
|
|
7120
|
+
rects.push(prev);
|
|
7121
|
+
}
|
|
7122
|
+
context.updateCharacterBounds(e.rangeStart, rects);
|
|
7123
|
+
});
|
|
7124
|
+
context.addEventListener("textformatupdate", e => {
|
|
7125
|
+
let deco = [];
|
|
7126
|
+
for (let format of e.getTextFormats()) {
|
|
7127
|
+
let lineStyle = format.underlineStyle, thickness = format.underlineThickness;
|
|
7128
|
+
if (lineStyle != "None" && thickness != "None") {
|
|
7129
|
+
let style = `text-decoration: underline ${lineStyle == "Dashed" ? "dashed " : lineStyle == "Squiggle" ? "wavy " : ""}${thickness == "Thin" ? 1 : 2}px`;
|
|
7130
|
+
deco.push(Decoration.mark({ attributes: { style } })
|
|
7131
|
+
.range(this.toEditorPos(format.rangeStart), this.toEditorPos(format.rangeEnd)));
|
|
7132
|
+
}
|
|
7133
|
+
}
|
|
7134
|
+
view.dispatch({ effects: setEditContextFormatting.of(Decoration.set(deco)) });
|
|
7135
|
+
});
|
|
7136
|
+
context.addEventListener("compositionstart", () => {
|
|
7137
|
+
if (view.inputState.composing < 0) {
|
|
7138
|
+
view.inputState.composing = 0;
|
|
7139
|
+
view.inputState.compositionFirstChange = true;
|
|
7140
|
+
}
|
|
7141
|
+
});
|
|
7142
|
+
context.addEventListener("compositionend", () => {
|
|
7143
|
+
view.inputState.composing = -1;
|
|
7144
|
+
view.inputState.compositionFirstChange = null;
|
|
7145
|
+
});
|
|
7146
|
+
this.measureReq = { read: view => {
|
|
7147
|
+
this.editContext.updateControlBounds(view.contentDOM.getBoundingClientRect());
|
|
7148
|
+
let sel = getSelection(view.root);
|
|
7149
|
+
if (sel && sel.rangeCount)
|
|
7150
|
+
this.editContext.updateSelectionBounds(sel.getRangeAt(0).getBoundingClientRect());
|
|
7151
|
+
} };
|
|
7152
|
+
}
|
|
7153
|
+
applyEdits(update) {
|
|
7154
|
+
let off = 0, abort = false, pending = this.pendingContextChange;
|
|
7155
|
+
update.changes.iterChanges((fromA, toA, _fromB, _toB, insert) => {
|
|
7156
|
+
if (abort)
|
|
7157
|
+
return;
|
|
7158
|
+
let dLen = insert.length - (toA - fromA);
|
|
7159
|
+
if (pending && toA >= pending.to) {
|
|
7160
|
+
if (pending.from == fromA && pending.to == toA && pending.insert.eq(insert)) {
|
|
7161
|
+
pending = this.pendingContextChange = null; // Match
|
|
7162
|
+
off += dLen;
|
|
7163
|
+
return;
|
|
7164
|
+
}
|
|
7165
|
+
else { // Mismatch, revert
|
|
7166
|
+
pending = null;
|
|
7167
|
+
this.revertPending(update.state);
|
|
7168
|
+
}
|
|
7169
|
+
}
|
|
7170
|
+
fromA += off;
|
|
7171
|
+
toA += off;
|
|
7172
|
+
if (toA <= this.from) { // Before the window
|
|
7173
|
+
this.from += dLen;
|
|
7174
|
+
this.to += dLen;
|
|
7175
|
+
}
|
|
7176
|
+
else if (fromA < this.to) { // Overlaps with window
|
|
7177
|
+
if (fromA < this.from || toA > this.to || (this.to - this.from) + insert.length > 30000 /* CxVp.MaxSize */) {
|
|
7178
|
+
abort = true;
|
|
7179
|
+
return;
|
|
7180
|
+
}
|
|
7181
|
+
this.editContext.updateText(this.toContextPos(fromA), this.toContextPos(toA), insert.toString());
|
|
7182
|
+
this.to += dLen;
|
|
7183
|
+
}
|
|
7184
|
+
off += dLen;
|
|
7185
|
+
});
|
|
7186
|
+
if (pending && !abort)
|
|
7187
|
+
this.revertPending(update.state);
|
|
7188
|
+
return !abort;
|
|
7189
|
+
}
|
|
7190
|
+
update(update) {
|
|
7191
|
+
if (!this.applyEdits(update) || !this.rangeIsValid(update.state)) {
|
|
7192
|
+
this.pendingContextChange = null;
|
|
7193
|
+
this.resetRange(update.state);
|
|
7194
|
+
this.editContext.updateText(0, this.editContext.text.length, update.state.doc.sliceString(this.from, this.to));
|
|
7195
|
+
this.setSelection(update.state);
|
|
7196
|
+
}
|
|
7197
|
+
else if (update.docChanged || update.selectionSet) {
|
|
7198
|
+
this.setSelection(update.state);
|
|
7199
|
+
}
|
|
7200
|
+
if (update.geometryChanged || update.docChanged || update.selectionSet)
|
|
7201
|
+
update.view.requestMeasure(this.measureReq);
|
|
7202
|
+
}
|
|
7203
|
+
resetRange(state) {
|
|
7204
|
+
let { head } = state.selection.main;
|
|
7205
|
+
this.from = Math.max(0, head - 10000 /* CxVp.Margin */);
|
|
7206
|
+
this.to = Math.min(state.doc.length, head + 10000 /* CxVp.Margin */);
|
|
7207
|
+
}
|
|
7208
|
+
revertPending(state) {
|
|
7209
|
+
let pending = this.pendingContextChange;
|
|
7210
|
+
this.pendingContextChange = null;
|
|
7211
|
+
this.editContext.updateText(this.toContextPos(pending.from), this.toContextPos(pending.to + pending.insert.length), state.doc.sliceString(pending.from, pending.to));
|
|
7212
|
+
}
|
|
7213
|
+
setSelection(state) {
|
|
7214
|
+
let { main } = state.selection;
|
|
7215
|
+
let start = this.toContextPos(Math.max(this.from, Math.min(this.to, main.anchor)));
|
|
7216
|
+
let end = this.toContextPos(main.head);
|
|
7217
|
+
if (this.editContext.selectionStart != start || this.editContext.selectionEnd != end)
|
|
7218
|
+
this.editContext.updateSelection(start, end);
|
|
7219
|
+
}
|
|
7220
|
+
rangeIsValid(state) {
|
|
7221
|
+
let { head } = state.selection.main;
|
|
7222
|
+
return !(this.from > 0 && head - this.from < 500 /* CxVp.MinMargin */ ||
|
|
7223
|
+
this.to < state.doc.length && this.to - head < 500 /* CxVp.MinMargin */ ||
|
|
7224
|
+
this.to - this.from > 10000 /* CxVp.Margin */ * 3);
|
|
7225
|
+
}
|
|
7226
|
+
toEditorPos(contextPos) { return contextPos + this.from; }
|
|
7227
|
+
toContextPos(editorPos) { return editorPos - this.from; }
|
|
7228
|
+
}
|
|
7032
7229
|
|
|
7033
7230
|
// The editor's update state machine looks something like this:
|
|
7034
7231
|
//
|
|
@@ -7851,6 +8048,8 @@ class EditorView {
|
|
|
7851
8048
|
calling this.
|
|
7852
8049
|
*/
|
|
7853
8050
|
destroy() {
|
|
8051
|
+
if (this.root.activeElement == this.contentDOM)
|
|
8052
|
+
this.contentDOM.blur();
|
|
7854
8053
|
for (let plugin of this.plugins)
|
|
7855
8054
|
plugin.destroy(this);
|
|
7856
8055
|
this.plugins = [];
|
package/dist/index.js
CHANGED
|
@@ -2364,6 +2364,7 @@ class ScrollTarget {
|
|
|
2364
2364
|
}
|
|
2365
2365
|
}
|
|
2366
2366
|
const scrollIntoView = /*@__PURE__*/StateEffect.define({ map: (t, ch) => t.map(ch) });
|
|
2367
|
+
const setEditContextFormatting = /*@__PURE__*/StateEffect.define();
|
|
2367
2368
|
/**
|
|
2368
2369
|
Log or report an unhandled exception in client code. Should
|
|
2369
2370
|
probably only be used by extension code that allows client code to
|
|
@@ -2700,10 +2701,11 @@ class DocView extends ContentView {
|
|
|
2700
2701
|
super();
|
|
2701
2702
|
this.view = view;
|
|
2702
2703
|
this.decorations = [];
|
|
2703
|
-
this.dynamicDecorationMap = [];
|
|
2704
|
+
this.dynamicDecorationMap = [false];
|
|
2704
2705
|
this.domChanged = null;
|
|
2705
2706
|
this.hasComposition = null;
|
|
2706
2707
|
this.markedForComposition = new Set;
|
|
2708
|
+
this.editContextFormatting = Decoration.none;
|
|
2707
2709
|
this.lastCompositionAfterCursor = false;
|
|
2708
2710
|
// Track a minimum width for the editor. When measuring sizes in
|
|
2709
2711
|
// measureVisibleLineHeights, this is updated to point at the width
|
|
@@ -2742,8 +2744,9 @@ class DocView extends ContentView {
|
|
|
2742
2744
|
this.minWidthTo = update.changes.mapPos(this.minWidthTo, 1);
|
|
2743
2745
|
}
|
|
2744
2746
|
}
|
|
2747
|
+
this.updateEditContextFormatting(update);
|
|
2745
2748
|
let readCompositionAt = -1;
|
|
2746
|
-
if (this.view.inputState.composing >= 0) {
|
|
2749
|
+
if (this.view.inputState.composing >= 0 && !this.view.observer.editContext) {
|
|
2747
2750
|
if ((_a = this.domChanged) === null || _a === void 0 ? void 0 : _a.newSel)
|
|
2748
2751
|
readCompositionAt = this.domChanged.newSel.head;
|
|
2749
2752
|
else if (!touchesComposition(update.changes, this.hasComposition) && !update.selectionSet)
|
|
@@ -2851,6 +2854,14 @@ class DocView extends ContentView {
|
|
|
2851
2854
|
if (composition)
|
|
2852
2855
|
this.fixCompositionDOM(composition);
|
|
2853
2856
|
}
|
|
2857
|
+
updateEditContextFormatting(update) {
|
|
2858
|
+
this.editContextFormatting = this.editContextFormatting.map(update.changes);
|
|
2859
|
+
for (let tr of update.transactions)
|
|
2860
|
+
for (let effect of tr.effects)
|
|
2861
|
+
if (effect.is(setEditContextFormatting)) {
|
|
2862
|
+
this.editContextFormatting = effect.value;
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2854
2865
|
compositionView(composition) {
|
|
2855
2866
|
let cur = new TextView(composition.text.nodeValue);
|
|
2856
2867
|
cur.flags |= 8 /* ViewFlag.Composition */;
|
|
@@ -3182,7 +3193,7 @@ class DocView extends ContentView {
|
|
|
3182
3193
|
return Decoration.set(deco);
|
|
3183
3194
|
}
|
|
3184
3195
|
updateDeco() {
|
|
3185
|
-
let i =
|
|
3196
|
+
let i = 1;
|
|
3186
3197
|
let allDeco = this.view.state.facet(decorations).map(d => {
|
|
3187
3198
|
let dynamic = this.dynamicDecorationMap[i++] = typeof d == "function";
|
|
3188
3199
|
return dynamic ? d(this.view) : d;
|
|
@@ -3198,6 +3209,7 @@ class DocView extends ContentView {
|
|
|
3198
3209
|
allDeco.push(RangeSet.join(outerDeco));
|
|
3199
3210
|
}
|
|
3200
3211
|
this.decorations = [
|
|
3212
|
+
this.editContextFormatting,
|
|
3201
3213
|
...allDeco,
|
|
3202
3214
|
this.computeBlockGapDeco(),
|
|
3203
3215
|
this.view.viewState.lineGapDeco
|
|
@@ -3891,6 +3903,7 @@ class InputState {
|
|
|
3891
3903
|
this.mouseSelection = mouseSelection;
|
|
3892
3904
|
}
|
|
3893
3905
|
update(update) {
|
|
3906
|
+
this.view.observer.update(update);
|
|
3894
3907
|
if (this.mouseSelection)
|
|
3895
3908
|
this.mouseSelection.update(update);
|
|
3896
3909
|
if (this.draggedContent && update.docChanged)
|
|
@@ -4488,6 +4501,8 @@ observers.blur = view => {
|
|
|
4488
4501
|
updateForFocusChange(view);
|
|
4489
4502
|
};
|
|
4490
4503
|
observers.compositionstart = observers.compositionupdate = view => {
|
|
4504
|
+
if (view.observer.editContext)
|
|
4505
|
+
return; // Composition handled by edit context
|
|
4491
4506
|
if (view.inputState.compositionFirstChange == null)
|
|
4492
4507
|
view.inputState.compositionFirstChange = true;
|
|
4493
4508
|
if (view.inputState.composing < 0) {
|
|
@@ -4496,6 +4511,8 @@ observers.compositionstart = observers.compositionupdate = view => {
|
|
|
4496
4511
|
}
|
|
4497
4512
|
};
|
|
4498
4513
|
observers.compositionend = view => {
|
|
4514
|
+
if (view.observer.editContext)
|
|
4515
|
+
return; // Composition handled by edit context
|
|
4499
4516
|
view.inputState.composing = -1;
|
|
4500
4517
|
view.inputState.compositionEndedAt = Date.now();
|
|
4501
4518
|
view.inputState.compositionPendingKey = true;
|
|
@@ -5688,12 +5705,12 @@ class ViewState {
|
|
|
5688
5705
|
}
|
|
5689
5706
|
gaps.push(gap);
|
|
5690
5707
|
};
|
|
5691
|
-
|
|
5692
|
-
if (line.length < doubleMargin)
|
|
5693
|
-
|
|
5708
|
+
let checkLine = (line) => {
|
|
5709
|
+
if (line.length < doubleMargin || line.type != BlockType.Text)
|
|
5710
|
+
return;
|
|
5694
5711
|
let structure = lineStructure(line.from, line.to, this.stateDeco);
|
|
5695
5712
|
if (structure.total < doubleMargin)
|
|
5696
|
-
|
|
5713
|
+
return;
|
|
5697
5714
|
let target = this.scrollTarget ? this.scrollTarget.range.head : null;
|
|
5698
5715
|
let viewFrom, viewTo;
|
|
5699
5716
|
if (wrapping) {
|
|
@@ -5733,6 +5750,12 @@ class ViewState {
|
|
|
5733
5750
|
addGap(line.from, viewFrom, line, structure);
|
|
5734
5751
|
if (viewTo < line.to)
|
|
5735
5752
|
addGap(viewTo, line.to, line, structure);
|
|
5753
|
+
};
|
|
5754
|
+
for (let line of this.viewportLines) {
|
|
5755
|
+
if (Array.isArray(line.type))
|
|
5756
|
+
line.type.forEach(checkLine);
|
|
5757
|
+
else
|
|
5758
|
+
checkLine(line);
|
|
5736
5759
|
}
|
|
5737
5760
|
return gaps;
|
|
5738
5761
|
}
|
|
@@ -6392,35 +6415,7 @@ function applyDOMChange(view, domChange) {
|
|
|
6392
6415
|
change = { from: sel.from, to: sel.to, insert: Text.of([" "]) };
|
|
6393
6416
|
}
|
|
6394
6417
|
if (change) {
|
|
6395
|
-
|
|
6396
|
-
return true;
|
|
6397
|
-
// Android browsers don't fire reasonable key events for enter,
|
|
6398
|
-
// backspace, or delete. So this detects changes that look like
|
|
6399
|
-
// they're caused by those keys, and reinterprets them as key
|
|
6400
|
-
// events. (Some of these keys are also handled by beforeinput
|
|
6401
|
-
// events and the pendingAndroidKey mechanism, but that's not
|
|
6402
|
-
// reliable in all situations.)
|
|
6403
|
-
if (browser.android &&
|
|
6404
|
-
((change.to == sel.to &&
|
|
6405
|
-
// GBoard will sometimes remove a space it just inserted
|
|
6406
|
-
// after a completion when you press enter
|
|
6407
|
-
(change.from == sel.from || change.from == sel.from - 1 && view.state.sliceDoc(change.from, sel.from) == " ") &&
|
|
6408
|
-
change.insert.length == 1 && change.insert.lines == 2 &&
|
|
6409
|
-
dispatchKey(view.contentDOM, "Enter", 13)) ||
|
|
6410
|
-
((change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 ||
|
|
6411
|
-
lastKey == 8 && change.insert.length < change.to - change.from && change.to > sel.head) &&
|
|
6412
|
-
dispatchKey(view.contentDOM, "Backspace", 8)) ||
|
|
6413
|
-
(change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
|
|
6414
|
-
dispatchKey(view.contentDOM, "Delete", 46))))
|
|
6415
|
-
return true;
|
|
6416
|
-
let text = change.insert.toString();
|
|
6417
|
-
if (view.inputState.composing >= 0)
|
|
6418
|
-
view.inputState.composing++;
|
|
6419
|
-
let defaultTr;
|
|
6420
|
-
let defaultInsert = () => defaultTr || (defaultTr = applyDefaultInsert(view, change, newSel));
|
|
6421
|
-
if (!view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text, defaultInsert)))
|
|
6422
|
-
view.dispatch(defaultInsert());
|
|
6423
|
-
return true;
|
|
6418
|
+
return applyDOMChangeInner(view, change, newSel, lastKey);
|
|
6424
6419
|
}
|
|
6425
6420
|
else if (newSel && !newSel.main.eq(sel)) {
|
|
6426
6421
|
let scrollIntoView = false, userEvent = "select";
|
|
@@ -6436,6 +6431,38 @@ function applyDOMChange(view, domChange) {
|
|
|
6436
6431
|
return false;
|
|
6437
6432
|
}
|
|
6438
6433
|
}
|
|
6434
|
+
function applyDOMChangeInner(view, change, newSel, lastKey = -1) {
|
|
6435
|
+
if (browser.ios && view.inputState.flushIOSKey(change))
|
|
6436
|
+
return true;
|
|
6437
|
+
let sel = view.state.selection.main;
|
|
6438
|
+
// Android browsers don't fire reasonable key events for enter,
|
|
6439
|
+
// backspace, or delete. So this detects changes that look like
|
|
6440
|
+
// they're caused by those keys, and reinterprets them as key
|
|
6441
|
+
// events. (Some of these keys are also handled by beforeinput
|
|
6442
|
+
// events and the pendingAndroidKey mechanism, but that's not
|
|
6443
|
+
// reliable in all situations.)
|
|
6444
|
+
if (browser.android &&
|
|
6445
|
+
((change.to == sel.to &&
|
|
6446
|
+
// GBoard will sometimes remove a space it just inserted
|
|
6447
|
+
// after a completion when you press enter
|
|
6448
|
+
(change.from == sel.from || change.from == sel.from - 1 && view.state.sliceDoc(change.from, sel.from) == " ") &&
|
|
6449
|
+
change.insert.length == 1 && change.insert.lines == 2 &&
|
|
6450
|
+
dispatchKey(view.contentDOM, "Enter", 13)) ||
|
|
6451
|
+
((change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 ||
|
|
6452
|
+
lastKey == 8 && change.insert.length < change.to - change.from && change.to > sel.head) &&
|
|
6453
|
+
dispatchKey(view.contentDOM, "Backspace", 8)) ||
|
|
6454
|
+
(change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
|
|
6455
|
+
dispatchKey(view.contentDOM, "Delete", 46))))
|
|
6456
|
+
return true;
|
|
6457
|
+
let text = change.insert.toString();
|
|
6458
|
+
if (view.inputState.composing >= 0)
|
|
6459
|
+
view.inputState.composing++;
|
|
6460
|
+
let defaultTr;
|
|
6461
|
+
let defaultInsert = () => defaultTr || (defaultTr = applyDefaultInsert(view, change, newSel));
|
|
6462
|
+
if (!view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text, defaultInsert)))
|
|
6463
|
+
view.dispatch(defaultInsert());
|
|
6464
|
+
return true;
|
|
6465
|
+
}
|
|
6439
6466
|
function applyDefaultInsert(view, change, newSel) {
|
|
6440
6467
|
let tr, startState = view.state, sel = startState.selection.main;
|
|
6441
6468
|
if (change.from >= sel.from && change.to <= sel.to && change.to - change.from >= (sel.to - sel.from) / 3 &&
|
|
@@ -6562,6 +6589,7 @@ class DOMObserver {
|
|
|
6562
6589
|
constructor(view) {
|
|
6563
6590
|
this.view = view;
|
|
6564
6591
|
this.active = false;
|
|
6592
|
+
this.editContext = null;
|
|
6565
6593
|
// The known selection. Kept in our own object, as opposed to just
|
|
6566
6594
|
// directly accessing the selection because:
|
|
6567
6595
|
// - Safari doesn't report the right selection in shadow DOM
|
|
@@ -6606,6 +6634,11 @@ class DOMObserver {
|
|
|
6606
6634
|
else
|
|
6607
6635
|
this.flush();
|
|
6608
6636
|
});
|
|
6637
|
+
if (window.EditContext && view.constructor.EDIT_CONTEXT === true) {
|
|
6638
|
+
this.editContext = new EditContextManager(view);
|
|
6639
|
+
if (view.state.facet(editable))
|
|
6640
|
+
view.contentDOM.editContext = this.editContext.editContext;
|
|
6641
|
+
}
|
|
6609
6642
|
if (useCharData)
|
|
6610
6643
|
this.onCharData = (event) => {
|
|
6611
6644
|
this.queue.push({ target: event.target,
|
|
@@ -6656,6 +6689,8 @@ class DOMObserver {
|
|
|
6656
6689
|
onScroll(e) {
|
|
6657
6690
|
if (this.intersecting)
|
|
6658
6691
|
this.flush(false);
|
|
6692
|
+
if (this.editContext)
|
|
6693
|
+
this.view.requestMeasure(this.editContext.measureReq);
|
|
6659
6694
|
this.onScrollChanged(e);
|
|
6660
6695
|
}
|
|
6661
6696
|
onResize() {
|
|
@@ -6965,6 +7000,13 @@ class DOMObserver {
|
|
|
6965
7000
|
win.removeEventListener("beforeprint", this.onPrint);
|
|
6966
7001
|
win.document.removeEventListener("selectionchange", this.onSelectionChange);
|
|
6967
7002
|
}
|
|
7003
|
+
update(update) {
|
|
7004
|
+
if (this.editContext) {
|
|
7005
|
+
this.editContext.update(update);
|
|
7006
|
+
if (update.startState.facet(editable) != update.state.facet(editable))
|
|
7007
|
+
update.view.contentDOM.editContext = update.state.facet(editable) ? this.editContext.editContext : null;
|
|
7008
|
+
}
|
|
7009
|
+
}
|
|
6968
7010
|
destroy() {
|
|
6969
7011
|
var _a, _b, _c;
|
|
6970
7012
|
this.stop();
|
|
@@ -7024,6 +7066,161 @@ function safariSelectionRangeHack(view, selection) {
|
|
|
7024
7066
|
view.contentDOM.removeEventListener("beforeinput", read, true);
|
|
7025
7067
|
return found ? buildSelectionRangeFromRange(view, found) : null;
|
|
7026
7068
|
}
|
|
7069
|
+
class EditContextManager {
|
|
7070
|
+
constructor(view) {
|
|
7071
|
+
// The document window for which the text in the context is
|
|
7072
|
+
// maintained. For large documents, this may be smaller than the
|
|
7073
|
+
// editor document. This window always includes the selection head.
|
|
7074
|
+
this.from = 0;
|
|
7075
|
+
this.to = 0;
|
|
7076
|
+
// When applying a transaction, this is used to compare the change
|
|
7077
|
+
// made to the context content to the change in the transaction in
|
|
7078
|
+
// order to make the minimal changes to the context (since touching
|
|
7079
|
+
// that sometimes breaks series of multiple edits made for a single
|
|
7080
|
+
// user action on some Android keyboards)
|
|
7081
|
+
this.pendingContextChange = null;
|
|
7082
|
+
this.resetRange(view.state);
|
|
7083
|
+
let context = this.editContext = new window.EditContext({
|
|
7084
|
+
text: view.state.doc.sliceString(this.from, this.to),
|
|
7085
|
+
selectionStart: this.toContextPos(Math.max(this.from, Math.min(this.to, view.state.selection.main.anchor))),
|
|
7086
|
+
selectionEnd: this.toContextPos(view.state.selection.main.head)
|
|
7087
|
+
});
|
|
7088
|
+
context.addEventListener("textupdate", e => {
|
|
7089
|
+
let { anchor } = view.state.selection.main;
|
|
7090
|
+
let change = { from: this.toEditorPos(e.updateRangeStart),
|
|
7091
|
+
to: this.toEditorPos(e.updateRangeEnd),
|
|
7092
|
+
insert: Text.of(e.text.split("\n")) };
|
|
7093
|
+
// If the window doesn't include the anchor, assume changes
|
|
7094
|
+
// adjacent to a side go up to the anchor.
|
|
7095
|
+
if (change.from == this.from && anchor < this.from)
|
|
7096
|
+
change.from = anchor;
|
|
7097
|
+
else if (change.to == this.to && anchor > this.to)
|
|
7098
|
+
change.to = anchor;
|
|
7099
|
+
// Edit context sometimes fire empty changes
|
|
7100
|
+
if (change.from == change.to && !change.insert.length)
|
|
7101
|
+
return;
|
|
7102
|
+
this.pendingContextChange = change;
|
|
7103
|
+
applyDOMChangeInner(view, change, EditorSelection.single(this.toEditorPos(e.selectionStart), this.toEditorPos(e.selectionEnd)));
|
|
7104
|
+
// If the transaction didn't flush our change, revert it so
|
|
7105
|
+
// that the context is in sync with the editor state again.
|
|
7106
|
+
if (this.pendingContextChange)
|
|
7107
|
+
this.revertPending(view.state);
|
|
7108
|
+
});
|
|
7109
|
+
context.addEventListener("characterboundsupdate", e => {
|
|
7110
|
+
let rects = [], prev = null;
|
|
7111
|
+
for (let i = this.toEditorPos(e.rangeStart), end = this.toEditorPos(e.rangeEnd); i < end; i++) {
|
|
7112
|
+
let rect = view.coordsForChar(i);
|
|
7113
|
+
prev = (rect && new DOMRect(rect.left, rect.right, rect.right - rect.left, rect.bottom - rect.top))
|
|
7114
|
+
|| prev || new DOMRect;
|
|
7115
|
+
rects.push(prev);
|
|
7116
|
+
}
|
|
7117
|
+
context.updateCharacterBounds(e.rangeStart, rects);
|
|
7118
|
+
});
|
|
7119
|
+
context.addEventListener("textformatupdate", e => {
|
|
7120
|
+
let deco = [];
|
|
7121
|
+
for (let format of e.getTextFormats()) {
|
|
7122
|
+
let lineStyle = format.underlineStyle, thickness = format.underlineThickness;
|
|
7123
|
+
if (lineStyle != "None" && thickness != "None") {
|
|
7124
|
+
let style = `text-decoration: underline ${lineStyle == "Dashed" ? "dashed " : lineStyle == "Squiggle" ? "wavy " : ""}${thickness == "Thin" ? 1 : 2}px`;
|
|
7125
|
+
deco.push(Decoration.mark({ attributes: { style } })
|
|
7126
|
+
.range(this.toEditorPos(format.rangeStart), this.toEditorPos(format.rangeEnd)));
|
|
7127
|
+
}
|
|
7128
|
+
}
|
|
7129
|
+
view.dispatch({ effects: setEditContextFormatting.of(Decoration.set(deco)) });
|
|
7130
|
+
});
|
|
7131
|
+
context.addEventListener("compositionstart", () => {
|
|
7132
|
+
if (view.inputState.composing < 0) {
|
|
7133
|
+
view.inputState.composing = 0;
|
|
7134
|
+
view.inputState.compositionFirstChange = true;
|
|
7135
|
+
}
|
|
7136
|
+
});
|
|
7137
|
+
context.addEventListener("compositionend", () => {
|
|
7138
|
+
view.inputState.composing = -1;
|
|
7139
|
+
view.inputState.compositionFirstChange = null;
|
|
7140
|
+
});
|
|
7141
|
+
this.measureReq = { read: view => {
|
|
7142
|
+
this.editContext.updateControlBounds(view.contentDOM.getBoundingClientRect());
|
|
7143
|
+
let sel = getSelection(view.root);
|
|
7144
|
+
if (sel && sel.rangeCount)
|
|
7145
|
+
this.editContext.updateSelectionBounds(sel.getRangeAt(0).getBoundingClientRect());
|
|
7146
|
+
} };
|
|
7147
|
+
}
|
|
7148
|
+
applyEdits(update) {
|
|
7149
|
+
let off = 0, abort = false, pending = this.pendingContextChange;
|
|
7150
|
+
update.changes.iterChanges((fromA, toA, _fromB, _toB, insert) => {
|
|
7151
|
+
if (abort)
|
|
7152
|
+
return;
|
|
7153
|
+
let dLen = insert.length - (toA - fromA);
|
|
7154
|
+
if (pending && toA >= pending.to) {
|
|
7155
|
+
if (pending.from == fromA && pending.to == toA && pending.insert.eq(insert)) {
|
|
7156
|
+
pending = this.pendingContextChange = null; // Match
|
|
7157
|
+
off += dLen;
|
|
7158
|
+
return;
|
|
7159
|
+
}
|
|
7160
|
+
else { // Mismatch, revert
|
|
7161
|
+
pending = null;
|
|
7162
|
+
this.revertPending(update.state);
|
|
7163
|
+
}
|
|
7164
|
+
}
|
|
7165
|
+
fromA += off;
|
|
7166
|
+
toA += off;
|
|
7167
|
+
if (toA <= this.from) { // Before the window
|
|
7168
|
+
this.from += dLen;
|
|
7169
|
+
this.to += dLen;
|
|
7170
|
+
}
|
|
7171
|
+
else if (fromA < this.to) { // Overlaps with window
|
|
7172
|
+
if (fromA < this.from || toA > this.to || (this.to - this.from) + insert.length > 30000 /* CxVp.MaxSize */) {
|
|
7173
|
+
abort = true;
|
|
7174
|
+
return;
|
|
7175
|
+
}
|
|
7176
|
+
this.editContext.updateText(this.toContextPos(fromA), this.toContextPos(toA), insert.toString());
|
|
7177
|
+
this.to += dLen;
|
|
7178
|
+
}
|
|
7179
|
+
off += dLen;
|
|
7180
|
+
});
|
|
7181
|
+
if (pending && !abort)
|
|
7182
|
+
this.revertPending(update.state);
|
|
7183
|
+
return !abort;
|
|
7184
|
+
}
|
|
7185
|
+
update(update) {
|
|
7186
|
+
if (!this.applyEdits(update) || !this.rangeIsValid(update.state)) {
|
|
7187
|
+
this.pendingContextChange = null;
|
|
7188
|
+
this.resetRange(update.state);
|
|
7189
|
+
this.editContext.updateText(0, this.editContext.text.length, update.state.doc.sliceString(this.from, this.to));
|
|
7190
|
+
this.setSelection(update.state);
|
|
7191
|
+
}
|
|
7192
|
+
else if (update.docChanged || update.selectionSet) {
|
|
7193
|
+
this.setSelection(update.state);
|
|
7194
|
+
}
|
|
7195
|
+
if (update.geometryChanged || update.docChanged || update.selectionSet)
|
|
7196
|
+
update.view.requestMeasure(this.measureReq);
|
|
7197
|
+
}
|
|
7198
|
+
resetRange(state) {
|
|
7199
|
+
let { head } = state.selection.main;
|
|
7200
|
+
this.from = Math.max(0, head - 10000 /* CxVp.Margin */);
|
|
7201
|
+
this.to = Math.min(state.doc.length, head + 10000 /* CxVp.Margin */);
|
|
7202
|
+
}
|
|
7203
|
+
revertPending(state) {
|
|
7204
|
+
let pending = this.pendingContextChange;
|
|
7205
|
+
this.pendingContextChange = null;
|
|
7206
|
+
this.editContext.updateText(this.toContextPos(pending.from), this.toContextPos(pending.to + pending.insert.length), state.doc.sliceString(pending.from, pending.to));
|
|
7207
|
+
}
|
|
7208
|
+
setSelection(state) {
|
|
7209
|
+
let { main } = state.selection;
|
|
7210
|
+
let start = this.toContextPos(Math.max(this.from, Math.min(this.to, main.anchor)));
|
|
7211
|
+
let end = this.toContextPos(main.head);
|
|
7212
|
+
if (this.editContext.selectionStart != start || this.editContext.selectionEnd != end)
|
|
7213
|
+
this.editContext.updateSelection(start, end);
|
|
7214
|
+
}
|
|
7215
|
+
rangeIsValid(state) {
|
|
7216
|
+
let { head } = state.selection.main;
|
|
7217
|
+
return !(this.from > 0 && head - this.from < 500 /* CxVp.MinMargin */ ||
|
|
7218
|
+
this.to < state.doc.length && this.to - head < 500 /* CxVp.MinMargin */ ||
|
|
7219
|
+
this.to - this.from > 10000 /* CxVp.Margin */ * 3);
|
|
7220
|
+
}
|
|
7221
|
+
toEditorPos(contextPos) { return contextPos + this.from; }
|
|
7222
|
+
toContextPos(editorPos) { return editorPos - this.from; }
|
|
7223
|
+
}
|
|
7027
7224
|
|
|
7028
7225
|
// The editor's update state machine looks something like this:
|
|
7029
7226
|
//
|
|
@@ -7846,6 +8043,8 @@ class EditorView {
|
|
|
7846
8043
|
calling this.
|
|
7847
8044
|
*/
|
|
7848
8045
|
destroy() {
|
|
8046
|
+
if (this.root.activeElement == this.contentDOM)
|
|
8047
|
+
this.contentDOM.blur();
|
|
7849
8048
|
for (let plugin of this.plugins)
|
|
7850
8049
|
plugin.destroy(this);
|
|
7851
8050
|
this.plugins = [];
|