@codemirror/view 6.2.5 → 6.3.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 +26 -0
- package/dist/index.cjs +426 -301
- package/dist/index.d.ts +6 -1
- package/dist/index.js +426 -301
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -871,7 +871,7 @@ class WidgetView extends ContentView {
|
|
|
871
871
|
if (pos > 0 ? i == 0 : i == rects.length - 1 || rect.top < rect.bottom)
|
|
872
872
|
break;
|
|
873
873
|
}
|
|
874
|
-
return (
|
|
874
|
+
return flattenRect(rect, this.side > 0);
|
|
875
875
|
}
|
|
876
876
|
get isEditable() { return false; }
|
|
877
877
|
destroy() {
|
|
@@ -1024,7 +1024,6 @@ function inlineDOMAtPos(parent, pos) {
|
|
|
1024
1024
|
break;
|
|
1025
1025
|
off = end;
|
|
1026
1026
|
}
|
|
1027
|
-
// if (i) return DOMPos.after(children[i - 1].dom!)
|
|
1028
1027
|
for (let j = i; j > 0; j--) {
|
|
1029
1028
|
let prev = children[j - 1];
|
|
1030
1029
|
if (prev.dom.parentNode == dom)
|
|
@@ -1051,21 +1050,59 @@ function joinInlineInto(parent, view, open) {
|
|
|
1051
1050
|
parent.length += view.length;
|
|
1052
1051
|
}
|
|
1053
1052
|
function coordsInChildren(view, pos, side) {
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1053
|
+
if (!view.children.length)
|
|
1054
|
+
return fallbackRect(view);
|
|
1055
|
+
return (side <= 0 ? coordsInChildrenBefore : coordsInChildrenAfter)(view, pos);
|
|
1056
|
+
}
|
|
1057
|
+
function coordsInChildrenBefore(view, pos) {
|
|
1058
|
+
// Find the last leaf in the tree that touches pos and doesn't have getSide() > 0
|
|
1059
|
+
let found = null, foundPos = -1;
|
|
1060
|
+
function scan(view, pos) {
|
|
1061
|
+
for (let i = 0, off = 0; i < view.children.length && off <= pos; i++) {
|
|
1062
|
+
let child = view.children[i], end = off + child.length;
|
|
1063
|
+
if (end >= pos) {
|
|
1064
|
+
if (child.children.length) {
|
|
1065
|
+
if (scan(child, pos - off))
|
|
1066
|
+
return true;
|
|
1067
|
+
}
|
|
1068
|
+
else if (end >= pos) {
|
|
1069
|
+
if (end == pos && child.getSide() > 0)
|
|
1070
|
+
return true;
|
|
1071
|
+
found = child;
|
|
1072
|
+
foundPos = pos - off;
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
off = end;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
scan(view, pos);
|
|
1079
|
+
return found ? found.coordsAt(Math.max(0, foundPos), -1) : coordsInChildrenAfter(view, pos);
|
|
1080
|
+
}
|
|
1081
|
+
function coordsInChildrenAfter(view, pos) {
|
|
1082
|
+
// Find the first leaf in the tree that touches pos and doesn't have getSide() < 0
|
|
1083
|
+
let found = null, foundPos = -1;
|
|
1084
|
+
function scan(view, pos) {
|
|
1085
|
+
for (let i = view.children.length - 1, off = view.length; i >= 0 && off >= pos; i--) {
|
|
1086
|
+
let child = view.children[i];
|
|
1087
|
+
off -= child.length;
|
|
1088
|
+
if (off <= pos) {
|
|
1089
|
+
if (child.children.length) {
|
|
1090
|
+
if (scan(child, pos - off))
|
|
1091
|
+
return true;
|
|
1092
|
+
}
|
|
1093
|
+
else if (off <= pos) {
|
|
1094
|
+
if (off == pos && child.getSide() < 0)
|
|
1095
|
+
return true;
|
|
1096
|
+
found = child;
|
|
1097
|
+
foundPos = pos - off;
|
|
1098
|
+
}
|
|
1063
1099
|
}
|
|
1064
|
-
let rect = child.coordsAt(Math.max(0, pos - off), side);
|
|
1065
|
-
return flatten && rect ? flattenRect(rect, side < 0) : rect;
|
|
1066
1100
|
}
|
|
1067
|
-
off = end;
|
|
1068
1101
|
}
|
|
1102
|
+
scan(view, pos);
|
|
1103
|
+
return found ? found.coordsAt(Math.max(0, foundPos), 1) : coordsInChildrenBefore(view, pos);
|
|
1104
|
+
}
|
|
1105
|
+
function fallbackRect(view) {
|
|
1069
1106
|
let last = view.dom.lastChild;
|
|
1070
1107
|
if (!last)
|
|
1071
1108
|
return view.dom.getBoundingClientRect();
|
|
@@ -1683,7 +1720,7 @@ class ContentBuilder {
|
|
|
1683
1720
|
this.addBlockWidget(new BlockWidgetView(deco.widget || new NullWidget("div"), len, type));
|
|
1684
1721
|
}
|
|
1685
1722
|
else {
|
|
1686
|
-
let view = WidgetView.create(deco.widget || new NullWidget("span"), len, deco.startSide);
|
|
1723
|
+
let view = WidgetView.create(deco.widget || new NullWidget("span"), len, len ? 0 : deco.startSide);
|
|
1687
1724
|
let cursorBefore = this.atCursorPos && !view.isEditable && openStart <= active.length && (from < to || deco.startSide > 0);
|
|
1688
1725
|
let cursorAfter = !view.isEditable && (from < to || deco.startSide <= 0);
|
|
1689
1726
|
let line = this.getLine();
|
|
@@ -4820,7 +4857,7 @@ class ViewState {
|
|
|
4820
4857
|
this.updateForViewport();
|
|
4821
4858
|
if (updateLines)
|
|
4822
4859
|
this.updateViewportLines();
|
|
4823
|
-
if (this.lineGaps.length || this.viewport.to - this.viewport.from >
|
|
4860
|
+
if (this.lineGaps.length || this.viewport.to - this.viewport.from > (2000 /* LG.Margin */ << 1))
|
|
4824
4861
|
this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
|
|
4825
4862
|
update.flags |= this.computeVisibleRanges();
|
|
4826
4863
|
if (scrollTarget)
|
|
@@ -4876,7 +4913,7 @@ class ViewState {
|
|
|
4876
4913
|
refresh = true;
|
|
4877
4914
|
if (refresh || oracle.lineWrapping && Math.abs(contentWidth - this.contentDOMWidth) > oracle.charWidth) {
|
|
4878
4915
|
let { lineHeight, charWidth } = view.docView.measureTextSize();
|
|
4879
|
-
refresh = oracle.refresh(whiteSpace, lineHeight, charWidth, contentWidth / charWidth, lineHeights);
|
|
4916
|
+
refresh = lineHeight > 0 && oracle.refresh(whiteSpace, lineHeight, charWidth, contentWidth / charWidth, lineHeights);
|
|
4880
4917
|
if (refresh) {
|
|
4881
4918
|
view.docView.minWidth = 0;
|
|
4882
4919
|
result |= 8 /* UpdateFlag.Geometry */;
|
|
@@ -4901,8 +4938,8 @@ class ViewState {
|
|
|
4901
4938
|
this.updateForViewport();
|
|
4902
4939
|
if ((result & 2 /* UpdateFlag.Height */) || viewportChange)
|
|
4903
4940
|
this.updateViewportLines();
|
|
4904
|
-
if (this.lineGaps.length || this.viewport.to - this.viewport.from >
|
|
4905
|
-
this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps));
|
|
4941
|
+
if (this.lineGaps.length || this.viewport.to - this.viewport.from > (2000 /* LG.Margin */ << 1))
|
|
4942
|
+
this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps, view));
|
|
4906
4943
|
result |= this.computeVisibleRanges();
|
|
4907
4944
|
if (this.mustEnforceCursorAssoc) {
|
|
4908
4945
|
this.mustEnforceCursorAssoc = false;
|
|
@@ -4973,46 +5010,86 @@ class ViewState {
|
|
|
4973
5010
|
// since actual DOM coordinates aren't always available and
|
|
4974
5011
|
// predictable. Relies on generous margins (see LG.Margin) to hide
|
|
4975
5012
|
// the artifacts this might produce from the user.
|
|
4976
|
-
ensureLineGaps(current) {
|
|
5013
|
+
ensureLineGaps(current, mayMeasure) {
|
|
5014
|
+
let wrapping = this.heightOracle.lineWrapping;
|
|
5015
|
+
let margin = wrapping ? 10000 /* LG.MarginWrap */ : 2000 /* LG.Margin */, halfMargin = margin >> 1, doubleMargin = margin << 1;
|
|
5016
|
+
// The non-wrapping logic won't work at all in predominantly right-to-left text.
|
|
5017
|
+
if (this.defaultTextDirection != Direction.LTR && !wrapping)
|
|
5018
|
+
return [];
|
|
4977
5019
|
let gaps = [];
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
5020
|
+
let addGap = (from, to, line, structure) => {
|
|
5021
|
+
if (to - from < halfMargin)
|
|
5022
|
+
return;
|
|
5023
|
+
let sel = this.state.selection.main, avoid = [sel.from];
|
|
5024
|
+
if (!sel.empty)
|
|
5025
|
+
avoid.push(sel.to);
|
|
5026
|
+
for (let pos of avoid) {
|
|
5027
|
+
if (pos > from && pos < to) {
|
|
5028
|
+
addGap(from, pos - 10 /* LG.SelectionMargin */, line, structure);
|
|
5029
|
+
addGap(pos + 10 /* LG.SelectionMargin */, to, line, structure);
|
|
5030
|
+
return;
|
|
5031
|
+
}
|
|
5032
|
+
}
|
|
5033
|
+
let gap = find(current, gap => gap.from >= line.from && gap.to <= line.to &&
|
|
5034
|
+
Math.abs(gap.from - from) < halfMargin && Math.abs(gap.to - to) < halfMargin &&
|
|
5035
|
+
!avoid.some(pos => gap.from < pos && gap.to > pos));
|
|
5036
|
+
if (!gap) {
|
|
5037
|
+
// When scrolling down, snap gap ends to line starts to avoid shifts in wrapping
|
|
5038
|
+
if (to < line.to && mayMeasure && wrapping &&
|
|
5039
|
+
mayMeasure.visibleRanges.some(r => r.from <= to && r.to >= to)) {
|
|
5040
|
+
let lineStart = mayMeasure.moveToLineBoundary(EditorSelection.cursor(to), false, true).head;
|
|
5041
|
+
if (lineStart > from)
|
|
5042
|
+
to = lineStart;
|
|
5043
|
+
}
|
|
5044
|
+
gap = new LineGap(from, to, this.gapSize(line, from, to, structure));
|
|
5045
|
+
}
|
|
5046
|
+
gaps.push(gap);
|
|
5047
|
+
};
|
|
4981
5048
|
for (let line of this.viewportLines) {
|
|
4982
|
-
if (line.length <
|
|
5049
|
+
if (line.length < doubleMargin)
|
|
4983
5050
|
continue;
|
|
4984
5051
|
let structure = lineStructure(line.from, line.to, this.stateDeco);
|
|
4985
|
-
if (structure.total <
|
|
5052
|
+
if (structure.total < doubleMargin)
|
|
4986
5053
|
continue;
|
|
5054
|
+
let target = this.scrollTarget ? this.scrollTarget.range.head : null;
|
|
4987
5055
|
let viewFrom, viewTo;
|
|
4988
|
-
if (
|
|
4989
|
-
let marginHeight = (
|
|
4990
|
-
|
|
4991
|
-
|
|
5056
|
+
if (wrapping) {
|
|
5057
|
+
let marginHeight = (margin / this.heightOracle.lineLength) * this.heightOracle.lineHeight;
|
|
5058
|
+
let top, bot;
|
|
5059
|
+
if (target != null) {
|
|
5060
|
+
let targetFrac = findFraction(structure, target);
|
|
5061
|
+
let spaceFrac = ((this.visibleBottom - this.visibleTop) / 2 + marginHeight) / line.height;
|
|
5062
|
+
top = targetFrac - spaceFrac;
|
|
5063
|
+
bot = targetFrac + spaceFrac;
|
|
5064
|
+
}
|
|
5065
|
+
else {
|
|
5066
|
+
top = (this.visibleTop - line.top - marginHeight) / line.height;
|
|
5067
|
+
bot = (this.visibleBottom - line.top + marginHeight) / line.height;
|
|
5068
|
+
}
|
|
5069
|
+
viewFrom = findPosition(structure, top);
|
|
5070
|
+
viewTo = findPosition(structure, bot);
|
|
4992
5071
|
}
|
|
4993
5072
|
else {
|
|
4994
5073
|
let totalWidth = structure.total * this.heightOracle.charWidth;
|
|
4995
|
-
let marginWidth =
|
|
4996
|
-
|
|
4997
|
-
|
|
5074
|
+
let marginWidth = margin * this.heightOracle.charWidth;
|
|
5075
|
+
let left, right;
|
|
5076
|
+
if (target != null) {
|
|
5077
|
+
let targetFrac = findFraction(structure, target);
|
|
5078
|
+
let spaceFrac = ((this.pixelViewport.right - this.pixelViewport.left) / 2 + marginWidth) / totalWidth;
|
|
5079
|
+
left = targetFrac - spaceFrac;
|
|
5080
|
+
right = targetFrac + spaceFrac;
|
|
5081
|
+
}
|
|
5082
|
+
else {
|
|
5083
|
+
left = (this.pixelViewport.left - marginWidth) / totalWidth;
|
|
5084
|
+
right = (this.pixelViewport.right + marginWidth) / totalWidth;
|
|
5085
|
+
}
|
|
5086
|
+
viewFrom = findPosition(structure, left);
|
|
5087
|
+
viewTo = findPosition(structure, right);
|
|
4998
5088
|
}
|
|
4999
|
-
let outside = [];
|
|
5000
5089
|
if (viewFrom > line.from)
|
|
5001
|
-
|
|
5090
|
+
addGap(line.from, viewFrom, line, structure);
|
|
5002
5091
|
if (viewTo < line.to)
|
|
5003
|
-
|
|
5004
|
-
let sel = this.state.selection.main;
|
|
5005
|
-
// Make sure the gaps don't cover a selection end
|
|
5006
|
-
if (sel.from >= line.from && sel.from <= line.to)
|
|
5007
|
-
cutRange(outside, sel.from - 10 /* LG.SelectionMargin */, sel.from + 10 /* LG.SelectionMargin */);
|
|
5008
|
-
if (!sel.empty && sel.to >= line.from && sel.to <= line.to)
|
|
5009
|
-
cutRange(outside, sel.to - 10 /* LG.SelectionMargin */, sel.to + 10 /* LG.SelectionMargin */);
|
|
5010
|
-
for (let { from, to } of outside)
|
|
5011
|
-
if (to - from > 1000 /* LG.HalfMargin */) {
|
|
5012
|
-
gaps.push(find(current, gap => gap.from >= line.from && gap.to <= line.to &&
|
|
5013
|
-
Math.abs(gap.from - from) < 1000 /* LG.HalfMargin */ && Math.abs(gap.to - to) < 1000 /* LG.HalfMargin */) ||
|
|
5014
|
-
new LineGap(from, to, this.gapSize(line, from, to, structure)));
|
|
5015
|
-
}
|
|
5092
|
+
addGap(viewTo, line.to, line, structure);
|
|
5016
5093
|
}
|
|
5017
5094
|
return gaps;
|
|
5018
5095
|
}
|
|
@@ -5110,20 +5187,6 @@ function findFraction(structure, pos) {
|
|
|
5110
5187
|
}
|
|
5111
5188
|
return counted / structure.total;
|
|
5112
5189
|
}
|
|
5113
|
-
function cutRange(ranges, from, to) {
|
|
5114
|
-
for (let i = 0; i < ranges.length; i++) {
|
|
5115
|
-
let r = ranges[i];
|
|
5116
|
-
if (r.from < to && r.to > from) {
|
|
5117
|
-
let pieces = [];
|
|
5118
|
-
if (r.from < from)
|
|
5119
|
-
pieces.push({ from: r.from, to: from });
|
|
5120
|
-
if (r.to > to)
|
|
5121
|
-
pieces.push({ from: to, to: r.to });
|
|
5122
|
-
ranges.splice(i, 1, ...pieces);
|
|
5123
|
-
i += pieces.length - 1;
|
|
5124
|
-
}
|
|
5125
|
-
}
|
|
5126
|
-
}
|
|
5127
5190
|
function find(array, f) {
|
|
5128
5191
|
for (let val of array)
|
|
5129
5192
|
if (f(val))
|
|
@@ -5423,6 +5486,227 @@ const baseTheme$1 = /*@__PURE__*/buildTheme("." + baseThemeID, {
|
|
|
5423
5486
|
}
|
|
5424
5487
|
}, lightDarkIDs);
|
|
5425
5488
|
|
|
5489
|
+
class DOMChange {
|
|
5490
|
+
constructor(view, start, end, typeOver) {
|
|
5491
|
+
this.typeOver = typeOver;
|
|
5492
|
+
this.bounds = null;
|
|
5493
|
+
this.text = "";
|
|
5494
|
+
let { impreciseHead: iHead, impreciseAnchor: iAnchor } = view.docView;
|
|
5495
|
+
if (start > -1 && !view.state.readOnly && (this.bounds = view.docView.domBoundsAround(start, end, 0))) {
|
|
5496
|
+
let selPoints = iHead || iAnchor ? [] : selectionPoints(view);
|
|
5497
|
+
let reader = new DOMReader(selPoints, view.state);
|
|
5498
|
+
reader.readRange(this.bounds.startDOM, this.bounds.endDOM);
|
|
5499
|
+
this.text = reader.text;
|
|
5500
|
+
this.newSel = selectionFromPoints(selPoints, this.bounds.from);
|
|
5501
|
+
}
|
|
5502
|
+
else {
|
|
5503
|
+
let domSel = view.observer.selectionRange;
|
|
5504
|
+
let head = iHead && iHead.node == domSel.focusNode && iHead.offset == domSel.focusOffset ||
|
|
5505
|
+
!contains(view.contentDOM, domSel.focusNode)
|
|
5506
|
+
? view.state.selection.main.head
|
|
5507
|
+
: view.docView.posFromDOM(domSel.focusNode, domSel.focusOffset);
|
|
5508
|
+
let anchor = iAnchor && iAnchor.node == domSel.anchorNode && iAnchor.offset == domSel.anchorOffset ||
|
|
5509
|
+
!contains(view.contentDOM, domSel.anchorNode)
|
|
5510
|
+
? view.state.selection.main.anchor
|
|
5511
|
+
: view.docView.posFromDOM(domSel.anchorNode, domSel.anchorOffset);
|
|
5512
|
+
this.newSel = EditorSelection.single(anchor, head);
|
|
5513
|
+
}
|
|
5514
|
+
}
|
|
5515
|
+
}
|
|
5516
|
+
function applyDOMChange(view, domChange) {
|
|
5517
|
+
let change;
|
|
5518
|
+
let { newSel } = domChange, sel = view.state.selection.main;
|
|
5519
|
+
if (domChange.bounds) {
|
|
5520
|
+
let { from, to } = domChange.bounds;
|
|
5521
|
+
let preferredPos = sel.from, preferredSide = null;
|
|
5522
|
+
// Prefer anchoring to end when Backspace is pressed (or, on
|
|
5523
|
+
// Android, when something was deleted)
|
|
5524
|
+
if (view.inputState.lastKeyCode === 8 && view.inputState.lastKeyTime > Date.now() - 100 ||
|
|
5525
|
+
browser.android && domChange.text.length < to - from) {
|
|
5526
|
+
preferredPos = sel.to;
|
|
5527
|
+
preferredSide = "end";
|
|
5528
|
+
}
|
|
5529
|
+
let diff = findDiff(view.state.doc.sliceString(from, to, LineBreakPlaceholder), domChange.text, preferredPos - from, preferredSide);
|
|
5530
|
+
if (diff) {
|
|
5531
|
+
// Chrome inserts two newlines when pressing shift-enter at the
|
|
5532
|
+
// end of a line. DomChange drops one of those.
|
|
5533
|
+
if (browser.chrome && view.inputState.lastKeyCode == 13 &&
|
|
5534
|
+
diff.toB == diff.from + 2 && domChange.text.slice(diff.from, diff.toB) == LineBreakPlaceholder + LineBreakPlaceholder)
|
|
5535
|
+
diff.toB--;
|
|
5536
|
+
change = { from: from + diff.from, to: from + diff.toA,
|
|
5537
|
+
insert: Text.of(domChange.text.slice(diff.from, diff.toB).split(LineBreakPlaceholder)) };
|
|
5538
|
+
}
|
|
5539
|
+
}
|
|
5540
|
+
else if (newSel && (!view.hasFocus || !view.state.facet(editable) || newSel.main.eq(sel))) {
|
|
5541
|
+
newSel = null;
|
|
5542
|
+
}
|
|
5543
|
+
if (!change && !newSel)
|
|
5544
|
+
return false;
|
|
5545
|
+
if (!change && domChange.typeOver && !sel.empty && newSel && newSel.main.empty) {
|
|
5546
|
+
// Heuristic to notice typing over a selected character
|
|
5547
|
+
change = { from: sel.from, to: sel.to, insert: view.state.doc.slice(sel.from, sel.to) };
|
|
5548
|
+
}
|
|
5549
|
+
else if (change && change.from >= sel.from && change.to <= sel.to &&
|
|
5550
|
+
(change.from != sel.from || change.to != sel.to) &&
|
|
5551
|
+
(sel.to - sel.from) - (change.to - change.from) <= 4) {
|
|
5552
|
+
// If the change is inside the selection and covers most of it,
|
|
5553
|
+
// assume it is a selection replace (with identical characters at
|
|
5554
|
+
// the start/end not included in the diff)
|
|
5555
|
+
change = {
|
|
5556
|
+
from: sel.from, to: sel.to,
|
|
5557
|
+
insert: view.state.doc.slice(sel.from, change.from).append(change.insert).append(view.state.doc.slice(change.to, sel.to))
|
|
5558
|
+
};
|
|
5559
|
+
}
|
|
5560
|
+
else if ((browser.mac || browser.android) && change && change.from == change.to && change.from == sel.head - 1 &&
|
|
5561
|
+
/^\. ?$/.test(change.insert.toString())) {
|
|
5562
|
+
// Detect insert-period-on-double-space Mac and Android behavior,
|
|
5563
|
+
// and transform it into a regular space insert.
|
|
5564
|
+
if (newSel && change.insert.length == 2)
|
|
5565
|
+
newSel = EditorSelection.single(newSel.main.anchor - 1, newSel.main.head - 1);
|
|
5566
|
+
change = { from: sel.from, to: sel.to, insert: Text.of([" "]) };
|
|
5567
|
+
}
|
|
5568
|
+
if (change) {
|
|
5569
|
+
let startState = view.state;
|
|
5570
|
+
if (browser.ios && view.inputState.flushIOSKey(view))
|
|
5571
|
+
return true;
|
|
5572
|
+
// Android browsers don't fire reasonable key events for enter,
|
|
5573
|
+
// backspace, or delete. So this detects changes that look like
|
|
5574
|
+
// they're caused by those keys, and reinterprets them as key
|
|
5575
|
+
// events. (Some of these keys are also handled by beforeinput
|
|
5576
|
+
// events and the pendingAndroidKey mechanism, but that's not
|
|
5577
|
+
// reliable in all situations.)
|
|
5578
|
+
if (browser.android &&
|
|
5579
|
+
((change.from == sel.from && change.to == sel.to &&
|
|
5580
|
+
change.insert.length == 1 && change.insert.lines == 2 &&
|
|
5581
|
+
dispatchKey(view.contentDOM, "Enter", 13)) ||
|
|
5582
|
+
(change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 &&
|
|
5583
|
+
dispatchKey(view.contentDOM, "Backspace", 8)) ||
|
|
5584
|
+
(change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
|
|
5585
|
+
dispatchKey(view.contentDOM, "Delete", 46))))
|
|
5586
|
+
return true;
|
|
5587
|
+
let text = change.insert.toString();
|
|
5588
|
+
if (view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text)))
|
|
5589
|
+
return true;
|
|
5590
|
+
if (view.inputState.composing >= 0)
|
|
5591
|
+
view.inputState.composing++;
|
|
5592
|
+
let tr;
|
|
5593
|
+
if (change.from >= sel.from && change.to <= sel.to && change.to - change.from >= (sel.to - sel.from) / 3 &&
|
|
5594
|
+
(!newSel || newSel.main.empty && newSel.main.from == change.from + change.insert.length) &&
|
|
5595
|
+
view.inputState.composing < 0) {
|
|
5596
|
+
let before = sel.from < change.from ? startState.sliceDoc(sel.from, change.from) : "";
|
|
5597
|
+
let after = sel.to > change.to ? startState.sliceDoc(change.to, sel.to) : "";
|
|
5598
|
+
tr = startState.replaceSelection(view.state.toText(before + change.insert.sliceString(0, undefined, view.state.lineBreak) + after));
|
|
5599
|
+
}
|
|
5600
|
+
else {
|
|
5601
|
+
let changes = startState.changes(change);
|
|
5602
|
+
let mainSel = newSel && !startState.selection.main.eq(newSel.main) && newSel.main.to <= changes.newLength
|
|
5603
|
+
? newSel.main : undefined;
|
|
5604
|
+
// Try to apply a composition change to all cursors
|
|
5605
|
+
if (startState.selection.ranges.length > 1 && view.inputState.composing >= 0 &&
|
|
5606
|
+
change.to <= sel.to && change.to >= sel.to - 10) {
|
|
5607
|
+
let replaced = view.state.sliceDoc(change.from, change.to);
|
|
5608
|
+
let compositionRange = compositionSurroundingNode(view) || view.state.doc.lineAt(sel.head);
|
|
5609
|
+
let offset = sel.to - change.to, size = sel.to - sel.from;
|
|
5610
|
+
tr = startState.changeByRange(range => {
|
|
5611
|
+
if (range.from == sel.from && range.to == sel.to)
|
|
5612
|
+
return { changes, range: mainSel || range.map(changes) };
|
|
5613
|
+
let to = range.to - offset, from = to - replaced.length;
|
|
5614
|
+
if (range.to - range.from != size || view.state.sliceDoc(from, to) != replaced ||
|
|
5615
|
+
// Unfortunately, there's no way to make multiple
|
|
5616
|
+
// changes in the same node work without aborting
|
|
5617
|
+
// composition, so cursors in the composition range are
|
|
5618
|
+
// ignored.
|
|
5619
|
+
compositionRange && range.to >= compositionRange.from && range.from <= compositionRange.to)
|
|
5620
|
+
return { range };
|
|
5621
|
+
let rangeChanges = startState.changes({ from, to, insert: change.insert }), selOff = range.to - sel.to;
|
|
5622
|
+
return {
|
|
5623
|
+
changes: rangeChanges,
|
|
5624
|
+
range: !mainSel ? range.map(rangeChanges) :
|
|
5625
|
+
EditorSelection.range(Math.max(0, mainSel.anchor + selOff), Math.max(0, mainSel.head + selOff))
|
|
5626
|
+
};
|
|
5627
|
+
});
|
|
5628
|
+
}
|
|
5629
|
+
else {
|
|
5630
|
+
tr = {
|
|
5631
|
+
changes,
|
|
5632
|
+
selection: mainSel && startState.selection.replaceRange(mainSel)
|
|
5633
|
+
};
|
|
5634
|
+
}
|
|
5635
|
+
}
|
|
5636
|
+
let userEvent = "input.type";
|
|
5637
|
+
if (view.composing) {
|
|
5638
|
+
userEvent += ".compose";
|
|
5639
|
+
if (view.inputState.compositionFirstChange) {
|
|
5640
|
+
userEvent += ".start";
|
|
5641
|
+
view.inputState.compositionFirstChange = false;
|
|
5642
|
+
}
|
|
5643
|
+
}
|
|
5644
|
+
view.dispatch(tr, { scrollIntoView: true, userEvent });
|
|
5645
|
+
return true;
|
|
5646
|
+
}
|
|
5647
|
+
else if (newSel && !newSel.main.eq(sel)) {
|
|
5648
|
+
let scrollIntoView = false, userEvent = "select";
|
|
5649
|
+
if (view.inputState.lastSelectionTime > Date.now() - 50) {
|
|
5650
|
+
if (view.inputState.lastSelectionOrigin == "select")
|
|
5651
|
+
scrollIntoView = true;
|
|
5652
|
+
userEvent = view.inputState.lastSelectionOrigin;
|
|
5653
|
+
}
|
|
5654
|
+
view.dispatch({ selection: newSel, scrollIntoView, userEvent });
|
|
5655
|
+
return true;
|
|
5656
|
+
}
|
|
5657
|
+
else {
|
|
5658
|
+
return false;
|
|
5659
|
+
}
|
|
5660
|
+
}
|
|
5661
|
+
function findDiff(a, b, preferredPos, preferredSide) {
|
|
5662
|
+
let minLen = Math.min(a.length, b.length);
|
|
5663
|
+
let from = 0;
|
|
5664
|
+
while (from < minLen && a.charCodeAt(from) == b.charCodeAt(from))
|
|
5665
|
+
from++;
|
|
5666
|
+
if (from == minLen && a.length == b.length)
|
|
5667
|
+
return null;
|
|
5668
|
+
let toA = a.length, toB = b.length;
|
|
5669
|
+
while (toA > 0 && toB > 0 && a.charCodeAt(toA - 1) == b.charCodeAt(toB - 1)) {
|
|
5670
|
+
toA--;
|
|
5671
|
+
toB--;
|
|
5672
|
+
}
|
|
5673
|
+
if (preferredSide == "end") {
|
|
5674
|
+
let adjust = Math.max(0, from - Math.min(toA, toB));
|
|
5675
|
+
preferredPos -= toA + adjust - from;
|
|
5676
|
+
}
|
|
5677
|
+
if (toA < from && a.length < b.length) {
|
|
5678
|
+
let move = preferredPos <= from && preferredPos >= toA ? from - preferredPos : 0;
|
|
5679
|
+
from -= move;
|
|
5680
|
+
toB = from + (toB - toA);
|
|
5681
|
+
toA = from;
|
|
5682
|
+
}
|
|
5683
|
+
else if (toB < from) {
|
|
5684
|
+
let move = preferredPos <= from && preferredPos >= toB ? from - preferredPos : 0;
|
|
5685
|
+
from -= move;
|
|
5686
|
+
toA = from + (toA - toB);
|
|
5687
|
+
toB = from;
|
|
5688
|
+
}
|
|
5689
|
+
return { from, toA, toB };
|
|
5690
|
+
}
|
|
5691
|
+
function selectionPoints(view) {
|
|
5692
|
+
let result = [];
|
|
5693
|
+
if (view.root.activeElement != view.contentDOM)
|
|
5694
|
+
return result;
|
|
5695
|
+
let { anchorNode, anchorOffset, focusNode, focusOffset } = view.observer.selectionRange;
|
|
5696
|
+
if (anchorNode) {
|
|
5697
|
+
result.push(new DOMPoint(anchorNode, anchorOffset));
|
|
5698
|
+
if (focusNode != anchorNode || focusOffset != anchorOffset)
|
|
5699
|
+
result.push(new DOMPoint(focusNode, focusOffset));
|
|
5700
|
+
}
|
|
5701
|
+
return result;
|
|
5702
|
+
}
|
|
5703
|
+
function selectionFromPoints(points, base) {
|
|
5704
|
+
if (points.length == 0)
|
|
5705
|
+
return null;
|
|
5706
|
+
let anchor = points[0].pos, head = points.length == 2 ? points[1].pos : anchor;
|
|
5707
|
+
return anchor > -1 && head > -1 ? EditorSelection.single(anchor + base, head + base) : null;
|
|
5708
|
+
}
|
|
5709
|
+
|
|
5426
5710
|
const observeOptions = {
|
|
5427
5711
|
childList: true,
|
|
5428
5712
|
characterData: true,
|
|
@@ -5434,10 +5718,8 @@ const observeOptions = {
|
|
|
5434
5718
|
// DOMCharacterDataModified there
|
|
5435
5719
|
const useCharData = browser.ie && browser.ie_version <= 11;
|
|
5436
5720
|
class DOMObserver {
|
|
5437
|
-
constructor(view
|
|
5721
|
+
constructor(view) {
|
|
5438
5722
|
this.view = view;
|
|
5439
|
-
this.onChange = onChange;
|
|
5440
|
-
this.onScrollChanged = onScrollChanged;
|
|
5441
5723
|
this.active = false;
|
|
5442
5724
|
// The known selection. Kept in our own object, as opposed to just
|
|
5443
5725
|
// directly accessing the selection because:
|
|
@@ -5452,6 +5734,7 @@ class DOMObserver {
|
|
|
5452
5734
|
this.resizeTimeout = -1;
|
|
5453
5735
|
this.queue = [];
|
|
5454
5736
|
this.delayedAndroidKey = null;
|
|
5737
|
+
this.flushingAndroidKey = -1;
|
|
5455
5738
|
this.lastChange = 0;
|
|
5456
5739
|
this.scrollTargets = [];
|
|
5457
5740
|
this.intersection = null;
|
|
@@ -5520,6 +5803,11 @@ class DOMObserver {
|
|
|
5520
5803
|
this.listenForScroll();
|
|
5521
5804
|
this.readSelectionRange();
|
|
5522
5805
|
}
|
|
5806
|
+
onScrollChanged(e) {
|
|
5807
|
+
this.view.inputState.runScrollHandlers(this.view, e);
|
|
5808
|
+
if (this.intersecting)
|
|
5809
|
+
this.view.measure();
|
|
5810
|
+
}
|
|
5523
5811
|
onScroll(e) {
|
|
5524
5812
|
if (this.intersecting)
|
|
5525
5813
|
this.flush(false);
|
|
@@ -5679,14 +5967,17 @@ class DOMObserver {
|
|
|
5679
5967
|
// them or, if that has no effect, dispatches the given key.
|
|
5680
5968
|
delayAndroidKey(key, keyCode) {
|
|
5681
5969
|
var _a;
|
|
5682
|
-
if (!this.delayedAndroidKey)
|
|
5683
|
-
|
|
5970
|
+
if (!this.delayedAndroidKey) {
|
|
5971
|
+
let flush = () => {
|
|
5684
5972
|
let key = this.delayedAndroidKey;
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5973
|
+
if (key) {
|
|
5974
|
+
this.clearDelayedAndroidKey();
|
|
5975
|
+
if (!this.flush() && key.force)
|
|
5976
|
+
dispatchKey(this.dom, key.key, key.keyCode);
|
|
5977
|
+
}
|
|
5978
|
+
};
|
|
5979
|
+
this.flushingAndroidKey = this.view.win.requestAnimationFrame(flush);
|
|
5980
|
+
}
|
|
5690
5981
|
// Since backspace beforeinput is sometimes signalled spuriously,
|
|
5691
5982
|
// Enter always takes precedence.
|
|
5692
5983
|
if (!this.delayedAndroidKey || key == "Enter")
|
|
@@ -5699,6 +5990,11 @@ class DOMObserver {
|
|
|
5699
5990
|
force: this.lastChange < Date.now() - 50 || !!((_a = this.delayedAndroidKey) === null || _a === void 0 ? void 0 : _a.force)
|
|
5700
5991
|
};
|
|
5701
5992
|
}
|
|
5993
|
+
clearDelayedAndroidKey() {
|
|
5994
|
+
this.win.cancelAnimationFrame(this.flushingAndroidKey);
|
|
5995
|
+
this.delayedAndroidKey = null;
|
|
5996
|
+
this.flushingAndroidKey = -1;
|
|
5997
|
+
}
|
|
5702
5998
|
flushSoon() {
|
|
5703
5999
|
if (this.delayedFlush < 0)
|
|
5704
6000
|
this.delayedFlush = this.view.win.requestAnimationFrame(() => { this.delayedFlush = -1; this.flush(); });
|
|
@@ -5733,6 +6029,17 @@ class DOMObserver {
|
|
|
5733
6029
|
}
|
|
5734
6030
|
return { from, to, typeOver };
|
|
5735
6031
|
}
|
|
6032
|
+
readChange() {
|
|
6033
|
+
let { from, to, typeOver } = this.processRecords();
|
|
6034
|
+
let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
|
|
6035
|
+
if (from < 0 && !newSel)
|
|
6036
|
+
return null;
|
|
6037
|
+
if (from > -1)
|
|
6038
|
+
this.lastChange = Date.now();
|
|
6039
|
+
this.view.inputState.lastFocusTime = 0;
|
|
6040
|
+
this.selectionChanged = false;
|
|
6041
|
+
return new DOMChange(this.view, from, to, typeOver);
|
|
6042
|
+
}
|
|
5736
6043
|
// Apply pending changes, if any
|
|
5737
6044
|
flush(readSelection = true) {
|
|
5738
6045
|
// Completely hold off flushing when pending keys are set—the code
|
|
@@ -5742,16 +6049,11 @@ class DOMObserver {
|
|
|
5742
6049
|
return false;
|
|
5743
6050
|
if (readSelection)
|
|
5744
6051
|
this.readSelectionRange();
|
|
5745
|
-
let
|
|
5746
|
-
|
|
5747
|
-
if (from < 0 && !newSel)
|
|
6052
|
+
let domChange = this.readChange();
|
|
6053
|
+
if (!domChange)
|
|
5748
6054
|
return false;
|
|
5749
|
-
if (from > -1)
|
|
5750
|
-
this.lastChange = Date.now();
|
|
5751
|
-
this.view.inputState.lastFocusTime = 0;
|
|
5752
|
-
this.selectionChanged = false;
|
|
5753
6055
|
let startState = this.view.state;
|
|
5754
|
-
let handled = this.
|
|
6056
|
+
let handled = applyDOMChange(this.view, domChange);
|
|
5755
6057
|
// The view wasn't updated
|
|
5756
6058
|
if (this.view.state == startState)
|
|
5757
6059
|
this.view.update([]);
|
|
@@ -5807,6 +6109,8 @@ class DOMObserver {
|
|
|
5807
6109
|
this.removeWindowListeners(this.win);
|
|
5808
6110
|
clearTimeout(this.parentCheck);
|
|
5809
6111
|
clearTimeout(this.resizeTimeout);
|
|
6112
|
+
this.win.cancelAnimationFrame(this.delayedFlush);
|
|
6113
|
+
this.win.cancelAnimationFrame(this.flushingAndroidKey);
|
|
5810
6114
|
}
|
|
5811
6115
|
}
|
|
5812
6116
|
function findChild(cView, dom, dir) {
|
|
@@ -5848,213 +6152,6 @@ function safariSelectionRangeHack(view) {
|
|
|
5848
6152
|
return { anchorNode, anchorOffset, focusNode, focusOffset };
|
|
5849
6153
|
}
|
|
5850
6154
|
|
|
5851
|
-
function applyDOMChange(view, start, end, typeOver) {
|
|
5852
|
-
let change, newSel;
|
|
5853
|
-
let sel = view.state.selection.main;
|
|
5854
|
-
if (start > -1) {
|
|
5855
|
-
let bounds = view.docView.domBoundsAround(start, end, 0);
|
|
5856
|
-
if (!bounds || view.state.readOnly)
|
|
5857
|
-
return false;
|
|
5858
|
-
let { from, to } = bounds;
|
|
5859
|
-
let selPoints = view.docView.impreciseHead || view.docView.impreciseAnchor ? [] : selectionPoints(view);
|
|
5860
|
-
let reader = new DOMReader(selPoints, view.state);
|
|
5861
|
-
reader.readRange(bounds.startDOM, bounds.endDOM);
|
|
5862
|
-
let preferredPos = sel.from, preferredSide = null;
|
|
5863
|
-
// Prefer anchoring to end when Backspace is pressed (or, on
|
|
5864
|
-
// Android, when something was deleted)
|
|
5865
|
-
if (view.inputState.lastKeyCode === 8 && view.inputState.lastKeyTime > Date.now() - 100 ||
|
|
5866
|
-
browser.android && reader.text.length < to - from) {
|
|
5867
|
-
preferredPos = sel.to;
|
|
5868
|
-
preferredSide = "end";
|
|
5869
|
-
}
|
|
5870
|
-
let diff = findDiff(view.state.doc.sliceString(from, to, LineBreakPlaceholder), reader.text, preferredPos - from, preferredSide);
|
|
5871
|
-
if (diff) {
|
|
5872
|
-
// Chrome inserts two newlines when pressing shift-enter at the
|
|
5873
|
-
// end of a line. This drops one of those.
|
|
5874
|
-
if (browser.chrome && view.inputState.lastKeyCode == 13 &&
|
|
5875
|
-
diff.toB == diff.from + 2 && reader.text.slice(diff.from, diff.toB) == LineBreakPlaceholder + LineBreakPlaceholder)
|
|
5876
|
-
diff.toB--;
|
|
5877
|
-
change = { from: from + diff.from, to: from + diff.toA,
|
|
5878
|
-
insert: Text.of(reader.text.slice(diff.from, diff.toB).split(LineBreakPlaceholder)) };
|
|
5879
|
-
}
|
|
5880
|
-
newSel = selectionFromPoints(selPoints, from);
|
|
5881
|
-
}
|
|
5882
|
-
else if (view.hasFocus || !view.state.facet(editable)) {
|
|
5883
|
-
let domSel = view.observer.selectionRange;
|
|
5884
|
-
let { impreciseHead: iHead, impreciseAnchor: iAnchor } = view.docView;
|
|
5885
|
-
let head = iHead && iHead.node == domSel.focusNode && iHead.offset == domSel.focusOffset ||
|
|
5886
|
-
!contains(view.contentDOM, domSel.focusNode)
|
|
5887
|
-
? view.state.selection.main.head
|
|
5888
|
-
: view.docView.posFromDOM(domSel.focusNode, domSel.focusOffset);
|
|
5889
|
-
let anchor = iAnchor && iAnchor.node == domSel.anchorNode && iAnchor.offset == domSel.anchorOffset ||
|
|
5890
|
-
!contains(view.contentDOM, domSel.anchorNode)
|
|
5891
|
-
? view.state.selection.main.anchor
|
|
5892
|
-
: view.docView.posFromDOM(domSel.anchorNode, domSel.anchorOffset);
|
|
5893
|
-
if (head != sel.head || anchor != sel.anchor)
|
|
5894
|
-
newSel = EditorSelection.single(anchor, head);
|
|
5895
|
-
}
|
|
5896
|
-
if (!change && !newSel)
|
|
5897
|
-
return false;
|
|
5898
|
-
// Heuristic to notice typing over a selected character
|
|
5899
|
-
if (!change && typeOver && !sel.empty && newSel && newSel.main.empty)
|
|
5900
|
-
change = { from: sel.from, to: sel.to, insert: view.state.doc.slice(sel.from, sel.to) };
|
|
5901
|
-
// If the change is inside the selection and covers most of it,
|
|
5902
|
-
// assume it is a selection replace (with identical characters at
|
|
5903
|
-
// the start/end not included in the diff)
|
|
5904
|
-
else if (change && change.from >= sel.from && change.to <= sel.to &&
|
|
5905
|
-
(change.from != sel.from || change.to != sel.to) &&
|
|
5906
|
-
(sel.to - sel.from) - (change.to - change.from) <= 4)
|
|
5907
|
-
change = {
|
|
5908
|
-
from: sel.from, to: sel.to,
|
|
5909
|
-
insert: view.state.doc.slice(sel.from, change.from).append(change.insert).append(view.state.doc.slice(change.to, sel.to))
|
|
5910
|
-
};
|
|
5911
|
-
// Detect insert-period-on-double-space Mac behavior, and transform
|
|
5912
|
-
// it into a regular space insert.
|
|
5913
|
-
else if ((browser.mac || browser.android) && change && change.from == change.to && change.from == sel.head - 1 &&
|
|
5914
|
-
change.insert.toString() == ".")
|
|
5915
|
-
change = { from: sel.from, to: sel.to, insert: Text.of([" "]) };
|
|
5916
|
-
if (change) {
|
|
5917
|
-
let startState = view.state;
|
|
5918
|
-
if (browser.ios && view.inputState.flushIOSKey(view))
|
|
5919
|
-
return true;
|
|
5920
|
-
// Android browsers don't fire reasonable key events for enter,
|
|
5921
|
-
// backspace, or delete. So this detects changes that look like
|
|
5922
|
-
// they're caused by those keys, and reinterprets them as key
|
|
5923
|
-
// events. (Some of these keys are also handled by beforeinput
|
|
5924
|
-
// events and the pendingAndroidKey mechanism, but that's not
|
|
5925
|
-
// reliable in all situations.)
|
|
5926
|
-
if (browser.android &&
|
|
5927
|
-
((change.from == sel.from && change.to == sel.to &&
|
|
5928
|
-
change.insert.length == 1 && change.insert.lines == 2 &&
|
|
5929
|
-
dispatchKey(view.contentDOM, "Enter", 13)) ||
|
|
5930
|
-
(change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 &&
|
|
5931
|
-
dispatchKey(view.contentDOM, "Backspace", 8)) ||
|
|
5932
|
-
(change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
|
|
5933
|
-
dispatchKey(view.contentDOM, "Delete", 46))))
|
|
5934
|
-
return true;
|
|
5935
|
-
let text = change.insert.toString();
|
|
5936
|
-
if (view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text)))
|
|
5937
|
-
return true;
|
|
5938
|
-
if (view.inputState.composing >= 0)
|
|
5939
|
-
view.inputState.composing++;
|
|
5940
|
-
let tr;
|
|
5941
|
-
if (change.from >= sel.from && change.to <= sel.to && change.to - change.from >= (sel.to - sel.from) / 3 &&
|
|
5942
|
-
(!newSel || newSel.main.empty && newSel.main.from == change.from + change.insert.length) &&
|
|
5943
|
-
view.inputState.composing < 0) {
|
|
5944
|
-
let before = sel.from < change.from ? startState.sliceDoc(sel.from, change.from) : "";
|
|
5945
|
-
let after = sel.to > change.to ? startState.sliceDoc(change.to, sel.to) : "";
|
|
5946
|
-
tr = startState.replaceSelection(view.state.toText(before + change.insert.sliceString(0, undefined, view.state.lineBreak) + after));
|
|
5947
|
-
}
|
|
5948
|
-
else {
|
|
5949
|
-
let changes = startState.changes(change);
|
|
5950
|
-
let mainSel = newSel && !startState.selection.main.eq(newSel.main) && newSel.main.to <= changes.newLength
|
|
5951
|
-
? newSel.main : undefined;
|
|
5952
|
-
// Try to apply a composition change to all cursors
|
|
5953
|
-
if (startState.selection.ranges.length > 1 && view.inputState.composing >= 0 &&
|
|
5954
|
-
change.to <= sel.to && change.to >= sel.to - 10) {
|
|
5955
|
-
let replaced = view.state.sliceDoc(change.from, change.to);
|
|
5956
|
-
let compositionRange = compositionSurroundingNode(view) || view.state.doc.lineAt(sel.head);
|
|
5957
|
-
let offset = sel.to - change.to, size = sel.to - sel.from;
|
|
5958
|
-
tr = startState.changeByRange(range => {
|
|
5959
|
-
if (range.from == sel.from && range.to == sel.to)
|
|
5960
|
-
return { changes, range: mainSel || range.map(changes) };
|
|
5961
|
-
let to = range.to - offset, from = to - replaced.length;
|
|
5962
|
-
if (range.to - range.from != size || view.state.sliceDoc(from, to) != replaced ||
|
|
5963
|
-
// Unfortunately, there's no way to make multiple
|
|
5964
|
-
// changes in the same node work without aborting
|
|
5965
|
-
// composition, so cursors in the composition range are
|
|
5966
|
-
// ignored.
|
|
5967
|
-
compositionRange && range.to >= compositionRange.from && range.from <= compositionRange.to)
|
|
5968
|
-
return { range };
|
|
5969
|
-
let rangeChanges = startState.changes({ from, to, insert: change.insert }), selOff = range.to - sel.to;
|
|
5970
|
-
return {
|
|
5971
|
-
changes: rangeChanges,
|
|
5972
|
-
range: !mainSel ? range.map(rangeChanges) :
|
|
5973
|
-
EditorSelection.range(Math.max(0, mainSel.anchor + selOff), Math.max(0, mainSel.head + selOff))
|
|
5974
|
-
};
|
|
5975
|
-
});
|
|
5976
|
-
}
|
|
5977
|
-
else {
|
|
5978
|
-
tr = {
|
|
5979
|
-
changes,
|
|
5980
|
-
selection: mainSel && startState.selection.replaceRange(mainSel)
|
|
5981
|
-
};
|
|
5982
|
-
}
|
|
5983
|
-
}
|
|
5984
|
-
let userEvent = "input.type";
|
|
5985
|
-
if (view.composing) {
|
|
5986
|
-
userEvent += ".compose";
|
|
5987
|
-
if (view.inputState.compositionFirstChange) {
|
|
5988
|
-
userEvent += ".start";
|
|
5989
|
-
view.inputState.compositionFirstChange = false;
|
|
5990
|
-
}
|
|
5991
|
-
}
|
|
5992
|
-
view.dispatch(tr, { scrollIntoView: true, userEvent });
|
|
5993
|
-
return true;
|
|
5994
|
-
}
|
|
5995
|
-
else if (newSel && !newSel.main.eq(sel)) {
|
|
5996
|
-
let scrollIntoView = false, userEvent = "select";
|
|
5997
|
-
if (view.inputState.lastSelectionTime > Date.now() - 50) {
|
|
5998
|
-
if (view.inputState.lastSelectionOrigin == "select")
|
|
5999
|
-
scrollIntoView = true;
|
|
6000
|
-
userEvent = view.inputState.lastSelectionOrigin;
|
|
6001
|
-
}
|
|
6002
|
-
view.dispatch({ selection: newSel, scrollIntoView, userEvent });
|
|
6003
|
-
return true;
|
|
6004
|
-
}
|
|
6005
|
-
else {
|
|
6006
|
-
return false;
|
|
6007
|
-
}
|
|
6008
|
-
}
|
|
6009
|
-
function findDiff(a, b, preferredPos, preferredSide) {
|
|
6010
|
-
let minLen = Math.min(a.length, b.length);
|
|
6011
|
-
let from = 0;
|
|
6012
|
-
while (from < minLen && a.charCodeAt(from) == b.charCodeAt(from))
|
|
6013
|
-
from++;
|
|
6014
|
-
if (from == minLen && a.length == b.length)
|
|
6015
|
-
return null;
|
|
6016
|
-
let toA = a.length, toB = b.length;
|
|
6017
|
-
while (toA > 0 && toB > 0 && a.charCodeAt(toA - 1) == b.charCodeAt(toB - 1)) {
|
|
6018
|
-
toA--;
|
|
6019
|
-
toB--;
|
|
6020
|
-
}
|
|
6021
|
-
if (preferredSide == "end") {
|
|
6022
|
-
let adjust = Math.max(0, from - Math.min(toA, toB));
|
|
6023
|
-
preferredPos -= toA + adjust - from;
|
|
6024
|
-
}
|
|
6025
|
-
if (toA < from && a.length < b.length) {
|
|
6026
|
-
let move = preferredPos <= from && preferredPos >= toA ? from - preferredPos : 0;
|
|
6027
|
-
from -= move;
|
|
6028
|
-
toB = from + (toB - toA);
|
|
6029
|
-
toA = from;
|
|
6030
|
-
}
|
|
6031
|
-
else if (toB < from) {
|
|
6032
|
-
let move = preferredPos <= from && preferredPos >= toB ? from - preferredPos : 0;
|
|
6033
|
-
from -= move;
|
|
6034
|
-
toA = from + (toA - toB);
|
|
6035
|
-
toB = from;
|
|
6036
|
-
}
|
|
6037
|
-
return { from, toA, toB };
|
|
6038
|
-
}
|
|
6039
|
-
function selectionPoints(view) {
|
|
6040
|
-
let result = [];
|
|
6041
|
-
if (view.root.activeElement != view.contentDOM)
|
|
6042
|
-
return result;
|
|
6043
|
-
let { anchorNode, anchorOffset, focusNode, focusOffset } = view.observer.selectionRange;
|
|
6044
|
-
if (anchorNode) {
|
|
6045
|
-
result.push(new DOMPoint(anchorNode, anchorOffset));
|
|
6046
|
-
if (focusNode != anchorNode || focusOffset != anchorOffset)
|
|
6047
|
-
result.push(new DOMPoint(focusNode, focusOffset));
|
|
6048
|
-
}
|
|
6049
|
-
return result;
|
|
6050
|
-
}
|
|
6051
|
-
function selectionFromPoints(points, base) {
|
|
6052
|
-
if (points.length == 0)
|
|
6053
|
-
return null;
|
|
6054
|
-
let anchor = points[0].pos, head = points.length == 2 ? points[1].pos : anchor;
|
|
6055
|
-
return anchor > -1 && head > -1 ? EditorSelection.single(anchor + base, head + base) : null;
|
|
6056
|
-
}
|
|
6057
|
-
|
|
6058
6155
|
// The editor's update state machine looks something like this:
|
|
6059
6156
|
//
|
|
6060
6157
|
// Idle → Updating ⇆ Idle (unchecked) → Measuring → Idle
|
|
@@ -6117,13 +6214,7 @@ class EditorView {
|
|
|
6117
6214
|
this.plugins = this.state.facet(viewPlugin).map(spec => new PluginInstance(spec));
|
|
6118
6215
|
for (let plugin of this.plugins)
|
|
6119
6216
|
plugin.update(this);
|
|
6120
|
-
this.observer = new DOMObserver(this
|
|
6121
|
-
return applyDOMChange(this, from, to, typeOver);
|
|
6122
|
-
}, event => {
|
|
6123
|
-
this.inputState.runScrollHandlers(this, event);
|
|
6124
|
-
if (this.observer.intersecting)
|
|
6125
|
-
this.measure();
|
|
6126
|
-
});
|
|
6217
|
+
this.observer = new DOMObserver(this);
|
|
6127
6218
|
this.inputState = new InputState(this);
|
|
6128
6219
|
this.inputState.ensureHandlers(this, this.plugins);
|
|
6129
6220
|
this.docView = new DocView(this);
|
|
@@ -6207,7 +6298,20 @@ class EditorView {
|
|
|
6207
6298
|
this.viewState.state = state;
|
|
6208
6299
|
return;
|
|
6209
6300
|
}
|
|
6210
|
-
|
|
6301
|
+
// If there was a pending DOM change, eagerly read it and try to
|
|
6302
|
+
// apply it after the given transactions.
|
|
6303
|
+
let pendingKey = this.observer.delayedAndroidKey, domChange = null;
|
|
6304
|
+
if (pendingKey) {
|
|
6305
|
+
this.observer.clearDelayedAndroidKey();
|
|
6306
|
+
domChange = this.observer.readChange();
|
|
6307
|
+
// Only try to apply DOM changes if the transactions didn't
|
|
6308
|
+
// change the doc or selection.
|
|
6309
|
+
if (domChange && !this.state.doc.eq(state.doc) || !this.state.selection.eq(state.selection))
|
|
6310
|
+
domChange = null;
|
|
6311
|
+
}
|
|
6312
|
+
else {
|
|
6313
|
+
this.observer.clear();
|
|
6314
|
+
}
|
|
6211
6315
|
// When the phrases change, redraw the editor
|
|
6212
6316
|
if (state.facet(EditorState.phrases) != this.state.facet(EditorState.phrases))
|
|
6213
6317
|
return this.setState(state);
|
|
@@ -6249,6 +6353,10 @@ class EditorView {
|
|
|
6249
6353
|
if (!update.empty)
|
|
6250
6354
|
for (let listener of this.state.facet(updateListener))
|
|
6251
6355
|
listener(update);
|
|
6356
|
+
if (domChange) {
|
|
6357
|
+
if (!applyDOMChange(this, domChange) && pendingKey.force)
|
|
6358
|
+
dispatchKey(this.contentDOM, pendingKey.key, pendingKey.keyCode);
|
|
6359
|
+
}
|
|
6252
6360
|
}
|
|
6253
6361
|
/**
|
|
6254
6362
|
Reset the view to the given state. (This will cause the entire
|
|
@@ -7086,6 +7194,7 @@ function buildKeymap(bindings, platform = currentPlatform) {
|
|
|
7086
7194
|
throw new Error("Key binding " + name + " is used both as a regular binding and as a multi-stroke prefix");
|
|
7087
7195
|
};
|
|
7088
7196
|
let add = (scope, key, command, preventDefault) => {
|
|
7197
|
+
var _a, _b;
|
|
7089
7198
|
let scopeObj = bound[scope] || (bound[scope] = Object.create(null));
|
|
7090
7199
|
let parts = key.split(/ (?!$)/).map(k => normalizeKeyName(k, platform));
|
|
7091
7200
|
for (let i = 1; i < parts.length; i++) {
|
|
@@ -7094,7 +7203,7 @@ function buildKeymap(bindings, platform = currentPlatform) {
|
|
|
7094
7203
|
if (!scopeObj[prefix])
|
|
7095
7204
|
scopeObj[prefix] = {
|
|
7096
7205
|
preventDefault: true,
|
|
7097
|
-
|
|
7206
|
+
run: [(view) => {
|
|
7098
7207
|
let ourObj = storedPrefix = { view, prefix, scope };
|
|
7099
7208
|
setTimeout(() => { if (storedPrefix == ourObj)
|
|
7100
7209
|
storedPrefix = null; }, PrefixTimeout);
|
|
@@ -7104,16 +7213,26 @@ function buildKeymap(bindings, platform = currentPlatform) {
|
|
|
7104
7213
|
}
|
|
7105
7214
|
let full = parts.join(" ");
|
|
7106
7215
|
checkPrefix(full, false);
|
|
7107
|
-
let binding = scopeObj[full] || (scopeObj[full] = { preventDefault: false,
|
|
7108
|
-
|
|
7216
|
+
let binding = scopeObj[full] || (scopeObj[full] = { preventDefault: false, run: ((_b = (_a = scopeObj._any) === null || _a === void 0 ? void 0 : _a.run) === null || _b === void 0 ? void 0 : _b.slice()) || [] });
|
|
7217
|
+
if (command)
|
|
7218
|
+
binding.run.push(command);
|
|
7109
7219
|
if (preventDefault)
|
|
7110
7220
|
binding.preventDefault = true;
|
|
7111
7221
|
};
|
|
7112
7222
|
for (let b of bindings) {
|
|
7223
|
+
let scopes = b.scope ? b.scope.split(" ") : ["editor"];
|
|
7224
|
+
if (b.any)
|
|
7225
|
+
for (let scope of scopes) {
|
|
7226
|
+
let scopeObj = bound[scope] || (bound[scope] = Object.create(null));
|
|
7227
|
+
if (!scopeObj._any)
|
|
7228
|
+
scopeObj._any = { preventDefault: false, run: [] };
|
|
7229
|
+
for (let key in scopeObj)
|
|
7230
|
+
scopeObj[key].run.push(b.any);
|
|
7231
|
+
}
|
|
7113
7232
|
let name = b[platform] || b.key;
|
|
7114
7233
|
if (!name)
|
|
7115
7234
|
continue;
|
|
7116
|
-
for (let scope of
|
|
7235
|
+
for (let scope of scopes) {
|
|
7117
7236
|
add(scope, name, b.run, b.preventDefault);
|
|
7118
7237
|
if (b.shift)
|
|
7119
7238
|
add(scope, "Shift-" + name, b.shift, b.preventDefault);
|
|
@@ -7130,11 +7249,15 @@ function runHandlers(map, event, view, scope) {
|
|
|
7130
7249
|
if (fallthrough = modifierCodes.indexOf(event.keyCode) < 0)
|
|
7131
7250
|
storedPrefix = null;
|
|
7132
7251
|
}
|
|
7252
|
+
let ran = new Set;
|
|
7133
7253
|
let runFor = (binding) => {
|
|
7134
7254
|
if (binding) {
|
|
7135
|
-
for (let cmd of binding.
|
|
7136
|
-
if (cmd
|
|
7137
|
-
|
|
7255
|
+
for (let cmd of binding.run)
|
|
7256
|
+
if (!ran.has(cmd)) {
|
|
7257
|
+
ran.add(cmd);
|
|
7258
|
+
if (cmd(view, event))
|
|
7259
|
+
return true;
|
|
7260
|
+
}
|
|
7138
7261
|
if (binding.preventDefault)
|
|
7139
7262
|
fallthrough = true;
|
|
7140
7263
|
}
|
|
@@ -7156,6 +7279,8 @@ function runHandlers(map, event, view, scope) {
|
|
|
7156
7279
|
if (runFor(scopeObj[prefix + modifiers(name, event, true)]))
|
|
7157
7280
|
return true;
|
|
7158
7281
|
}
|
|
7282
|
+
if (runFor(scopeObj._any))
|
|
7283
|
+
return true;
|
|
7159
7284
|
}
|
|
7160
7285
|
return fallthrough;
|
|
7161
7286
|
}
|