@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 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.constructor == child.constructor)
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
- rawSel.extend(head.node, head.offset);
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 && getClickType(event) == 1)
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 > 4000 /* LG.DoubleMargin */)
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 > 4000 /* LG.DoubleMargin */)
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
- // This won't work at all in predominantly right-to-left text.
4978
- if (this.defaultTextDirection != exports.Direction.LTR)
4979
- return gaps;
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 < 4000 /* LG.DoubleMargin */)
5019
+ if (line.length < doubleMargin)
4982
5020
  continue;
4983
5021
  let structure = lineStructure(line.from, line.to, this.stateDeco);
4984
- if (structure.total < 4000 /* LG.DoubleMargin */)
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 (this.heightOracle.lineWrapping) {
4988
- let marginHeight = (2000 /* LG.Margin */ / this.heightOracle.lineLength) * this.heightOracle.lineHeight;
4989
- viewFrom = findPosition(structure, (this.visibleTop - line.top - marginHeight) / line.height);
4990
- viewTo = findPosition(structure, (this.visibleBottom - line.top + marginHeight) / line.height);
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 totalWidth = structure.total * this.heightOracle.charWidth;
4994
- let marginWidth = 2000 /* LG.Margin */ * this.heightOracle.charWidth;
4995
- viewFrom = findPosition(structure, (this.pixelViewport.left - marginWidth) / totalWidth);
4996
- viewTo = findPosition(structure, (this.pixelViewport.right + marginWidth) / totalWidth);
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
- outside.push({ from: line.from, to: viewFrom });
5056
+ addGap(line.from, viewFrom, line, structure);
5001
5057
  if (viewTo < line.to)
5002
- outside.push({ from: viewTo, to: line.to });
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
- // Heuristic to notice typing over a selected character
5898
- if (!change && typeOver && !sel.empty && newSel && newSel.main.empty)
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
- // If the change is inside the selection and covers most of it,
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
- // Detect insert-period-on-double-space Mac behavior, and transform
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
- commands: [(view) => {
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, commands: [] });
7107
- binding.commands.push(command);
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 b.scope ? b.scope.split(" ") : ["editor"]) {
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.commands)
7135
- if (cmd(view))
7136
- return true;
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: Command;
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.constructor == child.constructor)
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
- rawSel.extend(head.node, head.offset);
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 && getClickType(event) == 1)
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 > 4000 /* LG.DoubleMargin */)
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 > 4000 /* LG.DoubleMargin */)
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
- // This won't work at all in predominantly right-to-left text.
4971
- if (this.defaultTextDirection != Direction.LTR)
4972
- return gaps;
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 < 4000 /* LG.DoubleMargin */)
5012
+ if (line.length < doubleMargin)
4975
5013
  continue;
4976
5014
  let structure = lineStructure(line.from, line.to, this.stateDeco);
4977
- if (structure.total < 4000 /* LG.DoubleMargin */)
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 (this.heightOracle.lineWrapping) {
4981
- let marginHeight = (2000 /* LG.Margin */ / this.heightOracle.lineLength) * this.heightOracle.lineHeight;
4982
- viewFrom = findPosition(structure, (this.visibleTop - line.top - marginHeight) / line.height);
4983
- viewTo = findPosition(structure, (this.visibleBottom - line.top + marginHeight) / line.height);
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 totalWidth = structure.total * this.heightOracle.charWidth;
4987
- let marginWidth = 2000 /* LG.Margin */ * this.heightOracle.charWidth;
4988
- viewFrom = findPosition(structure, (this.pixelViewport.left - marginWidth) / totalWidth);
4989
- viewTo = findPosition(structure, (this.pixelViewport.right + marginWidth) / totalWidth);
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
- outside.push({ from: line.from, to: viewFrom });
5049
+ addGap(line.from, viewFrom, line, structure);
4994
5050
  if (viewTo < line.to)
4995
- outside.push({ from: viewTo, to: line.to });
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
- // Heuristic to notice typing over a selected character
5891
- if (!change && typeOver && !sel.empty && newSel && newSel.main.empty)
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
- // If the change is inside the selection and covers most of it,
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
- // Detect insert-period-on-double-space Mac behavior, and transform
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
- commands: [(view) => {
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, commands: [] });
7100
- binding.commands.push(command);
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 b.scope ? b.scope.split(" ") : ["editor"]) {
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.commands)
7128
- if (cmd(view))
7129
- return true;
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "6.2.4",
3
+ "version": "6.3.0",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",