@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.cjs
CHANGED
|
@@ -875,7 +875,7 @@ class WidgetView extends ContentView {
|
|
|
875
875
|
if (pos > 0 ? i == 0 : i == rects.length - 1 || rect.top < rect.bottom)
|
|
876
876
|
break;
|
|
877
877
|
}
|
|
878
|
-
return (
|
|
878
|
+
return flattenRect(rect, this.side > 0);
|
|
879
879
|
}
|
|
880
880
|
get isEditable() { return false; }
|
|
881
881
|
destroy() {
|
|
@@ -1028,7 +1028,6 @@ function inlineDOMAtPos(parent, pos) {
|
|
|
1028
1028
|
break;
|
|
1029
1029
|
off = end;
|
|
1030
1030
|
}
|
|
1031
|
-
// if (i) return DOMPos.after(children[i - 1].dom!)
|
|
1032
1031
|
for (let j = i; j > 0; j--) {
|
|
1033
1032
|
let prev = children[j - 1];
|
|
1034
1033
|
if (prev.dom.parentNode == dom)
|
|
@@ -1055,21 +1054,59 @@ function joinInlineInto(parent, view, open) {
|
|
|
1055
1054
|
parent.length += view.length;
|
|
1056
1055
|
}
|
|
1057
1056
|
function coordsInChildren(view, pos, side) {
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1057
|
+
if (!view.children.length)
|
|
1058
|
+
return fallbackRect(view);
|
|
1059
|
+
return (side <= 0 ? coordsInChildrenBefore : coordsInChildrenAfter)(view, pos);
|
|
1060
|
+
}
|
|
1061
|
+
function coordsInChildrenBefore(view, pos) {
|
|
1062
|
+
// Find the last leaf in the tree that touches pos and doesn't have getSide() > 0
|
|
1063
|
+
let found = null, foundPos = -1;
|
|
1064
|
+
function scan(view, pos) {
|
|
1065
|
+
for (let i = 0, off = 0; i < view.children.length && off <= pos; i++) {
|
|
1066
|
+
let child = view.children[i], end = off + child.length;
|
|
1067
|
+
if (end >= pos) {
|
|
1068
|
+
if (child.children.length) {
|
|
1069
|
+
if (scan(child, pos - off))
|
|
1070
|
+
return true;
|
|
1071
|
+
}
|
|
1072
|
+
else if (end >= pos) {
|
|
1073
|
+
if (end == pos && child.getSide() > 0)
|
|
1074
|
+
return true;
|
|
1075
|
+
found = child;
|
|
1076
|
+
foundPos = pos - off;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
off = end;
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
scan(view, pos);
|
|
1083
|
+
return found ? found.coordsAt(Math.max(0, foundPos), -1) : coordsInChildrenAfter(view, pos);
|
|
1084
|
+
}
|
|
1085
|
+
function coordsInChildrenAfter(view, pos) {
|
|
1086
|
+
// Find the first leaf in the tree that touches pos and doesn't have getSide() < 0
|
|
1087
|
+
let found = null, foundPos = -1;
|
|
1088
|
+
function scan(view, pos) {
|
|
1089
|
+
for (let i = view.children.length - 1, off = view.length; i >= 0 && off >= pos; i--) {
|
|
1090
|
+
let child = view.children[i];
|
|
1091
|
+
off -= child.length;
|
|
1092
|
+
if (off <= pos) {
|
|
1093
|
+
if (child.children.length) {
|
|
1094
|
+
if (scan(child, pos - off))
|
|
1095
|
+
return true;
|
|
1096
|
+
}
|
|
1097
|
+
else if (off <= pos) {
|
|
1098
|
+
if (off == pos && child.getSide() < 0)
|
|
1099
|
+
return true;
|
|
1100
|
+
found = child;
|
|
1101
|
+
foundPos = pos - off;
|
|
1102
|
+
}
|
|
1067
1103
|
}
|
|
1068
|
-
let rect = child.coordsAt(Math.max(0, pos - off), side);
|
|
1069
|
-
return flatten && rect ? flattenRect(rect, side < 0) : rect;
|
|
1070
1104
|
}
|
|
1071
|
-
off = end;
|
|
1072
1105
|
}
|
|
1106
|
+
scan(view, pos);
|
|
1107
|
+
return found ? found.coordsAt(Math.max(0, foundPos), 1) : coordsInChildrenBefore(view, pos);
|
|
1108
|
+
}
|
|
1109
|
+
function fallbackRect(view) {
|
|
1073
1110
|
let last = view.dom.lastChild;
|
|
1074
1111
|
if (!last)
|
|
1075
1112
|
return view.dom.getBoundingClientRect();
|
|
@@ -1688,7 +1725,7 @@ class ContentBuilder {
|
|
|
1688
1725
|
this.addBlockWidget(new BlockWidgetView(deco.widget || new NullWidget("div"), len, type));
|
|
1689
1726
|
}
|
|
1690
1727
|
else {
|
|
1691
|
-
let view = WidgetView.create(deco.widget || new NullWidget("span"), len, deco.startSide);
|
|
1728
|
+
let view = WidgetView.create(deco.widget || new NullWidget("span"), len, len ? 0 : deco.startSide);
|
|
1692
1729
|
let cursorBefore = this.atCursorPos && !view.isEditable && openStart <= active.length && (from < to || deco.startSide > 0);
|
|
1693
1730
|
let cursorAfter = !view.isEditable && (from < to || deco.startSide <= 0);
|
|
1694
1731
|
let line = this.getLine();
|
|
@@ -4827,7 +4864,7 @@ class ViewState {
|
|
|
4827
4864
|
this.updateForViewport();
|
|
4828
4865
|
if (updateLines)
|
|
4829
4866
|
this.updateViewportLines();
|
|
4830
|
-
if (this.lineGaps.length || this.viewport.to - this.viewport.from >
|
|
4867
|
+
if (this.lineGaps.length || this.viewport.to - this.viewport.from > (2000 /* LG.Margin */ << 1))
|
|
4831
4868
|
this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
|
|
4832
4869
|
update.flags |= this.computeVisibleRanges();
|
|
4833
4870
|
if (scrollTarget)
|
|
@@ -4883,7 +4920,7 @@ class ViewState {
|
|
|
4883
4920
|
refresh = true;
|
|
4884
4921
|
if (refresh || oracle.lineWrapping && Math.abs(contentWidth - this.contentDOMWidth) > oracle.charWidth) {
|
|
4885
4922
|
let { lineHeight, charWidth } = view.docView.measureTextSize();
|
|
4886
|
-
refresh = oracle.refresh(whiteSpace, lineHeight, charWidth, contentWidth / charWidth, lineHeights);
|
|
4923
|
+
refresh = lineHeight > 0 && oracle.refresh(whiteSpace, lineHeight, charWidth, contentWidth / charWidth, lineHeights);
|
|
4887
4924
|
if (refresh) {
|
|
4888
4925
|
view.docView.minWidth = 0;
|
|
4889
4926
|
result |= 8 /* UpdateFlag.Geometry */;
|
|
@@ -4908,8 +4945,8 @@ class ViewState {
|
|
|
4908
4945
|
this.updateForViewport();
|
|
4909
4946
|
if ((result & 2 /* UpdateFlag.Height */) || viewportChange)
|
|
4910
4947
|
this.updateViewportLines();
|
|
4911
|
-
if (this.lineGaps.length || this.viewport.to - this.viewport.from >
|
|
4912
|
-
this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps));
|
|
4948
|
+
if (this.lineGaps.length || this.viewport.to - this.viewport.from > (2000 /* LG.Margin */ << 1))
|
|
4949
|
+
this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps, view));
|
|
4913
4950
|
result |= this.computeVisibleRanges();
|
|
4914
4951
|
if (this.mustEnforceCursorAssoc) {
|
|
4915
4952
|
this.mustEnforceCursorAssoc = false;
|
|
@@ -4980,46 +5017,86 @@ class ViewState {
|
|
|
4980
5017
|
// since actual DOM coordinates aren't always available and
|
|
4981
5018
|
// predictable. Relies on generous margins (see LG.Margin) to hide
|
|
4982
5019
|
// the artifacts this might produce from the user.
|
|
4983
|
-
ensureLineGaps(current) {
|
|
5020
|
+
ensureLineGaps(current, mayMeasure) {
|
|
5021
|
+
let wrapping = this.heightOracle.lineWrapping;
|
|
5022
|
+
let margin = wrapping ? 10000 /* LG.MarginWrap */ : 2000 /* LG.Margin */, halfMargin = margin >> 1, doubleMargin = margin << 1;
|
|
5023
|
+
// The non-wrapping logic won't work at all in predominantly right-to-left text.
|
|
5024
|
+
if (this.defaultTextDirection != exports.Direction.LTR && !wrapping)
|
|
5025
|
+
return [];
|
|
4984
5026
|
let gaps = [];
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
5027
|
+
let addGap = (from, to, line, structure) => {
|
|
5028
|
+
if (to - from < halfMargin)
|
|
5029
|
+
return;
|
|
5030
|
+
let sel = this.state.selection.main, avoid = [sel.from];
|
|
5031
|
+
if (!sel.empty)
|
|
5032
|
+
avoid.push(sel.to);
|
|
5033
|
+
for (let pos of avoid) {
|
|
5034
|
+
if (pos > from && pos < to) {
|
|
5035
|
+
addGap(from, pos - 10 /* LG.SelectionMargin */, line, structure);
|
|
5036
|
+
addGap(pos + 10 /* LG.SelectionMargin */, to, line, structure);
|
|
5037
|
+
return;
|
|
5038
|
+
}
|
|
5039
|
+
}
|
|
5040
|
+
let gap = find(current, gap => gap.from >= line.from && gap.to <= line.to &&
|
|
5041
|
+
Math.abs(gap.from - from) < halfMargin && Math.abs(gap.to - to) < halfMargin &&
|
|
5042
|
+
!avoid.some(pos => gap.from < pos && gap.to > pos));
|
|
5043
|
+
if (!gap) {
|
|
5044
|
+
// When scrolling down, snap gap ends to line starts to avoid shifts in wrapping
|
|
5045
|
+
if (to < line.to && mayMeasure && wrapping &&
|
|
5046
|
+
mayMeasure.visibleRanges.some(r => r.from <= to && r.to >= to)) {
|
|
5047
|
+
let lineStart = mayMeasure.moveToLineBoundary(state.EditorSelection.cursor(to), false, true).head;
|
|
5048
|
+
if (lineStart > from)
|
|
5049
|
+
to = lineStart;
|
|
5050
|
+
}
|
|
5051
|
+
gap = new LineGap(from, to, this.gapSize(line, from, to, structure));
|
|
5052
|
+
}
|
|
5053
|
+
gaps.push(gap);
|
|
5054
|
+
};
|
|
4988
5055
|
for (let line of this.viewportLines) {
|
|
4989
|
-
if (line.length <
|
|
5056
|
+
if (line.length < doubleMargin)
|
|
4990
5057
|
continue;
|
|
4991
5058
|
let structure = lineStructure(line.from, line.to, this.stateDeco);
|
|
4992
|
-
if (structure.total <
|
|
5059
|
+
if (structure.total < doubleMargin)
|
|
4993
5060
|
continue;
|
|
5061
|
+
let target = this.scrollTarget ? this.scrollTarget.range.head : null;
|
|
4994
5062
|
let viewFrom, viewTo;
|
|
4995
|
-
if (
|
|
4996
|
-
let marginHeight = (
|
|
4997
|
-
|
|
4998
|
-
|
|
5063
|
+
if (wrapping) {
|
|
5064
|
+
let marginHeight = (margin / this.heightOracle.lineLength) * this.heightOracle.lineHeight;
|
|
5065
|
+
let top, bot;
|
|
5066
|
+
if (target != null) {
|
|
5067
|
+
let targetFrac = findFraction(structure, target);
|
|
5068
|
+
let spaceFrac = ((this.visibleBottom - this.visibleTop) / 2 + marginHeight) / line.height;
|
|
5069
|
+
top = targetFrac - spaceFrac;
|
|
5070
|
+
bot = targetFrac + spaceFrac;
|
|
5071
|
+
}
|
|
5072
|
+
else {
|
|
5073
|
+
top = (this.visibleTop - line.top - marginHeight) / line.height;
|
|
5074
|
+
bot = (this.visibleBottom - line.top + marginHeight) / line.height;
|
|
5075
|
+
}
|
|
5076
|
+
viewFrom = findPosition(structure, top);
|
|
5077
|
+
viewTo = findPosition(structure, bot);
|
|
4999
5078
|
}
|
|
5000
5079
|
else {
|
|
5001
5080
|
let totalWidth = structure.total * this.heightOracle.charWidth;
|
|
5002
|
-
let marginWidth =
|
|
5003
|
-
|
|
5004
|
-
|
|
5081
|
+
let marginWidth = margin * this.heightOracle.charWidth;
|
|
5082
|
+
let left, right;
|
|
5083
|
+
if (target != null) {
|
|
5084
|
+
let targetFrac = findFraction(structure, target);
|
|
5085
|
+
let spaceFrac = ((this.pixelViewport.right - this.pixelViewport.left) / 2 + marginWidth) / totalWidth;
|
|
5086
|
+
left = targetFrac - spaceFrac;
|
|
5087
|
+
right = targetFrac + spaceFrac;
|
|
5088
|
+
}
|
|
5089
|
+
else {
|
|
5090
|
+
left = (this.pixelViewport.left - marginWidth) / totalWidth;
|
|
5091
|
+
right = (this.pixelViewport.right + marginWidth) / totalWidth;
|
|
5092
|
+
}
|
|
5093
|
+
viewFrom = findPosition(structure, left);
|
|
5094
|
+
viewTo = findPosition(structure, right);
|
|
5005
5095
|
}
|
|
5006
|
-
let outside = [];
|
|
5007
5096
|
if (viewFrom > line.from)
|
|
5008
|
-
|
|
5097
|
+
addGap(line.from, viewFrom, line, structure);
|
|
5009
5098
|
if (viewTo < line.to)
|
|
5010
|
-
|
|
5011
|
-
let sel = this.state.selection.main;
|
|
5012
|
-
// Make sure the gaps don't cover a selection end
|
|
5013
|
-
if (sel.from >= line.from && sel.from <= line.to)
|
|
5014
|
-
cutRange(outside, sel.from - 10 /* LG.SelectionMargin */, sel.from + 10 /* LG.SelectionMargin */);
|
|
5015
|
-
if (!sel.empty && sel.to >= line.from && sel.to <= line.to)
|
|
5016
|
-
cutRange(outside, sel.to - 10 /* LG.SelectionMargin */, sel.to + 10 /* LG.SelectionMargin */);
|
|
5017
|
-
for (let { from, to } of outside)
|
|
5018
|
-
if (to - from > 1000 /* LG.HalfMargin */) {
|
|
5019
|
-
gaps.push(find(current, gap => gap.from >= line.from && gap.to <= line.to &&
|
|
5020
|
-
Math.abs(gap.from - from) < 1000 /* LG.HalfMargin */ && Math.abs(gap.to - to) < 1000 /* LG.HalfMargin */) ||
|
|
5021
|
-
new LineGap(from, to, this.gapSize(line, from, to, structure)));
|
|
5022
|
-
}
|
|
5099
|
+
addGap(viewTo, line.to, line, structure);
|
|
5023
5100
|
}
|
|
5024
5101
|
return gaps;
|
|
5025
5102
|
}
|
|
@@ -5117,20 +5194,6 @@ function findFraction(structure, pos) {
|
|
|
5117
5194
|
}
|
|
5118
5195
|
return counted / structure.total;
|
|
5119
5196
|
}
|
|
5120
|
-
function cutRange(ranges, from, to) {
|
|
5121
|
-
for (let i = 0; i < ranges.length; i++) {
|
|
5122
|
-
let r = ranges[i];
|
|
5123
|
-
if (r.from < to && r.to > from) {
|
|
5124
|
-
let pieces = [];
|
|
5125
|
-
if (r.from < from)
|
|
5126
|
-
pieces.push({ from: r.from, to: from });
|
|
5127
|
-
if (r.to > to)
|
|
5128
|
-
pieces.push({ from: to, to: r.to });
|
|
5129
|
-
ranges.splice(i, 1, ...pieces);
|
|
5130
|
-
i += pieces.length - 1;
|
|
5131
|
-
}
|
|
5132
|
-
}
|
|
5133
|
-
}
|
|
5134
5197
|
function find(array, f) {
|
|
5135
5198
|
for (let val of array)
|
|
5136
5199
|
if (f(val))
|
|
@@ -5430,6 +5493,227 @@ const baseTheme$1 = buildTheme("." + baseThemeID, {
|
|
|
5430
5493
|
}
|
|
5431
5494
|
}, lightDarkIDs);
|
|
5432
5495
|
|
|
5496
|
+
class DOMChange {
|
|
5497
|
+
constructor(view, start, end, typeOver) {
|
|
5498
|
+
this.typeOver = typeOver;
|
|
5499
|
+
this.bounds = null;
|
|
5500
|
+
this.text = "";
|
|
5501
|
+
let { impreciseHead: iHead, impreciseAnchor: iAnchor } = view.docView;
|
|
5502
|
+
if (start > -1 && !view.state.readOnly && (this.bounds = view.docView.domBoundsAround(start, end, 0))) {
|
|
5503
|
+
let selPoints = iHead || iAnchor ? [] : selectionPoints(view);
|
|
5504
|
+
let reader = new DOMReader(selPoints, view.state);
|
|
5505
|
+
reader.readRange(this.bounds.startDOM, this.bounds.endDOM);
|
|
5506
|
+
this.text = reader.text;
|
|
5507
|
+
this.newSel = selectionFromPoints(selPoints, this.bounds.from);
|
|
5508
|
+
}
|
|
5509
|
+
else {
|
|
5510
|
+
let domSel = view.observer.selectionRange;
|
|
5511
|
+
let head = iHead && iHead.node == domSel.focusNode && iHead.offset == domSel.focusOffset ||
|
|
5512
|
+
!contains(view.contentDOM, domSel.focusNode)
|
|
5513
|
+
? view.state.selection.main.head
|
|
5514
|
+
: view.docView.posFromDOM(domSel.focusNode, domSel.focusOffset);
|
|
5515
|
+
let anchor = iAnchor && iAnchor.node == domSel.anchorNode && iAnchor.offset == domSel.anchorOffset ||
|
|
5516
|
+
!contains(view.contentDOM, domSel.anchorNode)
|
|
5517
|
+
? view.state.selection.main.anchor
|
|
5518
|
+
: view.docView.posFromDOM(domSel.anchorNode, domSel.anchorOffset);
|
|
5519
|
+
this.newSel = state.EditorSelection.single(anchor, head);
|
|
5520
|
+
}
|
|
5521
|
+
}
|
|
5522
|
+
}
|
|
5523
|
+
function applyDOMChange(view, domChange) {
|
|
5524
|
+
let change;
|
|
5525
|
+
let { newSel } = domChange, sel = view.state.selection.main;
|
|
5526
|
+
if (domChange.bounds) {
|
|
5527
|
+
let { from, to } = domChange.bounds;
|
|
5528
|
+
let preferredPos = sel.from, preferredSide = null;
|
|
5529
|
+
// Prefer anchoring to end when Backspace is pressed (or, on
|
|
5530
|
+
// Android, when something was deleted)
|
|
5531
|
+
if (view.inputState.lastKeyCode === 8 && view.inputState.lastKeyTime > Date.now() - 100 ||
|
|
5532
|
+
browser.android && domChange.text.length < to - from) {
|
|
5533
|
+
preferredPos = sel.to;
|
|
5534
|
+
preferredSide = "end";
|
|
5535
|
+
}
|
|
5536
|
+
let diff = findDiff(view.state.doc.sliceString(from, to, LineBreakPlaceholder), domChange.text, preferredPos - from, preferredSide);
|
|
5537
|
+
if (diff) {
|
|
5538
|
+
// Chrome inserts two newlines when pressing shift-enter at the
|
|
5539
|
+
// end of a line. DomChange drops one of those.
|
|
5540
|
+
if (browser.chrome && view.inputState.lastKeyCode == 13 &&
|
|
5541
|
+
diff.toB == diff.from + 2 && domChange.text.slice(diff.from, diff.toB) == LineBreakPlaceholder + LineBreakPlaceholder)
|
|
5542
|
+
diff.toB--;
|
|
5543
|
+
change = { from: from + diff.from, to: from + diff.toA,
|
|
5544
|
+
insert: state.Text.of(domChange.text.slice(diff.from, diff.toB).split(LineBreakPlaceholder)) };
|
|
5545
|
+
}
|
|
5546
|
+
}
|
|
5547
|
+
else if (newSel && (!view.hasFocus || !view.state.facet(editable) || newSel.main.eq(sel))) {
|
|
5548
|
+
newSel = null;
|
|
5549
|
+
}
|
|
5550
|
+
if (!change && !newSel)
|
|
5551
|
+
return false;
|
|
5552
|
+
if (!change && domChange.typeOver && !sel.empty && newSel && newSel.main.empty) {
|
|
5553
|
+
// Heuristic to notice typing over a selected character
|
|
5554
|
+
change = { from: sel.from, to: sel.to, insert: view.state.doc.slice(sel.from, sel.to) };
|
|
5555
|
+
}
|
|
5556
|
+
else if (change && change.from >= sel.from && change.to <= sel.to &&
|
|
5557
|
+
(change.from != sel.from || change.to != sel.to) &&
|
|
5558
|
+
(sel.to - sel.from) - (change.to - change.from) <= 4) {
|
|
5559
|
+
// If the change is inside the selection and covers most of it,
|
|
5560
|
+
// assume it is a selection replace (with identical characters at
|
|
5561
|
+
// the start/end not included in the diff)
|
|
5562
|
+
change = {
|
|
5563
|
+
from: sel.from, to: sel.to,
|
|
5564
|
+
insert: view.state.doc.slice(sel.from, change.from).append(change.insert).append(view.state.doc.slice(change.to, sel.to))
|
|
5565
|
+
};
|
|
5566
|
+
}
|
|
5567
|
+
else if ((browser.mac || browser.android) && change && change.from == change.to && change.from == sel.head - 1 &&
|
|
5568
|
+
/^\. ?$/.test(change.insert.toString())) {
|
|
5569
|
+
// Detect insert-period-on-double-space Mac and Android behavior,
|
|
5570
|
+
// and transform it into a regular space insert.
|
|
5571
|
+
if (newSel && change.insert.length == 2)
|
|
5572
|
+
newSel = state.EditorSelection.single(newSel.main.anchor - 1, newSel.main.head - 1);
|
|
5573
|
+
change = { from: sel.from, to: sel.to, insert: state.Text.of([" "]) };
|
|
5574
|
+
}
|
|
5575
|
+
if (change) {
|
|
5576
|
+
let startState = view.state;
|
|
5577
|
+
if (browser.ios && view.inputState.flushIOSKey(view))
|
|
5578
|
+
return true;
|
|
5579
|
+
// Android browsers don't fire reasonable key events for enter,
|
|
5580
|
+
// backspace, or delete. So this detects changes that look like
|
|
5581
|
+
// they're caused by those keys, and reinterprets them as key
|
|
5582
|
+
// events. (Some of these keys are also handled by beforeinput
|
|
5583
|
+
// events and the pendingAndroidKey mechanism, but that's not
|
|
5584
|
+
// reliable in all situations.)
|
|
5585
|
+
if (browser.android &&
|
|
5586
|
+
((change.from == sel.from && change.to == sel.to &&
|
|
5587
|
+
change.insert.length == 1 && change.insert.lines == 2 &&
|
|
5588
|
+
dispatchKey(view.contentDOM, "Enter", 13)) ||
|
|
5589
|
+
(change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 &&
|
|
5590
|
+
dispatchKey(view.contentDOM, "Backspace", 8)) ||
|
|
5591
|
+
(change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
|
|
5592
|
+
dispatchKey(view.contentDOM, "Delete", 46))))
|
|
5593
|
+
return true;
|
|
5594
|
+
let text = change.insert.toString();
|
|
5595
|
+
if (view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text)))
|
|
5596
|
+
return true;
|
|
5597
|
+
if (view.inputState.composing >= 0)
|
|
5598
|
+
view.inputState.composing++;
|
|
5599
|
+
let tr;
|
|
5600
|
+
if (change.from >= sel.from && change.to <= sel.to && change.to - change.from >= (sel.to - sel.from) / 3 &&
|
|
5601
|
+
(!newSel || newSel.main.empty && newSel.main.from == change.from + change.insert.length) &&
|
|
5602
|
+
view.inputState.composing < 0) {
|
|
5603
|
+
let before = sel.from < change.from ? startState.sliceDoc(sel.from, change.from) : "";
|
|
5604
|
+
let after = sel.to > change.to ? startState.sliceDoc(change.to, sel.to) : "";
|
|
5605
|
+
tr = startState.replaceSelection(view.state.toText(before + change.insert.sliceString(0, undefined, view.state.lineBreak) + after));
|
|
5606
|
+
}
|
|
5607
|
+
else {
|
|
5608
|
+
let changes = startState.changes(change);
|
|
5609
|
+
let mainSel = newSel && !startState.selection.main.eq(newSel.main) && newSel.main.to <= changes.newLength
|
|
5610
|
+
? newSel.main : undefined;
|
|
5611
|
+
// Try to apply a composition change to all cursors
|
|
5612
|
+
if (startState.selection.ranges.length > 1 && view.inputState.composing >= 0 &&
|
|
5613
|
+
change.to <= sel.to && change.to >= sel.to - 10) {
|
|
5614
|
+
let replaced = view.state.sliceDoc(change.from, change.to);
|
|
5615
|
+
let compositionRange = compositionSurroundingNode(view) || view.state.doc.lineAt(sel.head);
|
|
5616
|
+
let offset = sel.to - change.to, size = sel.to - sel.from;
|
|
5617
|
+
tr = startState.changeByRange(range => {
|
|
5618
|
+
if (range.from == sel.from && range.to == sel.to)
|
|
5619
|
+
return { changes, range: mainSel || range.map(changes) };
|
|
5620
|
+
let to = range.to - offset, from = to - replaced.length;
|
|
5621
|
+
if (range.to - range.from != size || view.state.sliceDoc(from, to) != replaced ||
|
|
5622
|
+
// Unfortunately, there's no way to make multiple
|
|
5623
|
+
// changes in the same node work without aborting
|
|
5624
|
+
// composition, so cursors in the composition range are
|
|
5625
|
+
// ignored.
|
|
5626
|
+
compositionRange && range.to >= compositionRange.from && range.from <= compositionRange.to)
|
|
5627
|
+
return { range };
|
|
5628
|
+
let rangeChanges = startState.changes({ from, to, insert: change.insert }), selOff = range.to - sel.to;
|
|
5629
|
+
return {
|
|
5630
|
+
changes: rangeChanges,
|
|
5631
|
+
range: !mainSel ? range.map(rangeChanges) :
|
|
5632
|
+
state.EditorSelection.range(Math.max(0, mainSel.anchor + selOff), Math.max(0, mainSel.head + selOff))
|
|
5633
|
+
};
|
|
5634
|
+
});
|
|
5635
|
+
}
|
|
5636
|
+
else {
|
|
5637
|
+
tr = {
|
|
5638
|
+
changes,
|
|
5639
|
+
selection: mainSel && startState.selection.replaceRange(mainSel)
|
|
5640
|
+
};
|
|
5641
|
+
}
|
|
5642
|
+
}
|
|
5643
|
+
let userEvent = "input.type";
|
|
5644
|
+
if (view.composing) {
|
|
5645
|
+
userEvent += ".compose";
|
|
5646
|
+
if (view.inputState.compositionFirstChange) {
|
|
5647
|
+
userEvent += ".start";
|
|
5648
|
+
view.inputState.compositionFirstChange = false;
|
|
5649
|
+
}
|
|
5650
|
+
}
|
|
5651
|
+
view.dispatch(tr, { scrollIntoView: true, userEvent });
|
|
5652
|
+
return true;
|
|
5653
|
+
}
|
|
5654
|
+
else if (newSel && !newSel.main.eq(sel)) {
|
|
5655
|
+
let scrollIntoView = false, userEvent = "select";
|
|
5656
|
+
if (view.inputState.lastSelectionTime > Date.now() - 50) {
|
|
5657
|
+
if (view.inputState.lastSelectionOrigin == "select")
|
|
5658
|
+
scrollIntoView = true;
|
|
5659
|
+
userEvent = view.inputState.lastSelectionOrigin;
|
|
5660
|
+
}
|
|
5661
|
+
view.dispatch({ selection: newSel, scrollIntoView, userEvent });
|
|
5662
|
+
return true;
|
|
5663
|
+
}
|
|
5664
|
+
else {
|
|
5665
|
+
return false;
|
|
5666
|
+
}
|
|
5667
|
+
}
|
|
5668
|
+
function findDiff(a, b, preferredPos, preferredSide) {
|
|
5669
|
+
let minLen = Math.min(a.length, b.length);
|
|
5670
|
+
let from = 0;
|
|
5671
|
+
while (from < minLen && a.charCodeAt(from) == b.charCodeAt(from))
|
|
5672
|
+
from++;
|
|
5673
|
+
if (from == minLen && a.length == b.length)
|
|
5674
|
+
return null;
|
|
5675
|
+
let toA = a.length, toB = b.length;
|
|
5676
|
+
while (toA > 0 && toB > 0 && a.charCodeAt(toA - 1) == b.charCodeAt(toB - 1)) {
|
|
5677
|
+
toA--;
|
|
5678
|
+
toB--;
|
|
5679
|
+
}
|
|
5680
|
+
if (preferredSide == "end") {
|
|
5681
|
+
let adjust = Math.max(0, from - Math.min(toA, toB));
|
|
5682
|
+
preferredPos -= toA + adjust - from;
|
|
5683
|
+
}
|
|
5684
|
+
if (toA < from && a.length < b.length) {
|
|
5685
|
+
let move = preferredPos <= from && preferredPos >= toA ? from - preferredPos : 0;
|
|
5686
|
+
from -= move;
|
|
5687
|
+
toB = from + (toB - toA);
|
|
5688
|
+
toA = from;
|
|
5689
|
+
}
|
|
5690
|
+
else if (toB < from) {
|
|
5691
|
+
let move = preferredPos <= from && preferredPos >= toB ? from - preferredPos : 0;
|
|
5692
|
+
from -= move;
|
|
5693
|
+
toA = from + (toA - toB);
|
|
5694
|
+
toB = from;
|
|
5695
|
+
}
|
|
5696
|
+
return { from, toA, toB };
|
|
5697
|
+
}
|
|
5698
|
+
function selectionPoints(view) {
|
|
5699
|
+
let result = [];
|
|
5700
|
+
if (view.root.activeElement != view.contentDOM)
|
|
5701
|
+
return result;
|
|
5702
|
+
let { anchorNode, anchorOffset, focusNode, focusOffset } = view.observer.selectionRange;
|
|
5703
|
+
if (anchorNode) {
|
|
5704
|
+
result.push(new DOMPoint(anchorNode, anchorOffset));
|
|
5705
|
+
if (focusNode != anchorNode || focusOffset != anchorOffset)
|
|
5706
|
+
result.push(new DOMPoint(focusNode, focusOffset));
|
|
5707
|
+
}
|
|
5708
|
+
return result;
|
|
5709
|
+
}
|
|
5710
|
+
function selectionFromPoints(points, base) {
|
|
5711
|
+
if (points.length == 0)
|
|
5712
|
+
return null;
|
|
5713
|
+
let anchor = points[0].pos, head = points.length == 2 ? points[1].pos : anchor;
|
|
5714
|
+
return anchor > -1 && head > -1 ? state.EditorSelection.single(anchor + base, head + base) : null;
|
|
5715
|
+
}
|
|
5716
|
+
|
|
5433
5717
|
const observeOptions = {
|
|
5434
5718
|
childList: true,
|
|
5435
5719
|
characterData: true,
|
|
@@ -5441,10 +5725,8 @@ const observeOptions = {
|
|
|
5441
5725
|
// DOMCharacterDataModified there
|
|
5442
5726
|
const useCharData = browser.ie && browser.ie_version <= 11;
|
|
5443
5727
|
class DOMObserver {
|
|
5444
|
-
constructor(view
|
|
5728
|
+
constructor(view) {
|
|
5445
5729
|
this.view = view;
|
|
5446
|
-
this.onChange = onChange;
|
|
5447
|
-
this.onScrollChanged = onScrollChanged;
|
|
5448
5730
|
this.active = false;
|
|
5449
5731
|
// The known selection. Kept in our own object, as opposed to just
|
|
5450
5732
|
// directly accessing the selection because:
|
|
@@ -5459,6 +5741,7 @@ class DOMObserver {
|
|
|
5459
5741
|
this.resizeTimeout = -1;
|
|
5460
5742
|
this.queue = [];
|
|
5461
5743
|
this.delayedAndroidKey = null;
|
|
5744
|
+
this.flushingAndroidKey = -1;
|
|
5462
5745
|
this.lastChange = 0;
|
|
5463
5746
|
this.scrollTargets = [];
|
|
5464
5747
|
this.intersection = null;
|
|
@@ -5527,6 +5810,11 @@ class DOMObserver {
|
|
|
5527
5810
|
this.listenForScroll();
|
|
5528
5811
|
this.readSelectionRange();
|
|
5529
5812
|
}
|
|
5813
|
+
onScrollChanged(e) {
|
|
5814
|
+
this.view.inputState.runScrollHandlers(this.view, e);
|
|
5815
|
+
if (this.intersecting)
|
|
5816
|
+
this.view.measure();
|
|
5817
|
+
}
|
|
5530
5818
|
onScroll(e) {
|
|
5531
5819
|
if (this.intersecting)
|
|
5532
5820
|
this.flush(false);
|
|
@@ -5686,14 +5974,17 @@ class DOMObserver {
|
|
|
5686
5974
|
// them or, if that has no effect, dispatches the given key.
|
|
5687
5975
|
delayAndroidKey(key, keyCode) {
|
|
5688
5976
|
var _a;
|
|
5689
|
-
if (!this.delayedAndroidKey)
|
|
5690
|
-
|
|
5977
|
+
if (!this.delayedAndroidKey) {
|
|
5978
|
+
let flush = () => {
|
|
5691
5979
|
let key = this.delayedAndroidKey;
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5980
|
+
if (key) {
|
|
5981
|
+
this.clearDelayedAndroidKey();
|
|
5982
|
+
if (!this.flush() && key.force)
|
|
5983
|
+
dispatchKey(this.dom, key.key, key.keyCode);
|
|
5984
|
+
}
|
|
5985
|
+
};
|
|
5986
|
+
this.flushingAndroidKey = this.view.win.requestAnimationFrame(flush);
|
|
5987
|
+
}
|
|
5697
5988
|
// Since backspace beforeinput is sometimes signalled spuriously,
|
|
5698
5989
|
// Enter always takes precedence.
|
|
5699
5990
|
if (!this.delayedAndroidKey || key == "Enter")
|
|
@@ -5706,6 +5997,11 @@ class DOMObserver {
|
|
|
5706
5997
|
force: this.lastChange < Date.now() - 50 || !!((_a = this.delayedAndroidKey) === null || _a === void 0 ? void 0 : _a.force)
|
|
5707
5998
|
};
|
|
5708
5999
|
}
|
|
6000
|
+
clearDelayedAndroidKey() {
|
|
6001
|
+
this.win.cancelAnimationFrame(this.flushingAndroidKey);
|
|
6002
|
+
this.delayedAndroidKey = null;
|
|
6003
|
+
this.flushingAndroidKey = -1;
|
|
6004
|
+
}
|
|
5709
6005
|
flushSoon() {
|
|
5710
6006
|
if (this.delayedFlush < 0)
|
|
5711
6007
|
this.delayedFlush = this.view.win.requestAnimationFrame(() => { this.delayedFlush = -1; this.flush(); });
|
|
@@ -5740,6 +6036,17 @@ class DOMObserver {
|
|
|
5740
6036
|
}
|
|
5741
6037
|
return { from, to, typeOver };
|
|
5742
6038
|
}
|
|
6039
|
+
readChange() {
|
|
6040
|
+
let { from, to, typeOver } = this.processRecords();
|
|
6041
|
+
let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
|
|
6042
|
+
if (from < 0 && !newSel)
|
|
6043
|
+
return null;
|
|
6044
|
+
if (from > -1)
|
|
6045
|
+
this.lastChange = Date.now();
|
|
6046
|
+
this.view.inputState.lastFocusTime = 0;
|
|
6047
|
+
this.selectionChanged = false;
|
|
6048
|
+
return new DOMChange(this.view, from, to, typeOver);
|
|
6049
|
+
}
|
|
5743
6050
|
// Apply pending changes, if any
|
|
5744
6051
|
flush(readSelection = true) {
|
|
5745
6052
|
// Completely hold off flushing when pending keys are set—the code
|
|
@@ -5749,16 +6056,11 @@ class DOMObserver {
|
|
|
5749
6056
|
return false;
|
|
5750
6057
|
if (readSelection)
|
|
5751
6058
|
this.readSelectionRange();
|
|
5752
|
-
let
|
|
5753
|
-
|
|
5754
|
-
if (from < 0 && !newSel)
|
|
6059
|
+
let domChange = this.readChange();
|
|
6060
|
+
if (!domChange)
|
|
5755
6061
|
return false;
|
|
5756
|
-
if (from > -1)
|
|
5757
|
-
this.lastChange = Date.now();
|
|
5758
|
-
this.view.inputState.lastFocusTime = 0;
|
|
5759
|
-
this.selectionChanged = false;
|
|
5760
6062
|
let startState = this.view.state;
|
|
5761
|
-
let handled = this.
|
|
6063
|
+
let handled = applyDOMChange(this.view, domChange);
|
|
5762
6064
|
// The view wasn't updated
|
|
5763
6065
|
if (this.view.state == startState)
|
|
5764
6066
|
this.view.update([]);
|
|
@@ -5814,6 +6116,8 @@ class DOMObserver {
|
|
|
5814
6116
|
this.removeWindowListeners(this.win);
|
|
5815
6117
|
clearTimeout(this.parentCheck);
|
|
5816
6118
|
clearTimeout(this.resizeTimeout);
|
|
6119
|
+
this.win.cancelAnimationFrame(this.delayedFlush);
|
|
6120
|
+
this.win.cancelAnimationFrame(this.flushingAndroidKey);
|
|
5817
6121
|
}
|
|
5818
6122
|
}
|
|
5819
6123
|
function findChild(cView, dom, dir) {
|
|
@@ -5855,213 +6159,6 @@ function safariSelectionRangeHack(view) {
|
|
|
5855
6159
|
return { anchorNode, anchorOffset, focusNode, focusOffset };
|
|
5856
6160
|
}
|
|
5857
6161
|
|
|
5858
|
-
function applyDOMChange(view, start, end, typeOver) {
|
|
5859
|
-
let change, newSel;
|
|
5860
|
-
let sel = view.state.selection.main;
|
|
5861
|
-
if (start > -1) {
|
|
5862
|
-
let bounds = view.docView.domBoundsAround(start, end, 0);
|
|
5863
|
-
if (!bounds || view.state.readOnly)
|
|
5864
|
-
return false;
|
|
5865
|
-
let { from, to } = bounds;
|
|
5866
|
-
let selPoints = view.docView.impreciseHead || view.docView.impreciseAnchor ? [] : selectionPoints(view);
|
|
5867
|
-
let reader = new DOMReader(selPoints, view.state);
|
|
5868
|
-
reader.readRange(bounds.startDOM, bounds.endDOM);
|
|
5869
|
-
let preferredPos = sel.from, preferredSide = null;
|
|
5870
|
-
// Prefer anchoring to end when Backspace is pressed (or, on
|
|
5871
|
-
// Android, when something was deleted)
|
|
5872
|
-
if (view.inputState.lastKeyCode === 8 && view.inputState.lastKeyTime > Date.now() - 100 ||
|
|
5873
|
-
browser.android && reader.text.length < to - from) {
|
|
5874
|
-
preferredPos = sel.to;
|
|
5875
|
-
preferredSide = "end";
|
|
5876
|
-
}
|
|
5877
|
-
let diff = findDiff(view.state.doc.sliceString(from, to, LineBreakPlaceholder), reader.text, preferredPos - from, preferredSide);
|
|
5878
|
-
if (diff) {
|
|
5879
|
-
// Chrome inserts two newlines when pressing shift-enter at the
|
|
5880
|
-
// end of a line. This drops one of those.
|
|
5881
|
-
if (browser.chrome && view.inputState.lastKeyCode == 13 &&
|
|
5882
|
-
diff.toB == diff.from + 2 && reader.text.slice(diff.from, diff.toB) == LineBreakPlaceholder + LineBreakPlaceholder)
|
|
5883
|
-
diff.toB--;
|
|
5884
|
-
change = { from: from + diff.from, to: from + diff.toA,
|
|
5885
|
-
insert: state.Text.of(reader.text.slice(diff.from, diff.toB).split(LineBreakPlaceholder)) };
|
|
5886
|
-
}
|
|
5887
|
-
newSel = selectionFromPoints(selPoints, from);
|
|
5888
|
-
}
|
|
5889
|
-
else if (view.hasFocus || !view.state.facet(editable)) {
|
|
5890
|
-
let domSel = view.observer.selectionRange;
|
|
5891
|
-
let { impreciseHead: iHead, impreciseAnchor: iAnchor } = view.docView;
|
|
5892
|
-
let head = iHead && iHead.node == domSel.focusNode && iHead.offset == domSel.focusOffset ||
|
|
5893
|
-
!contains(view.contentDOM, domSel.focusNode)
|
|
5894
|
-
? view.state.selection.main.head
|
|
5895
|
-
: view.docView.posFromDOM(domSel.focusNode, domSel.focusOffset);
|
|
5896
|
-
let anchor = iAnchor && iAnchor.node == domSel.anchorNode && iAnchor.offset == domSel.anchorOffset ||
|
|
5897
|
-
!contains(view.contentDOM, domSel.anchorNode)
|
|
5898
|
-
? view.state.selection.main.anchor
|
|
5899
|
-
: view.docView.posFromDOM(domSel.anchorNode, domSel.anchorOffset);
|
|
5900
|
-
if (head != sel.head || anchor != sel.anchor)
|
|
5901
|
-
newSel = state.EditorSelection.single(anchor, head);
|
|
5902
|
-
}
|
|
5903
|
-
if (!change && !newSel)
|
|
5904
|
-
return false;
|
|
5905
|
-
// Heuristic to notice typing over a selected character
|
|
5906
|
-
if (!change && typeOver && !sel.empty && newSel && newSel.main.empty)
|
|
5907
|
-
change = { from: sel.from, to: sel.to, insert: view.state.doc.slice(sel.from, sel.to) };
|
|
5908
|
-
// If the change is inside the selection and covers most of it,
|
|
5909
|
-
// assume it is a selection replace (with identical characters at
|
|
5910
|
-
// the start/end not included in the diff)
|
|
5911
|
-
else if (change && change.from >= sel.from && change.to <= sel.to &&
|
|
5912
|
-
(change.from != sel.from || change.to != sel.to) &&
|
|
5913
|
-
(sel.to - sel.from) - (change.to - change.from) <= 4)
|
|
5914
|
-
change = {
|
|
5915
|
-
from: sel.from, to: sel.to,
|
|
5916
|
-
insert: view.state.doc.slice(sel.from, change.from).append(change.insert).append(view.state.doc.slice(change.to, sel.to))
|
|
5917
|
-
};
|
|
5918
|
-
// Detect insert-period-on-double-space Mac behavior, and transform
|
|
5919
|
-
// it into a regular space insert.
|
|
5920
|
-
else if ((browser.mac || browser.android) && change && change.from == change.to && change.from == sel.head - 1 &&
|
|
5921
|
-
change.insert.toString() == ".")
|
|
5922
|
-
change = { from: sel.from, to: sel.to, insert: state.Text.of([" "]) };
|
|
5923
|
-
if (change) {
|
|
5924
|
-
let startState = view.state;
|
|
5925
|
-
if (browser.ios && view.inputState.flushIOSKey(view))
|
|
5926
|
-
return true;
|
|
5927
|
-
// Android browsers don't fire reasonable key events for enter,
|
|
5928
|
-
// backspace, or delete. So this detects changes that look like
|
|
5929
|
-
// they're caused by those keys, and reinterprets them as key
|
|
5930
|
-
// events. (Some of these keys are also handled by beforeinput
|
|
5931
|
-
// events and the pendingAndroidKey mechanism, but that's not
|
|
5932
|
-
// reliable in all situations.)
|
|
5933
|
-
if (browser.android &&
|
|
5934
|
-
((change.from == sel.from && change.to == sel.to &&
|
|
5935
|
-
change.insert.length == 1 && change.insert.lines == 2 &&
|
|
5936
|
-
dispatchKey(view.contentDOM, "Enter", 13)) ||
|
|
5937
|
-
(change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 &&
|
|
5938
|
-
dispatchKey(view.contentDOM, "Backspace", 8)) ||
|
|
5939
|
-
(change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
|
|
5940
|
-
dispatchKey(view.contentDOM, "Delete", 46))))
|
|
5941
|
-
return true;
|
|
5942
|
-
let text = change.insert.toString();
|
|
5943
|
-
if (view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text)))
|
|
5944
|
-
return true;
|
|
5945
|
-
if (view.inputState.composing >= 0)
|
|
5946
|
-
view.inputState.composing++;
|
|
5947
|
-
let tr;
|
|
5948
|
-
if (change.from >= sel.from && change.to <= sel.to && change.to - change.from >= (sel.to - sel.from) / 3 &&
|
|
5949
|
-
(!newSel || newSel.main.empty && newSel.main.from == change.from + change.insert.length) &&
|
|
5950
|
-
view.inputState.composing < 0) {
|
|
5951
|
-
let before = sel.from < change.from ? startState.sliceDoc(sel.from, change.from) : "";
|
|
5952
|
-
let after = sel.to > change.to ? startState.sliceDoc(change.to, sel.to) : "";
|
|
5953
|
-
tr = startState.replaceSelection(view.state.toText(before + change.insert.sliceString(0, undefined, view.state.lineBreak) + after));
|
|
5954
|
-
}
|
|
5955
|
-
else {
|
|
5956
|
-
let changes = startState.changes(change);
|
|
5957
|
-
let mainSel = newSel && !startState.selection.main.eq(newSel.main) && newSel.main.to <= changes.newLength
|
|
5958
|
-
? newSel.main : undefined;
|
|
5959
|
-
// Try to apply a composition change to all cursors
|
|
5960
|
-
if (startState.selection.ranges.length > 1 && view.inputState.composing >= 0 &&
|
|
5961
|
-
change.to <= sel.to && change.to >= sel.to - 10) {
|
|
5962
|
-
let replaced = view.state.sliceDoc(change.from, change.to);
|
|
5963
|
-
let compositionRange = compositionSurroundingNode(view) || view.state.doc.lineAt(sel.head);
|
|
5964
|
-
let offset = sel.to - change.to, size = sel.to - sel.from;
|
|
5965
|
-
tr = startState.changeByRange(range => {
|
|
5966
|
-
if (range.from == sel.from && range.to == sel.to)
|
|
5967
|
-
return { changes, range: mainSel || range.map(changes) };
|
|
5968
|
-
let to = range.to - offset, from = to - replaced.length;
|
|
5969
|
-
if (range.to - range.from != size || view.state.sliceDoc(from, to) != replaced ||
|
|
5970
|
-
// Unfortunately, there's no way to make multiple
|
|
5971
|
-
// changes in the same node work without aborting
|
|
5972
|
-
// composition, so cursors in the composition range are
|
|
5973
|
-
// ignored.
|
|
5974
|
-
compositionRange && range.to >= compositionRange.from && range.from <= compositionRange.to)
|
|
5975
|
-
return { range };
|
|
5976
|
-
let rangeChanges = startState.changes({ from, to, insert: change.insert }), selOff = range.to - sel.to;
|
|
5977
|
-
return {
|
|
5978
|
-
changes: rangeChanges,
|
|
5979
|
-
range: !mainSel ? range.map(rangeChanges) :
|
|
5980
|
-
state.EditorSelection.range(Math.max(0, mainSel.anchor + selOff), Math.max(0, mainSel.head + selOff))
|
|
5981
|
-
};
|
|
5982
|
-
});
|
|
5983
|
-
}
|
|
5984
|
-
else {
|
|
5985
|
-
tr = {
|
|
5986
|
-
changes,
|
|
5987
|
-
selection: mainSel && startState.selection.replaceRange(mainSel)
|
|
5988
|
-
};
|
|
5989
|
-
}
|
|
5990
|
-
}
|
|
5991
|
-
let userEvent = "input.type";
|
|
5992
|
-
if (view.composing) {
|
|
5993
|
-
userEvent += ".compose";
|
|
5994
|
-
if (view.inputState.compositionFirstChange) {
|
|
5995
|
-
userEvent += ".start";
|
|
5996
|
-
view.inputState.compositionFirstChange = false;
|
|
5997
|
-
}
|
|
5998
|
-
}
|
|
5999
|
-
view.dispatch(tr, { scrollIntoView: true, userEvent });
|
|
6000
|
-
return true;
|
|
6001
|
-
}
|
|
6002
|
-
else if (newSel && !newSel.main.eq(sel)) {
|
|
6003
|
-
let scrollIntoView = false, userEvent = "select";
|
|
6004
|
-
if (view.inputState.lastSelectionTime > Date.now() - 50) {
|
|
6005
|
-
if (view.inputState.lastSelectionOrigin == "select")
|
|
6006
|
-
scrollIntoView = true;
|
|
6007
|
-
userEvent = view.inputState.lastSelectionOrigin;
|
|
6008
|
-
}
|
|
6009
|
-
view.dispatch({ selection: newSel, scrollIntoView, userEvent });
|
|
6010
|
-
return true;
|
|
6011
|
-
}
|
|
6012
|
-
else {
|
|
6013
|
-
return false;
|
|
6014
|
-
}
|
|
6015
|
-
}
|
|
6016
|
-
function findDiff(a, b, preferredPos, preferredSide) {
|
|
6017
|
-
let minLen = Math.min(a.length, b.length);
|
|
6018
|
-
let from = 0;
|
|
6019
|
-
while (from < minLen && a.charCodeAt(from) == b.charCodeAt(from))
|
|
6020
|
-
from++;
|
|
6021
|
-
if (from == minLen && a.length == b.length)
|
|
6022
|
-
return null;
|
|
6023
|
-
let toA = a.length, toB = b.length;
|
|
6024
|
-
while (toA > 0 && toB > 0 && a.charCodeAt(toA - 1) == b.charCodeAt(toB - 1)) {
|
|
6025
|
-
toA--;
|
|
6026
|
-
toB--;
|
|
6027
|
-
}
|
|
6028
|
-
if (preferredSide == "end") {
|
|
6029
|
-
let adjust = Math.max(0, from - Math.min(toA, toB));
|
|
6030
|
-
preferredPos -= toA + adjust - from;
|
|
6031
|
-
}
|
|
6032
|
-
if (toA < from && a.length < b.length) {
|
|
6033
|
-
let move = preferredPos <= from && preferredPos >= toA ? from - preferredPos : 0;
|
|
6034
|
-
from -= move;
|
|
6035
|
-
toB = from + (toB - toA);
|
|
6036
|
-
toA = from;
|
|
6037
|
-
}
|
|
6038
|
-
else if (toB < from) {
|
|
6039
|
-
let move = preferredPos <= from && preferredPos >= toB ? from - preferredPos : 0;
|
|
6040
|
-
from -= move;
|
|
6041
|
-
toA = from + (toA - toB);
|
|
6042
|
-
toB = from;
|
|
6043
|
-
}
|
|
6044
|
-
return { from, toA, toB };
|
|
6045
|
-
}
|
|
6046
|
-
function selectionPoints(view) {
|
|
6047
|
-
let result = [];
|
|
6048
|
-
if (view.root.activeElement != view.contentDOM)
|
|
6049
|
-
return result;
|
|
6050
|
-
let { anchorNode, anchorOffset, focusNode, focusOffset } = view.observer.selectionRange;
|
|
6051
|
-
if (anchorNode) {
|
|
6052
|
-
result.push(new DOMPoint(anchorNode, anchorOffset));
|
|
6053
|
-
if (focusNode != anchorNode || focusOffset != anchorOffset)
|
|
6054
|
-
result.push(new DOMPoint(focusNode, focusOffset));
|
|
6055
|
-
}
|
|
6056
|
-
return result;
|
|
6057
|
-
}
|
|
6058
|
-
function selectionFromPoints(points, base) {
|
|
6059
|
-
if (points.length == 0)
|
|
6060
|
-
return null;
|
|
6061
|
-
let anchor = points[0].pos, head = points.length == 2 ? points[1].pos : anchor;
|
|
6062
|
-
return anchor > -1 && head > -1 ? state.EditorSelection.single(anchor + base, head + base) : null;
|
|
6063
|
-
}
|
|
6064
|
-
|
|
6065
6162
|
// The editor's update state machine looks something like this:
|
|
6066
6163
|
//
|
|
6067
6164
|
// Idle → Updating ⇆ Idle (unchecked) → Measuring → Idle
|
|
@@ -6124,13 +6221,7 @@ class EditorView {
|
|
|
6124
6221
|
this.plugins = this.state.facet(viewPlugin).map(spec => new PluginInstance(spec));
|
|
6125
6222
|
for (let plugin of this.plugins)
|
|
6126
6223
|
plugin.update(this);
|
|
6127
|
-
this.observer = new DOMObserver(this
|
|
6128
|
-
return applyDOMChange(this, from, to, typeOver);
|
|
6129
|
-
}, event => {
|
|
6130
|
-
this.inputState.runScrollHandlers(this, event);
|
|
6131
|
-
if (this.observer.intersecting)
|
|
6132
|
-
this.measure();
|
|
6133
|
-
});
|
|
6224
|
+
this.observer = new DOMObserver(this);
|
|
6134
6225
|
this.inputState = new InputState(this);
|
|
6135
6226
|
this.inputState.ensureHandlers(this, this.plugins);
|
|
6136
6227
|
this.docView = new DocView(this);
|
|
@@ -6214,7 +6305,20 @@ class EditorView {
|
|
|
6214
6305
|
this.viewState.state = state$1;
|
|
6215
6306
|
return;
|
|
6216
6307
|
}
|
|
6217
|
-
|
|
6308
|
+
// If there was a pending DOM change, eagerly read it and try to
|
|
6309
|
+
// apply it after the given transactions.
|
|
6310
|
+
let pendingKey = this.observer.delayedAndroidKey, domChange = null;
|
|
6311
|
+
if (pendingKey) {
|
|
6312
|
+
this.observer.clearDelayedAndroidKey();
|
|
6313
|
+
domChange = this.observer.readChange();
|
|
6314
|
+
// Only try to apply DOM changes if the transactions didn't
|
|
6315
|
+
// change the doc or selection.
|
|
6316
|
+
if (domChange && !this.state.doc.eq(state$1.doc) || !this.state.selection.eq(state$1.selection))
|
|
6317
|
+
domChange = null;
|
|
6318
|
+
}
|
|
6319
|
+
else {
|
|
6320
|
+
this.observer.clear();
|
|
6321
|
+
}
|
|
6218
6322
|
// When the phrases change, redraw the editor
|
|
6219
6323
|
if (state$1.facet(state.EditorState.phrases) != this.state.facet(state.EditorState.phrases))
|
|
6220
6324
|
return this.setState(state$1);
|
|
@@ -6256,6 +6360,10 @@ class EditorView {
|
|
|
6256
6360
|
if (!update.empty)
|
|
6257
6361
|
for (let listener of this.state.facet(updateListener))
|
|
6258
6362
|
listener(update);
|
|
6363
|
+
if (domChange) {
|
|
6364
|
+
if (!applyDOMChange(this, domChange) && pendingKey.force)
|
|
6365
|
+
dispatchKey(this.contentDOM, pendingKey.key, pendingKey.keyCode);
|
|
6366
|
+
}
|
|
6259
6367
|
}
|
|
6260
6368
|
/**
|
|
6261
6369
|
Reset the view to the given state. (This will cause the entire
|
|
@@ -7093,6 +7201,7 @@ function buildKeymap(bindings, platform = currentPlatform) {
|
|
|
7093
7201
|
throw new Error("Key binding " + name + " is used both as a regular binding and as a multi-stroke prefix");
|
|
7094
7202
|
};
|
|
7095
7203
|
let add = (scope, key, command, preventDefault) => {
|
|
7204
|
+
var _a, _b;
|
|
7096
7205
|
let scopeObj = bound[scope] || (bound[scope] = Object.create(null));
|
|
7097
7206
|
let parts = key.split(/ (?!$)/).map(k => normalizeKeyName(k, platform));
|
|
7098
7207
|
for (let i = 1; i < parts.length; i++) {
|
|
@@ -7101,7 +7210,7 @@ function buildKeymap(bindings, platform = currentPlatform) {
|
|
|
7101
7210
|
if (!scopeObj[prefix])
|
|
7102
7211
|
scopeObj[prefix] = {
|
|
7103
7212
|
preventDefault: true,
|
|
7104
|
-
|
|
7213
|
+
run: [(view) => {
|
|
7105
7214
|
let ourObj = storedPrefix = { view, prefix, scope };
|
|
7106
7215
|
setTimeout(() => { if (storedPrefix == ourObj)
|
|
7107
7216
|
storedPrefix = null; }, PrefixTimeout);
|
|
@@ -7111,16 +7220,26 @@ function buildKeymap(bindings, platform = currentPlatform) {
|
|
|
7111
7220
|
}
|
|
7112
7221
|
let full = parts.join(" ");
|
|
7113
7222
|
checkPrefix(full, false);
|
|
7114
|
-
let binding = scopeObj[full] || (scopeObj[full] = { preventDefault: false,
|
|
7115
|
-
|
|
7223
|
+
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()) || [] });
|
|
7224
|
+
if (command)
|
|
7225
|
+
binding.run.push(command);
|
|
7116
7226
|
if (preventDefault)
|
|
7117
7227
|
binding.preventDefault = true;
|
|
7118
7228
|
};
|
|
7119
7229
|
for (let b of bindings) {
|
|
7230
|
+
let scopes = b.scope ? b.scope.split(" ") : ["editor"];
|
|
7231
|
+
if (b.any)
|
|
7232
|
+
for (let scope of scopes) {
|
|
7233
|
+
let scopeObj = bound[scope] || (bound[scope] = Object.create(null));
|
|
7234
|
+
if (!scopeObj._any)
|
|
7235
|
+
scopeObj._any = { preventDefault: false, run: [] };
|
|
7236
|
+
for (let key in scopeObj)
|
|
7237
|
+
scopeObj[key].run.push(b.any);
|
|
7238
|
+
}
|
|
7120
7239
|
let name = b[platform] || b.key;
|
|
7121
7240
|
if (!name)
|
|
7122
7241
|
continue;
|
|
7123
|
-
for (let scope of
|
|
7242
|
+
for (let scope of scopes) {
|
|
7124
7243
|
add(scope, name, b.run, b.preventDefault);
|
|
7125
7244
|
if (b.shift)
|
|
7126
7245
|
add(scope, "Shift-" + name, b.shift, b.preventDefault);
|
|
@@ -7137,11 +7256,15 @@ function runHandlers(map, event, view, scope) {
|
|
|
7137
7256
|
if (fallthrough = modifierCodes.indexOf(event.keyCode) < 0)
|
|
7138
7257
|
storedPrefix = null;
|
|
7139
7258
|
}
|
|
7259
|
+
let ran = new Set;
|
|
7140
7260
|
let runFor = (binding) => {
|
|
7141
7261
|
if (binding) {
|
|
7142
|
-
for (let cmd of binding.
|
|
7143
|
-
if (cmd
|
|
7144
|
-
|
|
7262
|
+
for (let cmd of binding.run)
|
|
7263
|
+
if (!ran.has(cmd)) {
|
|
7264
|
+
ran.add(cmd);
|
|
7265
|
+
if (cmd(view, event))
|
|
7266
|
+
return true;
|
|
7267
|
+
}
|
|
7145
7268
|
if (binding.preventDefault)
|
|
7146
7269
|
fallthrough = true;
|
|
7147
7270
|
}
|
|
@@ -7163,6 +7286,8 @@ function runHandlers(map, event, view, scope) {
|
|
|
7163
7286
|
if (runFor(scopeObj[prefix + modifiers(name, event, true)]))
|
|
7164
7287
|
return true;
|
|
7165
7288
|
}
|
|
7289
|
+
if (runFor(scopeObj._any))
|
|
7290
|
+
return true;
|
|
7166
7291
|
}
|
|
7167
7292
|
return fallthrough;
|
|
7168
7293
|
}
|