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