@codemirror/view 6.2.4 → 6.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/dist/index.cjs +120 -68
- package/dist/index.d.ts +6 -1
- package/dist/index.js +120 -68
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
## 6.3.0 (2022-09-28)
|
|
2
|
+
|
|
3
|
+
### Bug fixes
|
|
4
|
+
|
|
5
|
+
Reduce the amount of wrap-point jittering when scrolling through a very long wrapped line.
|
|
6
|
+
|
|
7
|
+
Fix an issue where scrolling to content that wasn't currently drawn due to being on a very long line would often fail to scroll to the right position.
|
|
8
|
+
|
|
9
|
+
Suppress double-space-adds-period behavior on Chrome Mac when it behaves weirdly next to widget.
|
|
10
|
+
|
|
11
|
+
### New features
|
|
12
|
+
|
|
13
|
+
Key binding objects with an `any` property will now add handlers that are called for any key, within the ordering of the keybindings.
|
|
14
|
+
|
|
15
|
+
## 6.2.5 (2022-09-24)
|
|
16
|
+
|
|
17
|
+
### Bug fixes
|
|
18
|
+
|
|
19
|
+
Don't override double/triple tap behavior on touch screen devices, so that the mobile selection menu pops up properly.
|
|
20
|
+
|
|
21
|
+
Fix an issue where updating the selection could crash on Safari when the editor was hidden.
|
|
22
|
+
|
|
1
23
|
## 6.2.4 (2022-09-16)
|
|
2
24
|
|
|
3
25
|
### Bug fixes
|
package/dist/index.cjs
CHANGED
|
@@ -100,7 +100,7 @@ function windowRect(win) {
|
|
|
100
100
|
top: 0, bottom: win.innerHeight };
|
|
101
101
|
}
|
|
102
102
|
function scrollRectIntoView(dom, rect, side, x, y, xMargin, yMargin, ltr) {
|
|
103
|
-
let doc = dom.ownerDocument, win = doc.defaultView;
|
|
103
|
+
let doc = dom.ownerDocument, win = doc.defaultView || window;
|
|
104
104
|
for (let cur = dom; cur;) {
|
|
105
105
|
if (cur.nodeType == 1) { // Element
|
|
106
106
|
let bounding, top = cur == doc.body;
|
|
@@ -350,7 +350,7 @@ class ContentView {
|
|
|
350
350
|
if (child.dirty) {
|
|
351
351
|
if (!child.dom && (next = prev ? prev.nextSibling : parent.firstChild)) {
|
|
352
352
|
let contentView = ContentView.get(next);
|
|
353
|
-
if (!contentView || !contentView.parent && contentView.
|
|
353
|
+
if (!contentView || !contentView.parent && contentView.canReuseDOM(child))
|
|
354
354
|
child.reuseDOM(next);
|
|
355
355
|
}
|
|
356
356
|
child.sync(track);
|
|
@@ -508,6 +508,7 @@ class ContentView {
|
|
|
508
508
|
return false;
|
|
509
509
|
}
|
|
510
510
|
become(other) { return false; }
|
|
511
|
+
canReuseDOM(other) { return other.constructor == this.constructor; }
|
|
511
512
|
// When this is a zero-length view with a side, this should return a
|
|
512
513
|
// number <= 0 to indicate it is before its position, or a
|
|
513
514
|
// number > 0 when after its position.
|
|
@@ -911,6 +912,7 @@ class CompositionView extends WidgetView {
|
|
|
911
912
|
(_a = this.widget.topView) === null || _a === void 0 ? void 0 : _a.destroy();
|
|
912
913
|
}
|
|
913
914
|
get isEditable() { return true; }
|
|
915
|
+
canReuseDOM() { return true; }
|
|
914
916
|
}
|
|
915
917
|
// Uses the old structure of a chunk of content view frozen for
|
|
916
918
|
// composition to try and find a reasonable DOM location for the given
|
|
@@ -2639,7 +2641,13 @@ class DocView extends ContentView {
|
|
|
2639
2641
|
// (one where the focus is before the anchor), but not all
|
|
2640
2642
|
// browsers support it yet.
|
|
2641
2643
|
rawSel.collapse(anchor.node, anchor.offset);
|
|
2642
|
-
|
|
2644
|
+
// Safari will ignore the call above when the editor is
|
|
2645
|
+
// hidden, and then raise an error on the call to extend
|
|
2646
|
+
// (#940).
|
|
2647
|
+
try {
|
|
2648
|
+
rawSel.extend(head.node, head.offset);
|
|
2649
|
+
}
|
|
2650
|
+
catch (_) { }
|
|
2643
2651
|
}
|
|
2644
2652
|
else {
|
|
2645
2653
|
// Primitive (IE) way
|
|
@@ -3692,7 +3700,7 @@ handlers.touchmove = view => {
|
|
|
3692
3700
|
handlerOptions.touchstart = handlerOptions.touchmove = { passive: true };
|
|
3693
3701
|
handlers.mousedown = (view, event) => {
|
|
3694
3702
|
view.observer.flush();
|
|
3695
|
-
if (view.inputState.lastTouchTime > Date.now() - 2000
|
|
3703
|
+
if (view.inputState.lastTouchTime > Date.now() - 2000)
|
|
3696
3704
|
return; // Ignore touch interaction
|
|
3697
3705
|
let style = null;
|
|
3698
3706
|
for (let makeStyle of view.state.facet(mouseSelectionStyle)) {
|
|
@@ -4663,7 +4671,7 @@ class DecorationComparator {
|
|
|
4663
4671
|
|
|
4664
4672
|
function visiblePixelRange(dom, paddingTop) {
|
|
4665
4673
|
let rect = dom.getBoundingClientRect();
|
|
4666
|
-
let doc = dom.ownerDocument, win = doc.defaultView;
|
|
4674
|
+
let doc = dom.ownerDocument, win = doc.defaultView || window;
|
|
4667
4675
|
let left = Math.max(0, rect.left), right = Math.min(win.innerWidth, rect.right);
|
|
4668
4676
|
let top = Math.max(0, rect.top), bottom = Math.min(win.innerHeight, rect.bottom);
|
|
4669
4677
|
for (let parent = dom.parentNode; parent && parent != doc.body;) {
|
|
@@ -4819,7 +4827,7 @@ class ViewState {
|
|
|
4819
4827
|
this.updateForViewport();
|
|
4820
4828
|
if (updateLines)
|
|
4821
4829
|
this.updateViewportLines();
|
|
4822
|
-
if (this.lineGaps.length || this.viewport.to - this.viewport.from >
|
|
4830
|
+
if (this.lineGaps.length || this.viewport.to - this.viewport.from > (2000 /* LG.Margin */ << 1))
|
|
4823
4831
|
this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
|
|
4824
4832
|
update.flags |= this.computeVisibleRanges();
|
|
4825
4833
|
if (scrollTarget)
|
|
@@ -4900,8 +4908,8 @@ class ViewState {
|
|
|
4900
4908
|
this.updateForViewport();
|
|
4901
4909
|
if ((result & 2 /* UpdateFlag.Height */) || viewportChange)
|
|
4902
4910
|
this.updateViewportLines();
|
|
4903
|
-
if (this.lineGaps.length || this.viewport.to - this.viewport.from >
|
|
4904
|
-
this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps));
|
|
4911
|
+
if (this.lineGaps.length || this.viewport.to - this.viewport.from > (2000 /* LG.Margin */ << 1))
|
|
4912
|
+
this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps, view));
|
|
4905
4913
|
result |= this.computeVisibleRanges();
|
|
4906
4914
|
if (this.mustEnforceCursorAssoc) {
|
|
4907
4915
|
this.mustEnforceCursorAssoc = false;
|
|
@@ -4972,46 +4980,82 @@ class ViewState {
|
|
|
4972
4980
|
// since actual DOM coordinates aren't always available and
|
|
4973
4981
|
// predictable. Relies on generous margins (see LG.Margin) to hide
|
|
4974
4982
|
// the artifacts this might produce from the user.
|
|
4975
|
-
ensureLineGaps(current) {
|
|
4983
|
+
ensureLineGaps(current, mayMeasure) {
|
|
4984
|
+
let wrapping = this.heightOracle.lineWrapping;
|
|
4985
|
+
let margin = wrapping ? 10000 /* LG.MarginWrap */ : 2000 /* LG.Margin */, halfMargin = margin >> 1, doubleMargin = margin << 1;
|
|
4986
|
+
// The non-wrapping logic won't work at all in predominantly right-to-left text.
|
|
4987
|
+
if (this.defaultTextDirection != exports.Direction.LTR && !wrapping)
|
|
4988
|
+
return [];
|
|
4976
4989
|
let gaps = [];
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4990
|
+
let addGap = (from, to, line, structure) => {
|
|
4991
|
+
if (to - from < halfMargin)
|
|
4992
|
+
return;
|
|
4993
|
+
let sel = this.state.selection.main, avoid = [sel.from];
|
|
4994
|
+
if (!sel.empty)
|
|
4995
|
+
avoid.push(sel.to);
|
|
4996
|
+
for (let pos of avoid) {
|
|
4997
|
+
if (pos > from && pos < to) {
|
|
4998
|
+
addGap(from, pos - 10 /* LG.SelectionMargin */, line, structure);
|
|
4999
|
+
addGap(pos + 10 /* LG.SelectionMargin */, to, line, structure);
|
|
5000
|
+
return;
|
|
5001
|
+
}
|
|
5002
|
+
}
|
|
5003
|
+
let gap = find(current, gap => gap.from >= line.from && gap.to <= line.to &&
|
|
5004
|
+
Math.abs(gap.from - from) < halfMargin && Math.abs(gap.to - to) < halfMargin &&
|
|
5005
|
+
!avoid.some(pos => gap.from < pos && gap.to > pos));
|
|
5006
|
+
if (!gap) {
|
|
5007
|
+
// When scrolling down, snap gap ends to line starts to avoid shifts in wrapping
|
|
5008
|
+
if (to < line.to && mayMeasure && wrapping &&
|
|
5009
|
+
mayMeasure.visibleRanges.some(r => r.from <= to && r.to >= to)) {
|
|
5010
|
+
let lineStart = mayMeasure.moveToLineBoundary(state.EditorSelection.cursor(to), false, true).head;
|
|
5011
|
+
if (lineStart > from)
|
|
5012
|
+
to = lineStart;
|
|
5013
|
+
}
|
|
5014
|
+
gap = new LineGap(from, to, this.gapSize(line, from, to, structure));
|
|
5015
|
+
}
|
|
5016
|
+
gaps.push(gap);
|
|
5017
|
+
};
|
|
4980
5018
|
for (let line of this.viewportLines) {
|
|
4981
|
-
if (line.length <
|
|
5019
|
+
if (line.length < doubleMargin)
|
|
4982
5020
|
continue;
|
|
4983
5021
|
let structure = lineStructure(line.from, line.to, this.stateDeco);
|
|
4984
|
-
if (structure.total <
|
|
5022
|
+
if (structure.total < doubleMargin)
|
|
4985
5023
|
continue;
|
|
5024
|
+
let target = this.scrollTarget ? this.scrollTarget.range.head : null;
|
|
4986
5025
|
let viewFrom, viewTo;
|
|
4987
|
-
if (
|
|
4988
|
-
let marginHeight = (
|
|
4989
|
-
|
|
4990
|
-
|
|
5026
|
+
if (wrapping) {
|
|
5027
|
+
let marginHeight = (margin / this.heightOracle.lineLength) * this.heightOracle.lineHeight;
|
|
5028
|
+
let top, bot;
|
|
5029
|
+
if (target != null) {
|
|
5030
|
+
top = Math.max(line.from, target - margin);
|
|
5031
|
+
bot = Math.min(line.to, target + margin);
|
|
5032
|
+
}
|
|
5033
|
+
else {
|
|
5034
|
+
top = (this.visibleTop - line.top - marginHeight) / line.height;
|
|
5035
|
+
bot = (this.visibleBottom - line.top + marginHeight) / line.height;
|
|
5036
|
+
}
|
|
5037
|
+
viewFrom = findPosition(structure, top);
|
|
5038
|
+
viewTo = findPosition(structure, bot);
|
|
4991
5039
|
}
|
|
4992
5040
|
else {
|
|
4993
|
-
let
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
5041
|
+
let left, right;
|
|
5042
|
+
if (target != null) {
|
|
5043
|
+
left = Math.max(line.from, target - doubleMargin);
|
|
5044
|
+
right = Math.min(line.to, target + doubleMargin);
|
|
5045
|
+
}
|
|
5046
|
+
else {
|
|
5047
|
+
let totalWidth = structure.total * this.heightOracle.charWidth;
|
|
5048
|
+
let marginWidth = margin * this.heightOracle.charWidth;
|
|
5049
|
+
left = (this.pixelViewport.left - marginWidth) / totalWidth;
|
|
5050
|
+
right = (this.pixelViewport.right + marginWidth) / totalWidth;
|
|
5051
|
+
}
|
|
5052
|
+
viewFrom = findPosition(structure, left);
|
|
5053
|
+
viewTo = findPosition(structure, right);
|
|
4997
5054
|
}
|
|
4998
|
-
let outside = [];
|
|
4999
5055
|
if (viewFrom > line.from)
|
|
5000
|
-
|
|
5056
|
+
addGap(line.from, viewFrom, line, structure);
|
|
5001
5057
|
if (viewTo < line.to)
|
|
5002
|
-
|
|
5003
|
-
let sel = this.state.selection.main;
|
|
5004
|
-
// Make sure the gaps don't cover a selection end
|
|
5005
|
-
if (sel.from >= line.from && sel.from <= line.to)
|
|
5006
|
-
cutRange(outside, sel.from - 10 /* LG.SelectionMargin */, sel.from + 10 /* LG.SelectionMargin */);
|
|
5007
|
-
if (!sel.empty && sel.to >= line.from && sel.to <= line.to)
|
|
5008
|
-
cutRange(outside, sel.to - 10 /* LG.SelectionMargin */, sel.to + 10 /* LG.SelectionMargin */);
|
|
5009
|
-
for (let { from, to } of outside)
|
|
5010
|
-
if (to - from > 1000 /* LG.HalfMargin */) {
|
|
5011
|
-
gaps.push(find(current, gap => gap.from >= line.from && gap.to <= line.to &&
|
|
5012
|
-
Math.abs(gap.from - from) < 1000 /* LG.HalfMargin */ && Math.abs(gap.to - to) < 1000 /* LG.HalfMargin */) ||
|
|
5013
|
-
new LineGap(from, to, this.gapSize(line, from, to, structure)));
|
|
5014
|
-
}
|
|
5058
|
+
addGap(viewTo, line.to, line, structure);
|
|
5015
5059
|
}
|
|
5016
5060
|
return gaps;
|
|
5017
5061
|
}
|
|
@@ -5109,20 +5153,6 @@ function findFraction(structure, pos) {
|
|
|
5109
5153
|
}
|
|
5110
5154
|
return counted / structure.total;
|
|
5111
5155
|
}
|
|
5112
|
-
function cutRange(ranges, from, to) {
|
|
5113
|
-
for (let i = 0; i < ranges.length; i++) {
|
|
5114
|
-
let r = ranges[i];
|
|
5115
|
-
if (r.from < to && r.to > from) {
|
|
5116
|
-
let pieces = [];
|
|
5117
|
-
if (r.from < from)
|
|
5118
|
-
pieces.push({ from: r.from, to: from });
|
|
5119
|
-
if (r.to > to)
|
|
5120
|
-
pieces.push({ from: to, to: r.to });
|
|
5121
|
-
ranges.splice(i, 1, ...pieces);
|
|
5122
|
-
i += pieces.length - 1;
|
|
5123
|
-
}
|
|
5124
|
-
}
|
|
5125
|
-
}
|
|
5126
5156
|
function find(array, f) {
|
|
5127
5157
|
for (let val of array)
|
|
5128
5158
|
if (f(val))
|
|
@@ -5894,24 +5924,29 @@ function applyDOMChange(view, start, end, typeOver) {
|
|
|
5894
5924
|
}
|
|
5895
5925
|
if (!change && !newSel)
|
|
5896
5926
|
return false;
|
|
5897
|
-
|
|
5898
|
-
|
|
5927
|
+
if (!change && typeOver && !sel.empty && newSel && newSel.main.empty) {
|
|
5928
|
+
// Heuristic to notice typing over a selected character
|
|
5899
5929
|
change = { from: sel.from, to: sel.to, insert: view.state.doc.slice(sel.from, sel.to) };
|
|
5900
|
-
|
|
5901
|
-
// assume it is a selection replace (with identical characters at
|
|
5902
|
-
// the start/end not included in the diff)
|
|
5930
|
+
}
|
|
5903
5931
|
else if (change && change.from >= sel.from && change.to <= sel.to &&
|
|
5904
5932
|
(change.from != sel.from || change.to != sel.to) &&
|
|
5905
|
-
(sel.to - sel.from) - (change.to - change.from) <= 4)
|
|
5933
|
+
(sel.to - sel.from) - (change.to - change.from) <= 4) {
|
|
5934
|
+
// If the change is inside the selection and covers most of it,
|
|
5935
|
+
// assume it is a selection replace (with identical characters at
|
|
5936
|
+
// the start/end not included in the diff)
|
|
5906
5937
|
change = {
|
|
5907
5938
|
from: sel.from, to: sel.to,
|
|
5908
5939
|
insert: view.state.doc.slice(sel.from, change.from).append(change.insert).append(view.state.doc.slice(change.to, sel.to))
|
|
5909
5940
|
};
|
|
5910
|
-
|
|
5911
|
-
// it into a regular space insert.
|
|
5941
|
+
}
|
|
5912
5942
|
else if ((browser.mac || browser.android) && change && change.from == change.to && change.from == sel.head - 1 &&
|
|
5913
|
-
change.insert.toString()
|
|
5943
|
+
/^\. ?$/.test(change.insert.toString())) {
|
|
5944
|
+
// Detect insert-period-on-double-space Mac and Android behavior,
|
|
5945
|
+
// and transform it into a regular space insert.
|
|
5946
|
+
if (newSel && change.insert.length == 2)
|
|
5947
|
+
newSel = state.EditorSelection.single(newSel.main.anchor - 1, newSel.main.head - 1);
|
|
5914
5948
|
change = { from: sel.from, to: sel.to, insert: state.Text.of([" "]) };
|
|
5949
|
+
}
|
|
5915
5950
|
if (change) {
|
|
5916
5951
|
let startState = view.state;
|
|
5917
5952
|
if (browser.ios && view.inputState.flushIOSKey(view))
|
|
@@ -6738,7 +6773,7 @@ class EditorView {
|
|
|
6738
6773
|
setRoot(root) {
|
|
6739
6774
|
if (this._root != root) {
|
|
6740
6775
|
this._root = root;
|
|
6741
|
-
this.observer.setWindow((root.nodeType == 9 ? root : root.ownerDocument).defaultView);
|
|
6776
|
+
this.observer.setWindow((root.nodeType == 9 ? root : root.ownerDocument).defaultView || window);
|
|
6742
6777
|
this.mountStyles();
|
|
6743
6778
|
}
|
|
6744
6779
|
}
|
|
@@ -7085,6 +7120,7 @@ function buildKeymap(bindings, platform = currentPlatform) {
|
|
|
7085
7120
|
throw new Error("Key binding " + name + " is used both as a regular binding and as a multi-stroke prefix");
|
|
7086
7121
|
};
|
|
7087
7122
|
let add = (scope, key, command, preventDefault) => {
|
|
7123
|
+
var _a, _b;
|
|
7088
7124
|
let scopeObj = bound[scope] || (bound[scope] = Object.create(null));
|
|
7089
7125
|
let parts = key.split(/ (?!$)/).map(k => normalizeKeyName(k, platform));
|
|
7090
7126
|
for (let i = 1; i < parts.length; i++) {
|
|
@@ -7093,7 +7129,7 @@ function buildKeymap(bindings, platform = currentPlatform) {
|
|
|
7093
7129
|
if (!scopeObj[prefix])
|
|
7094
7130
|
scopeObj[prefix] = {
|
|
7095
7131
|
preventDefault: true,
|
|
7096
|
-
|
|
7132
|
+
run: [(view) => {
|
|
7097
7133
|
let ourObj = storedPrefix = { view, prefix, scope };
|
|
7098
7134
|
setTimeout(() => { if (storedPrefix == ourObj)
|
|
7099
7135
|
storedPrefix = null; }, PrefixTimeout);
|
|
@@ -7103,16 +7139,26 @@ function buildKeymap(bindings, platform = currentPlatform) {
|
|
|
7103
7139
|
}
|
|
7104
7140
|
let full = parts.join(" ");
|
|
7105
7141
|
checkPrefix(full, false);
|
|
7106
|
-
let binding = scopeObj[full] || (scopeObj[full] = { preventDefault: false,
|
|
7107
|
-
|
|
7142
|
+
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()) || [] });
|
|
7143
|
+
if (command)
|
|
7144
|
+
binding.run.push(command);
|
|
7108
7145
|
if (preventDefault)
|
|
7109
7146
|
binding.preventDefault = true;
|
|
7110
7147
|
};
|
|
7111
7148
|
for (let b of bindings) {
|
|
7149
|
+
let scopes = b.scope ? b.scope.split(" ") : ["editor"];
|
|
7150
|
+
if (b.any)
|
|
7151
|
+
for (let scope of scopes) {
|
|
7152
|
+
let scopeObj = bound[scope] || (bound[scope] = Object.create(null));
|
|
7153
|
+
if (!scopeObj._any)
|
|
7154
|
+
scopeObj._any = { preventDefault: false, run: [] };
|
|
7155
|
+
for (let key in scopeObj)
|
|
7156
|
+
scopeObj[key].run.push(b.any);
|
|
7157
|
+
}
|
|
7112
7158
|
let name = b[platform] || b.key;
|
|
7113
7159
|
if (!name)
|
|
7114
7160
|
continue;
|
|
7115
|
-
for (let scope of
|
|
7161
|
+
for (let scope of scopes) {
|
|
7116
7162
|
add(scope, name, b.run, b.preventDefault);
|
|
7117
7163
|
if (b.shift)
|
|
7118
7164
|
add(scope, "Shift-" + name, b.shift, b.preventDefault);
|
|
@@ -7129,11 +7175,15 @@ function runHandlers(map, event, view, scope) {
|
|
|
7129
7175
|
if (fallthrough = modifierCodes.indexOf(event.keyCode) < 0)
|
|
7130
7176
|
storedPrefix = null;
|
|
7131
7177
|
}
|
|
7178
|
+
let ran = new Set;
|
|
7132
7179
|
let runFor = (binding) => {
|
|
7133
7180
|
if (binding) {
|
|
7134
|
-
for (let cmd of binding.
|
|
7135
|
-
if (cmd
|
|
7136
|
-
|
|
7181
|
+
for (let cmd of binding.run)
|
|
7182
|
+
if (!ran.has(cmd)) {
|
|
7183
|
+
ran.add(cmd);
|
|
7184
|
+
if (cmd(view, event))
|
|
7185
|
+
return true;
|
|
7186
|
+
}
|
|
7137
7187
|
if (binding.preventDefault)
|
|
7138
7188
|
fallthrough = true;
|
|
7139
7189
|
}
|
|
@@ -7155,6 +7205,8 @@ function runHandlers(map, event, view, scope) {
|
|
|
7155
7205
|
if (runFor(scopeObj[prefix + modifiers(name, event, true)]))
|
|
7156
7206
|
return true;
|
|
7157
7207
|
}
|
|
7208
|
+
if (runFor(scopeObj._any))
|
|
7209
|
+
return true;
|
|
7158
7210
|
}
|
|
7159
7211
|
return fallthrough;
|
|
7160
7212
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1219,7 +1219,7 @@ interface KeyBinding {
|
|
|
1219
1219
|
command function returns `false`, further bindings will be tried
|
|
1220
1220
|
for the key.
|
|
1221
1221
|
*/
|
|
1222
|
-
run
|
|
1222
|
+
run?: Command;
|
|
1223
1223
|
/**
|
|
1224
1224
|
When given, this defines a second binding, using the (possibly
|
|
1225
1225
|
platform-specific) key name prefixed with `Shift-` to activate
|
|
@@ -1227,6 +1227,11 @@ interface KeyBinding {
|
|
|
1227
1227
|
*/
|
|
1228
1228
|
shift?: Command;
|
|
1229
1229
|
/**
|
|
1230
|
+
When this property is present, the function is called for every
|
|
1231
|
+
key that is not a multi-stroke prefix.
|
|
1232
|
+
*/
|
|
1233
|
+
any?: (view: EditorView, event: KeyboardEvent) => boolean;
|
|
1234
|
+
/**
|
|
1230
1235
|
By default, key bindings apply when focus is on the editor
|
|
1231
1236
|
content (the `"editor"` scope). Some extensions, mostly those
|
|
1232
1237
|
that define their own panels, might want to allow you to
|
package/dist/index.js
CHANGED
|
@@ -96,7 +96,7 @@ function windowRect(win) {
|
|
|
96
96
|
top: 0, bottom: win.innerHeight };
|
|
97
97
|
}
|
|
98
98
|
function scrollRectIntoView(dom, rect, side, x, y, xMargin, yMargin, ltr) {
|
|
99
|
-
let doc = dom.ownerDocument, win = doc.defaultView;
|
|
99
|
+
let doc = dom.ownerDocument, win = doc.defaultView || window;
|
|
100
100
|
for (let cur = dom; cur;) {
|
|
101
101
|
if (cur.nodeType == 1) { // Element
|
|
102
102
|
let bounding, top = cur == doc.body;
|
|
@@ -346,7 +346,7 @@ class ContentView {
|
|
|
346
346
|
if (child.dirty) {
|
|
347
347
|
if (!child.dom && (next = prev ? prev.nextSibling : parent.firstChild)) {
|
|
348
348
|
let contentView = ContentView.get(next);
|
|
349
|
-
if (!contentView || !contentView.parent && contentView.
|
|
349
|
+
if (!contentView || !contentView.parent && contentView.canReuseDOM(child))
|
|
350
350
|
child.reuseDOM(next);
|
|
351
351
|
}
|
|
352
352
|
child.sync(track);
|
|
@@ -504,6 +504,7 @@ class ContentView {
|
|
|
504
504
|
return false;
|
|
505
505
|
}
|
|
506
506
|
become(other) { return false; }
|
|
507
|
+
canReuseDOM(other) { return other.constructor == this.constructor; }
|
|
507
508
|
// When this is a zero-length view with a side, this should return a
|
|
508
509
|
// number <= 0 to indicate it is before its position, or a
|
|
509
510
|
// number > 0 when after its position.
|
|
@@ -907,6 +908,7 @@ class CompositionView extends WidgetView {
|
|
|
907
908
|
(_a = this.widget.topView) === null || _a === void 0 ? void 0 : _a.destroy();
|
|
908
909
|
}
|
|
909
910
|
get isEditable() { return true; }
|
|
911
|
+
canReuseDOM() { return true; }
|
|
910
912
|
}
|
|
911
913
|
// Uses the old structure of a chunk of content view frozen for
|
|
912
914
|
// composition to try and find a reasonable DOM location for the given
|
|
@@ -2633,7 +2635,13 @@ class DocView extends ContentView {
|
|
|
2633
2635
|
// (one where the focus is before the anchor), but not all
|
|
2634
2636
|
// browsers support it yet.
|
|
2635
2637
|
rawSel.collapse(anchor.node, anchor.offset);
|
|
2636
|
-
|
|
2638
|
+
// Safari will ignore the call above when the editor is
|
|
2639
|
+
// hidden, and then raise an error on the call to extend
|
|
2640
|
+
// (#940).
|
|
2641
|
+
try {
|
|
2642
|
+
rawSel.extend(head.node, head.offset);
|
|
2643
|
+
}
|
|
2644
|
+
catch (_) { }
|
|
2637
2645
|
}
|
|
2638
2646
|
else {
|
|
2639
2647
|
// Primitive (IE) way
|
|
@@ -3686,7 +3694,7 @@ handlers.touchmove = view => {
|
|
|
3686
3694
|
handlerOptions.touchstart = handlerOptions.touchmove = { passive: true };
|
|
3687
3695
|
handlers.mousedown = (view, event) => {
|
|
3688
3696
|
view.observer.flush();
|
|
3689
|
-
if (view.inputState.lastTouchTime > Date.now() - 2000
|
|
3697
|
+
if (view.inputState.lastTouchTime > Date.now() - 2000)
|
|
3690
3698
|
return; // Ignore touch interaction
|
|
3691
3699
|
let style = null;
|
|
3692
3700
|
for (let makeStyle of view.state.facet(mouseSelectionStyle)) {
|
|
@@ -4656,7 +4664,7 @@ class DecorationComparator {
|
|
|
4656
4664
|
|
|
4657
4665
|
function visiblePixelRange(dom, paddingTop) {
|
|
4658
4666
|
let rect = dom.getBoundingClientRect();
|
|
4659
|
-
let doc = dom.ownerDocument, win = doc.defaultView;
|
|
4667
|
+
let doc = dom.ownerDocument, win = doc.defaultView || window;
|
|
4660
4668
|
let left = Math.max(0, rect.left), right = Math.min(win.innerWidth, rect.right);
|
|
4661
4669
|
let top = Math.max(0, rect.top), bottom = Math.min(win.innerHeight, rect.bottom);
|
|
4662
4670
|
for (let parent = dom.parentNode; parent && parent != doc.body;) {
|
|
@@ -4812,7 +4820,7 @@ class ViewState {
|
|
|
4812
4820
|
this.updateForViewport();
|
|
4813
4821
|
if (updateLines)
|
|
4814
4822
|
this.updateViewportLines();
|
|
4815
|
-
if (this.lineGaps.length || this.viewport.to - this.viewport.from >
|
|
4823
|
+
if (this.lineGaps.length || this.viewport.to - this.viewport.from > (2000 /* LG.Margin */ << 1))
|
|
4816
4824
|
this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
|
|
4817
4825
|
update.flags |= this.computeVisibleRanges();
|
|
4818
4826
|
if (scrollTarget)
|
|
@@ -4893,8 +4901,8 @@ class ViewState {
|
|
|
4893
4901
|
this.updateForViewport();
|
|
4894
4902
|
if ((result & 2 /* UpdateFlag.Height */) || viewportChange)
|
|
4895
4903
|
this.updateViewportLines();
|
|
4896
|
-
if (this.lineGaps.length || this.viewport.to - this.viewport.from >
|
|
4897
|
-
this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps));
|
|
4904
|
+
if (this.lineGaps.length || this.viewport.to - this.viewport.from > (2000 /* LG.Margin */ << 1))
|
|
4905
|
+
this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps, view));
|
|
4898
4906
|
result |= this.computeVisibleRanges();
|
|
4899
4907
|
if (this.mustEnforceCursorAssoc) {
|
|
4900
4908
|
this.mustEnforceCursorAssoc = false;
|
|
@@ -4965,46 +4973,82 @@ class ViewState {
|
|
|
4965
4973
|
// since actual DOM coordinates aren't always available and
|
|
4966
4974
|
// predictable. Relies on generous margins (see LG.Margin) to hide
|
|
4967
4975
|
// the artifacts this might produce from the user.
|
|
4968
|
-
ensureLineGaps(current) {
|
|
4976
|
+
ensureLineGaps(current, mayMeasure) {
|
|
4977
|
+
let wrapping = this.heightOracle.lineWrapping;
|
|
4978
|
+
let margin = wrapping ? 10000 /* LG.MarginWrap */ : 2000 /* LG.Margin */, halfMargin = margin >> 1, doubleMargin = margin << 1;
|
|
4979
|
+
// The non-wrapping logic won't work at all in predominantly right-to-left text.
|
|
4980
|
+
if (this.defaultTextDirection != Direction.LTR && !wrapping)
|
|
4981
|
+
return [];
|
|
4969
4982
|
let gaps = [];
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4983
|
+
let addGap = (from, to, line, structure) => {
|
|
4984
|
+
if (to - from < halfMargin)
|
|
4985
|
+
return;
|
|
4986
|
+
let sel = this.state.selection.main, avoid = [sel.from];
|
|
4987
|
+
if (!sel.empty)
|
|
4988
|
+
avoid.push(sel.to);
|
|
4989
|
+
for (let pos of avoid) {
|
|
4990
|
+
if (pos > from && pos < to) {
|
|
4991
|
+
addGap(from, pos - 10 /* LG.SelectionMargin */, line, structure);
|
|
4992
|
+
addGap(pos + 10 /* LG.SelectionMargin */, to, line, structure);
|
|
4993
|
+
return;
|
|
4994
|
+
}
|
|
4995
|
+
}
|
|
4996
|
+
let gap = find(current, gap => gap.from >= line.from && gap.to <= line.to &&
|
|
4997
|
+
Math.abs(gap.from - from) < halfMargin && Math.abs(gap.to - to) < halfMargin &&
|
|
4998
|
+
!avoid.some(pos => gap.from < pos && gap.to > pos));
|
|
4999
|
+
if (!gap) {
|
|
5000
|
+
// When scrolling down, snap gap ends to line starts to avoid shifts in wrapping
|
|
5001
|
+
if (to < line.to && mayMeasure && wrapping &&
|
|
5002
|
+
mayMeasure.visibleRanges.some(r => r.from <= to && r.to >= to)) {
|
|
5003
|
+
let lineStart = mayMeasure.moveToLineBoundary(EditorSelection.cursor(to), false, true).head;
|
|
5004
|
+
if (lineStart > from)
|
|
5005
|
+
to = lineStart;
|
|
5006
|
+
}
|
|
5007
|
+
gap = new LineGap(from, to, this.gapSize(line, from, to, structure));
|
|
5008
|
+
}
|
|
5009
|
+
gaps.push(gap);
|
|
5010
|
+
};
|
|
4973
5011
|
for (let line of this.viewportLines) {
|
|
4974
|
-
if (line.length <
|
|
5012
|
+
if (line.length < doubleMargin)
|
|
4975
5013
|
continue;
|
|
4976
5014
|
let structure = lineStructure(line.from, line.to, this.stateDeco);
|
|
4977
|
-
if (structure.total <
|
|
5015
|
+
if (structure.total < doubleMargin)
|
|
4978
5016
|
continue;
|
|
5017
|
+
let target = this.scrollTarget ? this.scrollTarget.range.head : null;
|
|
4979
5018
|
let viewFrom, viewTo;
|
|
4980
|
-
if (
|
|
4981
|
-
let marginHeight = (
|
|
4982
|
-
|
|
4983
|
-
|
|
5019
|
+
if (wrapping) {
|
|
5020
|
+
let marginHeight = (margin / this.heightOracle.lineLength) * this.heightOracle.lineHeight;
|
|
5021
|
+
let top, bot;
|
|
5022
|
+
if (target != null) {
|
|
5023
|
+
top = Math.max(line.from, target - margin);
|
|
5024
|
+
bot = Math.min(line.to, target + margin);
|
|
5025
|
+
}
|
|
5026
|
+
else {
|
|
5027
|
+
top = (this.visibleTop - line.top - marginHeight) / line.height;
|
|
5028
|
+
bot = (this.visibleBottom - line.top + marginHeight) / line.height;
|
|
5029
|
+
}
|
|
5030
|
+
viewFrom = findPosition(structure, top);
|
|
5031
|
+
viewTo = findPosition(structure, bot);
|
|
4984
5032
|
}
|
|
4985
5033
|
else {
|
|
4986
|
-
let
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
5034
|
+
let left, right;
|
|
5035
|
+
if (target != null) {
|
|
5036
|
+
left = Math.max(line.from, target - doubleMargin);
|
|
5037
|
+
right = Math.min(line.to, target + doubleMargin);
|
|
5038
|
+
}
|
|
5039
|
+
else {
|
|
5040
|
+
let totalWidth = structure.total * this.heightOracle.charWidth;
|
|
5041
|
+
let marginWidth = margin * this.heightOracle.charWidth;
|
|
5042
|
+
left = (this.pixelViewport.left - marginWidth) / totalWidth;
|
|
5043
|
+
right = (this.pixelViewport.right + marginWidth) / totalWidth;
|
|
5044
|
+
}
|
|
5045
|
+
viewFrom = findPosition(structure, left);
|
|
5046
|
+
viewTo = findPosition(structure, right);
|
|
4990
5047
|
}
|
|
4991
|
-
let outside = [];
|
|
4992
5048
|
if (viewFrom > line.from)
|
|
4993
|
-
|
|
5049
|
+
addGap(line.from, viewFrom, line, structure);
|
|
4994
5050
|
if (viewTo < line.to)
|
|
4995
|
-
|
|
4996
|
-
let sel = this.state.selection.main;
|
|
4997
|
-
// Make sure the gaps don't cover a selection end
|
|
4998
|
-
if (sel.from >= line.from && sel.from <= line.to)
|
|
4999
|
-
cutRange(outside, sel.from - 10 /* LG.SelectionMargin */, sel.from + 10 /* LG.SelectionMargin */);
|
|
5000
|
-
if (!sel.empty && sel.to >= line.from && sel.to <= line.to)
|
|
5001
|
-
cutRange(outside, sel.to - 10 /* LG.SelectionMargin */, sel.to + 10 /* LG.SelectionMargin */);
|
|
5002
|
-
for (let { from, to } of outside)
|
|
5003
|
-
if (to - from > 1000 /* LG.HalfMargin */) {
|
|
5004
|
-
gaps.push(find(current, gap => gap.from >= line.from && gap.to <= line.to &&
|
|
5005
|
-
Math.abs(gap.from - from) < 1000 /* LG.HalfMargin */ && Math.abs(gap.to - to) < 1000 /* LG.HalfMargin */) ||
|
|
5006
|
-
new LineGap(from, to, this.gapSize(line, from, to, structure)));
|
|
5007
|
-
}
|
|
5051
|
+
addGap(viewTo, line.to, line, structure);
|
|
5008
5052
|
}
|
|
5009
5053
|
return gaps;
|
|
5010
5054
|
}
|
|
@@ -5102,20 +5146,6 @@ function findFraction(structure, pos) {
|
|
|
5102
5146
|
}
|
|
5103
5147
|
return counted / structure.total;
|
|
5104
5148
|
}
|
|
5105
|
-
function cutRange(ranges, from, to) {
|
|
5106
|
-
for (let i = 0; i < ranges.length; i++) {
|
|
5107
|
-
let r = ranges[i];
|
|
5108
|
-
if (r.from < to && r.to > from) {
|
|
5109
|
-
let pieces = [];
|
|
5110
|
-
if (r.from < from)
|
|
5111
|
-
pieces.push({ from: r.from, to: from });
|
|
5112
|
-
if (r.to > to)
|
|
5113
|
-
pieces.push({ from: to, to: r.to });
|
|
5114
|
-
ranges.splice(i, 1, ...pieces);
|
|
5115
|
-
i += pieces.length - 1;
|
|
5116
|
-
}
|
|
5117
|
-
}
|
|
5118
|
-
}
|
|
5119
5149
|
function find(array, f) {
|
|
5120
5150
|
for (let val of array)
|
|
5121
5151
|
if (f(val))
|
|
@@ -5887,24 +5917,29 @@ function applyDOMChange(view, start, end, typeOver) {
|
|
|
5887
5917
|
}
|
|
5888
5918
|
if (!change && !newSel)
|
|
5889
5919
|
return false;
|
|
5890
|
-
|
|
5891
|
-
|
|
5920
|
+
if (!change && typeOver && !sel.empty && newSel && newSel.main.empty) {
|
|
5921
|
+
// Heuristic to notice typing over a selected character
|
|
5892
5922
|
change = { from: sel.from, to: sel.to, insert: view.state.doc.slice(sel.from, sel.to) };
|
|
5893
|
-
|
|
5894
|
-
// assume it is a selection replace (with identical characters at
|
|
5895
|
-
// the start/end not included in the diff)
|
|
5923
|
+
}
|
|
5896
5924
|
else if (change && change.from >= sel.from && change.to <= sel.to &&
|
|
5897
5925
|
(change.from != sel.from || change.to != sel.to) &&
|
|
5898
|
-
(sel.to - sel.from) - (change.to - change.from) <= 4)
|
|
5926
|
+
(sel.to - sel.from) - (change.to - change.from) <= 4) {
|
|
5927
|
+
// If the change is inside the selection and covers most of it,
|
|
5928
|
+
// assume it is a selection replace (with identical characters at
|
|
5929
|
+
// the start/end not included in the diff)
|
|
5899
5930
|
change = {
|
|
5900
5931
|
from: sel.from, to: sel.to,
|
|
5901
5932
|
insert: view.state.doc.slice(sel.from, change.from).append(change.insert).append(view.state.doc.slice(change.to, sel.to))
|
|
5902
5933
|
};
|
|
5903
|
-
|
|
5904
|
-
// it into a regular space insert.
|
|
5934
|
+
}
|
|
5905
5935
|
else if ((browser.mac || browser.android) && change && change.from == change.to && change.from == sel.head - 1 &&
|
|
5906
|
-
change.insert.toString()
|
|
5936
|
+
/^\. ?$/.test(change.insert.toString())) {
|
|
5937
|
+
// Detect insert-period-on-double-space Mac and Android behavior,
|
|
5938
|
+
// and transform it into a regular space insert.
|
|
5939
|
+
if (newSel && change.insert.length == 2)
|
|
5940
|
+
newSel = EditorSelection.single(newSel.main.anchor - 1, newSel.main.head - 1);
|
|
5907
5941
|
change = { from: sel.from, to: sel.to, insert: Text.of([" "]) };
|
|
5942
|
+
}
|
|
5908
5943
|
if (change) {
|
|
5909
5944
|
let startState = view.state;
|
|
5910
5945
|
if (browser.ios && view.inputState.flushIOSKey(view))
|
|
@@ -6731,7 +6766,7 @@ class EditorView {
|
|
|
6731
6766
|
setRoot(root) {
|
|
6732
6767
|
if (this._root != root) {
|
|
6733
6768
|
this._root = root;
|
|
6734
|
-
this.observer.setWindow((root.nodeType == 9 ? root : root.ownerDocument).defaultView);
|
|
6769
|
+
this.observer.setWindow((root.nodeType == 9 ? root : root.ownerDocument).defaultView || window);
|
|
6735
6770
|
this.mountStyles();
|
|
6736
6771
|
}
|
|
6737
6772
|
}
|
|
@@ -7078,6 +7113,7 @@ function buildKeymap(bindings, platform = currentPlatform) {
|
|
|
7078
7113
|
throw new Error("Key binding " + name + " is used both as a regular binding and as a multi-stroke prefix");
|
|
7079
7114
|
};
|
|
7080
7115
|
let add = (scope, key, command, preventDefault) => {
|
|
7116
|
+
var _a, _b;
|
|
7081
7117
|
let scopeObj = bound[scope] || (bound[scope] = Object.create(null));
|
|
7082
7118
|
let parts = key.split(/ (?!$)/).map(k => normalizeKeyName(k, platform));
|
|
7083
7119
|
for (let i = 1; i < parts.length; i++) {
|
|
@@ -7086,7 +7122,7 @@ function buildKeymap(bindings, platform = currentPlatform) {
|
|
|
7086
7122
|
if (!scopeObj[prefix])
|
|
7087
7123
|
scopeObj[prefix] = {
|
|
7088
7124
|
preventDefault: true,
|
|
7089
|
-
|
|
7125
|
+
run: [(view) => {
|
|
7090
7126
|
let ourObj = storedPrefix = { view, prefix, scope };
|
|
7091
7127
|
setTimeout(() => { if (storedPrefix == ourObj)
|
|
7092
7128
|
storedPrefix = null; }, PrefixTimeout);
|
|
@@ -7096,16 +7132,26 @@ function buildKeymap(bindings, platform = currentPlatform) {
|
|
|
7096
7132
|
}
|
|
7097
7133
|
let full = parts.join(" ");
|
|
7098
7134
|
checkPrefix(full, false);
|
|
7099
|
-
let binding = scopeObj[full] || (scopeObj[full] = { preventDefault: false,
|
|
7100
|
-
|
|
7135
|
+
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()) || [] });
|
|
7136
|
+
if (command)
|
|
7137
|
+
binding.run.push(command);
|
|
7101
7138
|
if (preventDefault)
|
|
7102
7139
|
binding.preventDefault = true;
|
|
7103
7140
|
};
|
|
7104
7141
|
for (let b of bindings) {
|
|
7142
|
+
let scopes = b.scope ? b.scope.split(" ") : ["editor"];
|
|
7143
|
+
if (b.any)
|
|
7144
|
+
for (let scope of scopes) {
|
|
7145
|
+
let scopeObj = bound[scope] || (bound[scope] = Object.create(null));
|
|
7146
|
+
if (!scopeObj._any)
|
|
7147
|
+
scopeObj._any = { preventDefault: false, run: [] };
|
|
7148
|
+
for (let key in scopeObj)
|
|
7149
|
+
scopeObj[key].run.push(b.any);
|
|
7150
|
+
}
|
|
7105
7151
|
let name = b[platform] || b.key;
|
|
7106
7152
|
if (!name)
|
|
7107
7153
|
continue;
|
|
7108
|
-
for (let scope of
|
|
7154
|
+
for (let scope of scopes) {
|
|
7109
7155
|
add(scope, name, b.run, b.preventDefault);
|
|
7110
7156
|
if (b.shift)
|
|
7111
7157
|
add(scope, "Shift-" + name, b.shift, b.preventDefault);
|
|
@@ -7122,11 +7168,15 @@ function runHandlers(map, event, view, scope) {
|
|
|
7122
7168
|
if (fallthrough = modifierCodes.indexOf(event.keyCode) < 0)
|
|
7123
7169
|
storedPrefix = null;
|
|
7124
7170
|
}
|
|
7171
|
+
let ran = new Set;
|
|
7125
7172
|
let runFor = (binding) => {
|
|
7126
7173
|
if (binding) {
|
|
7127
|
-
for (let cmd of binding.
|
|
7128
|
-
if (cmd
|
|
7129
|
-
|
|
7174
|
+
for (let cmd of binding.run)
|
|
7175
|
+
if (!ran.has(cmd)) {
|
|
7176
|
+
ran.add(cmd);
|
|
7177
|
+
if (cmd(view, event))
|
|
7178
|
+
return true;
|
|
7179
|
+
}
|
|
7130
7180
|
if (binding.preventDefault)
|
|
7131
7181
|
fallthrough = true;
|
|
7132
7182
|
}
|
|
@@ -7148,6 +7198,8 @@ function runHandlers(map, event, view, scope) {
|
|
|
7148
7198
|
if (runFor(scopeObj[prefix + modifiers(name, event, true)]))
|
|
7149
7199
|
return true;
|
|
7150
7200
|
}
|
|
7201
|
+
if (runFor(scopeObj._any))
|
|
7202
|
+
return true;
|
|
7151
7203
|
}
|
|
7152
7204
|
return fallthrough;
|
|
7153
7205
|
}
|