@codemirror/view 6.3.0 → 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.cjs CHANGED
@@ -875,7 +875,7 @@ class WidgetView extends ContentView {
875
875
  if (pos > 0 ? i == 0 : i == rects.length - 1 || rect.top < rect.bottom)
876
876
  break;
877
877
  }
878
- return (pos == 0 && side > 0 || pos == this.length && side <= 0) ? rect : flattenRect(rect, pos == 0);
878
+ return flattenRect(rect, this.side > 0);
879
879
  }
880
880
  get isEditable() { return false; }
881
881
  destroy() {
@@ -1028,7 +1028,6 @@ function inlineDOMAtPos(parent, pos) {
1028
1028
  break;
1029
1029
  off = end;
1030
1030
  }
1031
- // if (i) return DOMPos.after(children[i - 1].dom!)
1032
1031
  for (let j = i; j > 0; j--) {
1033
1032
  let prev = children[j - 1];
1034
1033
  if (prev.dom.parentNode == dom)
@@ -1055,21 +1054,59 @@ function joinInlineInto(parent, view, open) {
1055
1054
  parent.length += view.length;
1056
1055
  }
1057
1056
  function coordsInChildren(view, pos, side) {
1058
- for (let off = 0, i = 0; i < view.children.length; i++) {
1059
- let child = view.children[i], end = off + child.length, next;
1060
- if ((side <= 0 || end == view.length || child.getSide() > 0 ? end >= pos : end > pos) &&
1061
- (pos < end || i + 1 == view.children.length || (next = view.children[i + 1]).length || next.getSide() > 0)) {
1062
- let flatten = 0;
1063
- if (end == off) {
1064
- if (child.getSide() <= 0)
1065
- continue;
1066
- flatten = side = -child.getSide();
1057
+ if (!view.children.length)
1058
+ return fallbackRect(view);
1059
+ return (side <= 0 ? coordsInChildrenBefore : coordsInChildrenAfter)(view, pos);
1060
+ }
1061
+ function coordsInChildrenBefore(view, pos) {
1062
+ // Find the last leaf in the tree that touches pos and doesn't have getSide() > 0
1063
+ let found = null, foundPos = -1;
1064
+ function scan(view, pos) {
1065
+ for (let i = 0, off = 0; i < view.children.length && off <= pos; i++) {
1066
+ let child = view.children[i], end = off + child.length;
1067
+ if (end >= pos) {
1068
+ if (child.children.length) {
1069
+ if (scan(child, pos - off))
1070
+ return true;
1071
+ }
1072
+ else if (end >= pos) {
1073
+ if (end == pos && child.getSide() > 0)
1074
+ return true;
1075
+ found = child;
1076
+ foundPos = pos - off;
1077
+ }
1078
+ }
1079
+ off = end;
1080
+ }
1081
+ }
1082
+ scan(view, pos);
1083
+ return found ? found.coordsAt(Math.max(0, foundPos), -1) : coordsInChildrenAfter(view, pos);
1084
+ }
1085
+ function coordsInChildrenAfter(view, pos) {
1086
+ // Find the first leaf in the tree that touches pos and doesn't have getSide() < 0
1087
+ let found = null, foundPos = -1;
1088
+ function scan(view, pos) {
1089
+ for (let i = view.children.length - 1, off = view.length; i >= 0 && off >= pos; i--) {
1090
+ let child = view.children[i];
1091
+ off -= child.length;
1092
+ if (off <= pos) {
1093
+ if (child.children.length) {
1094
+ if (scan(child, pos - off))
1095
+ return true;
1096
+ }
1097
+ else if (off <= pos) {
1098
+ if (off == pos && child.getSide() < 0)
1099
+ return true;
1100
+ found = child;
1101
+ foundPos = pos - off;
1102
+ }
1067
1103
  }
1068
- let rect = child.coordsAt(Math.max(0, pos - off), side);
1069
- return flatten && rect ? flattenRect(rect, side < 0) : rect;
1070
1104
  }
1071
- off = end;
1072
1105
  }
1106
+ scan(view, pos);
1107
+ return found ? found.coordsAt(Math.max(0, foundPos), 1) : coordsInChildrenBefore(view, pos);
1108
+ }
1109
+ function fallbackRect(view) {
1073
1110
  let last = view.dom.lastChild;
1074
1111
  if (!last)
1075
1112
  return view.dom.getBoundingClientRect();
@@ -1688,7 +1725,7 @@ class ContentBuilder {
1688
1725
  this.addBlockWidget(new BlockWidgetView(deco.widget || new NullWidget("div"), len, type));
1689
1726
  }
1690
1727
  else {
1691
- let view = WidgetView.create(deco.widget || new NullWidget("span"), len, deco.startSide);
1728
+ let view = WidgetView.create(deco.widget || new NullWidget("span"), len, len ? 0 : deco.startSide);
1692
1729
  let cursorBefore = this.atCursorPos && !view.isEditable && openStart <= active.length && (from < to || deco.startSide > 0);
1693
1730
  let cursorAfter = !view.isEditable && (from < to || deco.startSide <= 0);
1694
1731
  let line = this.getLine();
@@ -4883,7 +4920,7 @@ class ViewState {
4883
4920
  refresh = true;
4884
4921
  if (refresh || oracle.lineWrapping && Math.abs(contentWidth - this.contentDOMWidth) > oracle.charWidth) {
4885
4922
  let { lineHeight, charWidth } = view.docView.measureTextSize();
4886
- refresh = oracle.refresh(whiteSpace, lineHeight, charWidth, contentWidth / charWidth, lineHeights);
4923
+ refresh = lineHeight > 0 && oracle.refresh(whiteSpace, lineHeight, charWidth, contentWidth / charWidth, lineHeights);
4887
4924
  if (refresh) {
4888
4925
  view.docView.minWidth = 0;
4889
4926
  result |= 8 /* UpdateFlag.Geometry */;
@@ -5027,8 +5064,10 @@ class ViewState {
5027
5064
  let marginHeight = (margin / this.heightOracle.lineLength) * this.heightOracle.lineHeight;
5028
5065
  let top, bot;
5029
5066
  if (target != null) {
5030
- top = Math.max(line.from, target - margin);
5031
- bot = Math.min(line.to, target + margin);
5067
+ let targetFrac = findFraction(structure, target);
5068
+ let spaceFrac = ((this.visibleBottom - this.visibleTop) / 2 + marginHeight) / line.height;
5069
+ top = targetFrac - spaceFrac;
5070
+ bot = targetFrac + spaceFrac;
5032
5071
  }
5033
5072
  else {
5034
5073
  top = (this.visibleTop - line.top - marginHeight) / line.height;
@@ -5038,14 +5077,16 @@ class ViewState {
5038
5077
  viewTo = findPosition(structure, bot);
5039
5078
  }
5040
5079
  else {
5080
+ let totalWidth = structure.total * this.heightOracle.charWidth;
5081
+ let marginWidth = margin * this.heightOracle.charWidth;
5041
5082
  let left, right;
5042
5083
  if (target != null) {
5043
- left = Math.max(line.from, target - doubleMargin);
5044
- right = Math.min(line.to, target + doubleMargin);
5084
+ let targetFrac = findFraction(structure, target);
5085
+ let spaceFrac = ((this.pixelViewport.right - this.pixelViewport.left) / 2 + marginWidth) / totalWidth;
5086
+ left = targetFrac - spaceFrac;
5087
+ right = targetFrac + spaceFrac;
5045
5088
  }
5046
5089
  else {
5047
- let totalWidth = structure.total * this.heightOracle.charWidth;
5048
- let marginWidth = margin * this.heightOracle.charWidth;
5049
5090
  left = (this.pixelViewport.left - marginWidth) / totalWidth;
5050
5091
  right = (this.pixelViewport.right + marginWidth) / totalWidth;
5051
5092
  }
@@ -5452,6 +5493,227 @@ const baseTheme$1 = buildTheme("." + baseThemeID, {
5452
5493
  }
5453
5494
  }, lightDarkIDs);
5454
5495
 
5496
+ class DOMChange {
5497
+ constructor(view, start, end, typeOver) {
5498
+ this.typeOver = typeOver;
5499
+ this.bounds = null;
5500
+ this.text = "";
5501
+ let { impreciseHead: iHead, impreciseAnchor: iAnchor } = view.docView;
5502
+ if (start > -1 && !view.state.readOnly && (this.bounds = view.docView.domBoundsAround(start, end, 0))) {
5503
+ let selPoints = iHead || iAnchor ? [] : selectionPoints(view);
5504
+ let reader = new DOMReader(selPoints, view.state);
5505
+ reader.readRange(this.bounds.startDOM, this.bounds.endDOM);
5506
+ this.text = reader.text;
5507
+ this.newSel = selectionFromPoints(selPoints, this.bounds.from);
5508
+ }
5509
+ else {
5510
+ let domSel = view.observer.selectionRange;
5511
+ let head = iHead && iHead.node == domSel.focusNode && iHead.offset == domSel.focusOffset ||
5512
+ !contains(view.contentDOM, domSel.focusNode)
5513
+ ? view.state.selection.main.head
5514
+ : view.docView.posFromDOM(domSel.focusNode, domSel.focusOffset);
5515
+ let anchor = iAnchor && iAnchor.node == domSel.anchorNode && iAnchor.offset == domSel.anchorOffset ||
5516
+ !contains(view.contentDOM, domSel.anchorNode)
5517
+ ? view.state.selection.main.anchor
5518
+ : view.docView.posFromDOM(domSel.anchorNode, domSel.anchorOffset);
5519
+ this.newSel = state.EditorSelection.single(anchor, head);
5520
+ }
5521
+ }
5522
+ }
5523
+ function applyDOMChange(view, domChange) {
5524
+ let change;
5525
+ let { newSel } = domChange, sel = view.state.selection.main;
5526
+ if (domChange.bounds) {
5527
+ let { from, to } = domChange.bounds;
5528
+ let preferredPos = sel.from, preferredSide = null;
5529
+ // Prefer anchoring to end when Backspace is pressed (or, on
5530
+ // Android, when something was deleted)
5531
+ if (view.inputState.lastKeyCode === 8 && view.inputState.lastKeyTime > Date.now() - 100 ||
5532
+ browser.android && domChange.text.length < to - from) {
5533
+ preferredPos = sel.to;
5534
+ preferredSide = "end";
5535
+ }
5536
+ let diff = findDiff(view.state.doc.sliceString(from, to, LineBreakPlaceholder), domChange.text, preferredPos - from, preferredSide);
5537
+ if (diff) {
5538
+ // Chrome inserts two newlines when pressing shift-enter at the
5539
+ // end of a line. DomChange drops one of those.
5540
+ if (browser.chrome && view.inputState.lastKeyCode == 13 &&
5541
+ diff.toB == diff.from + 2 && domChange.text.slice(diff.from, diff.toB) == LineBreakPlaceholder + LineBreakPlaceholder)
5542
+ diff.toB--;
5543
+ change = { from: from + diff.from, to: from + diff.toA,
5544
+ insert: state.Text.of(domChange.text.slice(diff.from, diff.toB).split(LineBreakPlaceholder)) };
5545
+ }
5546
+ }
5547
+ else if (newSel && (!view.hasFocus || !view.state.facet(editable) || newSel.main.eq(sel))) {
5548
+ newSel = null;
5549
+ }
5550
+ if (!change && !newSel)
5551
+ return false;
5552
+ if (!change && domChange.typeOver && !sel.empty && newSel && newSel.main.empty) {
5553
+ // Heuristic to notice typing over a selected character
5554
+ change = { from: sel.from, to: sel.to, insert: view.state.doc.slice(sel.from, sel.to) };
5555
+ }
5556
+ else if (change && change.from >= sel.from && change.to <= sel.to &&
5557
+ (change.from != sel.from || change.to != sel.to) &&
5558
+ (sel.to - sel.from) - (change.to - change.from) <= 4) {
5559
+ // If the change is inside the selection and covers most of it,
5560
+ // assume it is a selection replace (with identical characters at
5561
+ // the start/end not included in the diff)
5562
+ change = {
5563
+ from: sel.from, to: sel.to,
5564
+ insert: view.state.doc.slice(sel.from, change.from).append(change.insert).append(view.state.doc.slice(change.to, sel.to))
5565
+ };
5566
+ }
5567
+ else if ((browser.mac || browser.android) && change && change.from == change.to && change.from == sel.head - 1 &&
5568
+ /^\. ?$/.test(change.insert.toString())) {
5569
+ // Detect insert-period-on-double-space Mac and Android behavior,
5570
+ // and transform it into a regular space insert.
5571
+ if (newSel && change.insert.length == 2)
5572
+ newSel = state.EditorSelection.single(newSel.main.anchor - 1, newSel.main.head - 1);
5573
+ change = { from: sel.from, to: sel.to, insert: state.Text.of([" "]) };
5574
+ }
5575
+ if (change) {
5576
+ let startState = view.state;
5577
+ if (browser.ios && view.inputState.flushIOSKey(view))
5578
+ return true;
5579
+ // Android browsers don't fire reasonable key events for enter,
5580
+ // backspace, or delete. So this detects changes that look like
5581
+ // they're caused by those keys, and reinterprets them as key
5582
+ // events. (Some of these keys are also handled by beforeinput
5583
+ // events and the pendingAndroidKey mechanism, but that's not
5584
+ // reliable in all situations.)
5585
+ if (browser.android &&
5586
+ ((change.from == sel.from && change.to == sel.to &&
5587
+ change.insert.length == 1 && change.insert.lines == 2 &&
5588
+ dispatchKey(view.contentDOM, "Enter", 13)) ||
5589
+ (change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 &&
5590
+ dispatchKey(view.contentDOM, "Backspace", 8)) ||
5591
+ (change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
5592
+ dispatchKey(view.contentDOM, "Delete", 46))))
5593
+ return true;
5594
+ let text = change.insert.toString();
5595
+ if (view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text)))
5596
+ return true;
5597
+ if (view.inputState.composing >= 0)
5598
+ view.inputState.composing++;
5599
+ let tr;
5600
+ if (change.from >= sel.from && change.to <= sel.to && change.to - change.from >= (sel.to - sel.from) / 3 &&
5601
+ (!newSel || newSel.main.empty && newSel.main.from == change.from + change.insert.length) &&
5602
+ view.inputState.composing < 0) {
5603
+ let before = sel.from < change.from ? startState.sliceDoc(sel.from, change.from) : "";
5604
+ let after = sel.to > change.to ? startState.sliceDoc(change.to, sel.to) : "";
5605
+ tr = startState.replaceSelection(view.state.toText(before + change.insert.sliceString(0, undefined, view.state.lineBreak) + after));
5606
+ }
5607
+ else {
5608
+ let changes = startState.changes(change);
5609
+ let mainSel = newSel && !startState.selection.main.eq(newSel.main) && newSel.main.to <= changes.newLength
5610
+ ? newSel.main : undefined;
5611
+ // Try to apply a composition change to all cursors
5612
+ if (startState.selection.ranges.length > 1 && view.inputState.composing >= 0 &&
5613
+ change.to <= sel.to && change.to >= sel.to - 10) {
5614
+ let replaced = view.state.sliceDoc(change.from, change.to);
5615
+ let compositionRange = compositionSurroundingNode(view) || view.state.doc.lineAt(sel.head);
5616
+ let offset = sel.to - change.to, size = sel.to - sel.from;
5617
+ tr = startState.changeByRange(range => {
5618
+ if (range.from == sel.from && range.to == sel.to)
5619
+ return { changes, range: mainSel || range.map(changes) };
5620
+ let to = range.to - offset, from = to - replaced.length;
5621
+ if (range.to - range.from != size || view.state.sliceDoc(from, to) != replaced ||
5622
+ // Unfortunately, there's no way to make multiple
5623
+ // changes in the same node work without aborting
5624
+ // composition, so cursors in the composition range are
5625
+ // ignored.
5626
+ compositionRange && range.to >= compositionRange.from && range.from <= compositionRange.to)
5627
+ return { range };
5628
+ let rangeChanges = startState.changes({ from, to, insert: change.insert }), selOff = range.to - sel.to;
5629
+ return {
5630
+ changes: rangeChanges,
5631
+ range: !mainSel ? range.map(rangeChanges) :
5632
+ state.EditorSelection.range(Math.max(0, mainSel.anchor + selOff), Math.max(0, mainSel.head + selOff))
5633
+ };
5634
+ });
5635
+ }
5636
+ else {
5637
+ tr = {
5638
+ changes,
5639
+ selection: mainSel && startState.selection.replaceRange(mainSel)
5640
+ };
5641
+ }
5642
+ }
5643
+ let userEvent = "input.type";
5644
+ if (view.composing) {
5645
+ userEvent += ".compose";
5646
+ if (view.inputState.compositionFirstChange) {
5647
+ userEvent += ".start";
5648
+ view.inputState.compositionFirstChange = false;
5649
+ }
5650
+ }
5651
+ view.dispatch(tr, { scrollIntoView: true, userEvent });
5652
+ return true;
5653
+ }
5654
+ else if (newSel && !newSel.main.eq(sel)) {
5655
+ let scrollIntoView = false, userEvent = "select";
5656
+ if (view.inputState.lastSelectionTime > Date.now() - 50) {
5657
+ if (view.inputState.lastSelectionOrigin == "select")
5658
+ scrollIntoView = true;
5659
+ userEvent = view.inputState.lastSelectionOrigin;
5660
+ }
5661
+ view.dispatch({ selection: newSel, scrollIntoView, userEvent });
5662
+ return true;
5663
+ }
5664
+ else {
5665
+ return false;
5666
+ }
5667
+ }
5668
+ function findDiff(a, b, preferredPos, preferredSide) {
5669
+ let minLen = Math.min(a.length, b.length);
5670
+ let from = 0;
5671
+ while (from < minLen && a.charCodeAt(from) == b.charCodeAt(from))
5672
+ from++;
5673
+ if (from == minLen && a.length == b.length)
5674
+ return null;
5675
+ let toA = a.length, toB = b.length;
5676
+ while (toA > 0 && toB > 0 && a.charCodeAt(toA - 1) == b.charCodeAt(toB - 1)) {
5677
+ toA--;
5678
+ toB--;
5679
+ }
5680
+ if (preferredSide == "end") {
5681
+ let adjust = Math.max(0, from - Math.min(toA, toB));
5682
+ preferredPos -= toA + adjust - from;
5683
+ }
5684
+ if (toA < from && a.length < b.length) {
5685
+ let move = preferredPos <= from && preferredPos >= toA ? from - preferredPos : 0;
5686
+ from -= move;
5687
+ toB = from + (toB - toA);
5688
+ toA = from;
5689
+ }
5690
+ else if (toB < from) {
5691
+ let move = preferredPos <= from && preferredPos >= toB ? from - preferredPos : 0;
5692
+ from -= move;
5693
+ toA = from + (toA - toB);
5694
+ toB = from;
5695
+ }
5696
+ return { from, toA, toB };
5697
+ }
5698
+ function selectionPoints(view) {
5699
+ let result = [];
5700
+ if (view.root.activeElement != view.contentDOM)
5701
+ return result;
5702
+ let { anchorNode, anchorOffset, focusNode, focusOffset } = view.observer.selectionRange;
5703
+ if (anchorNode) {
5704
+ result.push(new DOMPoint(anchorNode, anchorOffset));
5705
+ if (focusNode != anchorNode || focusOffset != anchorOffset)
5706
+ result.push(new DOMPoint(focusNode, focusOffset));
5707
+ }
5708
+ return result;
5709
+ }
5710
+ function selectionFromPoints(points, base) {
5711
+ if (points.length == 0)
5712
+ return null;
5713
+ let anchor = points[0].pos, head = points.length == 2 ? points[1].pos : anchor;
5714
+ return anchor > -1 && head > -1 ? state.EditorSelection.single(anchor + base, head + base) : null;
5715
+ }
5716
+
5455
5717
  const observeOptions = {
5456
5718
  childList: true,
5457
5719
  characterData: true,
@@ -5463,10 +5725,8 @@ const observeOptions = {
5463
5725
  // DOMCharacterDataModified there
5464
5726
  const useCharData = browser.ie && browser.ie_version <= 11;
5465
5727
  class DOMObserver {
5466
- constructor(view, onChange, onScrollChanged) {
5728
+ constructor(view) {
5467
5729
  this.view = view;
5468
- this.onChange = onChange;
5469
- this.onScrollChanged = onScrollChanged;
5470
5730
  this.active = false;
5471
5731
  // The known selection. Kept in our own object, as opposed to just
5472
5732
  // directly accessing the selection because:
@@ -5481,6 +5741,7 @@ class DOMObserver {
5481
5741
  this.resizeTimeout = -1;
5482
5742
  this.queue = [];
5483
5743
  this.delayedAndroidKey = null;
5744
+ this.flushingAndroidKey = -1;
5484
5745
  this.lastChange = 0;
5485
5746
  this.scrollTargets = [];
5486
5747
  this.intersection = null;
@@ -5549,6 +5810,11 @@ class DOMObserver {
5549
5810
  this.listenForScroll();
5550
5811
  this.readSelectionRange();
5551
5812
  }
5813
+ onScrollChanged(e) {
5814
+ this.view.inputState.runScrollHandlers(this.view, e);
5815
+ if (this.intersecting)
5816
+ this.view.measure();
5817
+ }
5552
5818
  onScroll(e) {
5553
5819
  if (this.intersecting)
5554
5820
  this.flush(false);
@@ -5708,14 +5974,17 @@ class DOMObserver {
5708
5974
  // them or, if that has no effect, dispatches the given key.
5709
5975
  delayAndroidKey(key, keyCode) {
5710
5976
  var _a;
5711
- if (!this.delayedAndroidKey)
5712
- this.view.win.requestAnimationFrame(() => {
5977
+ if (!this.delayedAndroidKey) {
5978
+ let flush = () => {
5713
5979
  let key = this.delayedAndroidKey;
5714
- this.delayedAndroidKey = null;
5715
- this.delayedFlush = -1;
5716
- if (!this.flush() && key.force)
5717
- dispatchKey(this.dom, key.key, key.keyCode);
5718
- });
5980
+ if (key) {
5981
+ this.clearDelayedAndroidKey();
5982
+ if (!this.flush() && key.force)
5983
+ dispatchKey(this.dom, key.key, key.keyCode);
5984
+ }
5985
+ };
5986
+ this.flushingAndroidKey = this.view.win.requestAnimationFrame(flush);
5987
+ }
5719
5988
  // Since backspace beforeinput is sometimes signalled spuriously,
5720
5989
  // Enter always takes precedence.
5721
5990
  if (!this.delayedAndroidKey || key == "Enter")
@@ -5728,6 +5997,11 @@ class DOMObserver {
5728
5997
  force: this.lastChange < Date.now() - 50 || !!((_a = this.delayedAndroidKey) === null || _a === void 0 ? void 0 : _a.force)
5729
5998
  };
5730
5999
  }
6000
+ clearDelayedAndroidKey() {
6001
+ this.win.cancelAnimationFrame(this.flushingAndroidKey);
6002
+ this.delayedAndroidKey = null;
6003
+ this.flushingAndroidKey = -1;
6004
+ }
5731
6005
  flushSoon() {
5732
6006
  if (this.delayedFlush < 0)
5733
6007
  this.delayedFlush = this.view.win.requestAnimationFrame(() => { this.delayedFlush = -1; this.flush(); });
@@ -5762,6 +6036,17 @@ class DOMObserver {
5762
6036
  }
5763
6037
  return { from, to, typeOver };
5764
6038
  }
6039
+ readChange() {
6040
+ let { from, to, typeOver } = this.processRecords();
6041
+ let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
6042
+ if (from < 0 && !newSel)
6043
+ return null;
6044
+ if (from > -1)
6045
+ this.lastChange = Date.now();
6046
+ this.view.inputState.lastFocusTime = 0;
6047
+ this.selectionChanged = false;
6048
+ return new DOMChange(this.view, from, to, typeOver);
6049
+ }
5765
6050
  // Apply pending changes, if any
5766
6051
  flush(readSelection = true) {
5767
6052
  // Completely hold off flushing when pending keys are set—the code
@@ -5771,16 +6056,11 @@ class DOMObserver {
5771
6056
  return false;
5772
6057
  if (readSelection)
5773
6058
  this.readSelectionRange();
5774
- let { from, to, typeOver } = this.processRecords();
5775
- let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
5776
- if (from < 0 && !newSel)
6059
+ let domChange = this.readChange();
6060
+ if (!domChange)
5777
6061
  return false;
5778
- if (from > -1)
5779
- this.lastChange = Date.now();
5780
- this.view.inputState.lastFocusTime = 0;
5781
- this.selectionChanged = false;
5782
6062
  let startState = this.view.state;
5783
- let handled = this.onChange(from, to, typeOver);
6063
+ let handled = applyDOMChange(this.view, domChange);
5784
6064
  // The view wasn't updated
5785
6065
  if (this.view.state == startState)
5786
6066
  this.view.update([]);
@@ -5836,6 +6116,8 @@ class DOMObserver {
5836
6116
  this.removeWindowListeners(this.win);
5837
6117
  clearTimeout(this.parentCheck);
5838
6118
  clearTimeout(this.resizeTimeout);
6119
+ this.win.cancelAnimationFrame(this.delayedFlush);
6120
+ this.win.cancelAnimationFrame(this.flushingAndroidKey);
5839
6121
  }
5840
6122
  }
5841
6123
  function findChild(cView, dom, dir) {
@@ -5877,218 +6159,6 @@ function safariSelectionRangeHack(view) {
5877
6159
  return { anchorNode, anchorOffset, focusNode, focusOffset };
5878
6160
  }
5879
6161
 
5880
- function applyDOMChange(view, start, end, typeOver) {
5881
- let change, newSel;
5882
- let sel = view.state.selection.main;
5883
- if (start > -1) {
5884
- let bounds = view.docView.domBoundsAround(start, end, 0);
5885
- if (!bounds || view.state.readOnly)
5886
- return false;
5887
- let { from, to } = bounds;
5888
- let selPoints = view.docView.impreciseHead || view.docView.impreciseAnchor ? [] : selectionPoints(view);
5889
- let reader = new DOMReader(selPoints, view.state);
5890
- reader.readRange(bounds.startDOM, bounds.endDOM);
5891
- let preferredPos = sel.from, preferredSide = null;
5892
- // Prefer anchoring to end when Backspace is pressed (or, on
5893
- // Android, when something was deleted)
5894
- if (view.inputState.lastKeyCode === 8 && view.inputState.lastKeyTime > Date.now() - 100 ||
5895
- browser.android && reader.text.length < to - from) {
5896
- preferredPos = sel.to;
5897
- preferredSide = "end";
5898
- }
5899
- let diff = findDiff(view.state.doc.sliceString(from, to, LineBreakPlaceholder), reader.text, preferredPos - from, preferredSide);
5900
- if (diff) {
5901
- // Chrome inserts two newlines when pressing shift-enter at the
5902
- // end of a line. This drops one of those.
5903
- if (browser.chrome && view.inputState.lastKeyCode == 13 &&
5904
- diff.toB == diff.from + 2 && reader.text.slice(diff.from, diff.toB) == LineBreakPlaceholder + LineBreakPlaceholder)
5905
- diff.toB--;
5906
- change = { from: from + diff.from, to: from + diff.toA,
5907
- insert: state.Text.of(reader.text.slice(diff.from, diff.toB).split(LineBreakPlaceholder)) };
5908
- }
5909
- newSel = selectionFromPoints(selPoints, from);
5910
- }
5911
- else if (view.hasFocus || !view.state.facet(editable)) {
5912
- let domSel = view.observer.selectionRange;
5913
- let { impreciseHead: iHead, impreciseAnchor: iAnchor } = view.docView;
5914
- let head = iHead && iHead.node == domSel.focusNode && iHead.offset == domSel.focusOffset ||
5915
- !contains(view.contentDOM, domSel.focusNode)
5916
- ? view.state.selection.main.head
5917
- : view.docView.posFromDOM(domSel.focusNode, domSel.focusOffset);
5918
- let anchor = iAnchor && iAnchor.node == domSel.anchorNode && iAnchor.offset == domSel.anchorOffset ||
5919
- !contains(view.contentDOM, domSel.anchorNode)
5920
- ? view.state.selection.main.anchor
5921
- : view.docView.posFromDOM(domSel.anchorNode, domSel.anchorOffset);
5922
- if (head != sel.head || anchor != sel.anchor)
5923
- newSel = state.EditorSelection.single(anchor, head);
5924
- }
5925
- if (!change && !newSel)
5926
- return false;
5927
- if (!change && typeOver && !sel.empty && newSel && newSel.main.empty) {
5928
- // Heuristic to notice typing over a selected character
5929
- change = { from: sel.from, to: sel.to, insert: view.state.doc.slice(sel.from, sel.to) };
5930
- }
5931
- else if (change && change.from >= sel.from && change.to <= sel.to &&
5932
- (change.from != sel.from || change.to != sel.to) &&
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)
5937
- change = {
5938
- from: sel.from, to: sel.to,
5939
- insert: view.state.doc.slice(sel.from, change.from).append(change.insert).append(view.state.doc.slice(change.to, sel.to))
5940
- };
5941
- }
5942
- else if ((browser.mac || browser.android) && change && change.from == change.to && change.from == sel.head - 1 &&
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);
5948
- change = { from: sel.from, to: sel.to, insert: state.Text.of([" "]) };
5949
- }
5950
- if (change) {
5951
- let startState = view.state;
5952
- if (browser.ios && view.inputState.flushIOSKey(view))
5953
- return true;
5954
- // Android browsers don't fire reasonable key events for enter,
5955
- // backspace, or delete. So this detects changes that look like
5956
- // they're caused by those keys, and reinterprets them as key
5957
- // events. (Some of these keys are also handled by beforeinput
5958
- // events and the pendingAndroidKey mechanism, but that's not
5959
- // reliable in all situations.)
5960
- if (browser.android &&
5961
- ((change.from == sel.from && change.to == sel.to &&
5962
- change.insert.length == 1 && change.insert.lines == 2 &&
5963
- dispatchKey(view.contentDOM, "Enter", 13)) ||
5964
- (change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 &&
5965
- dispatchKey(view.contentDOM, "Backspace", 8)) ||
5966
- (change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
5967
- dispatchKey(view.contentDOM, "Delete", 46))))
5968
- return true;
5969
- let text = change.insert.toString();
5970
- if (view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text)))
5971
- return true;
5972
- if (view.inputState.composing >= 0)
5973
- view.inputState.composing++;
5974
- let tr;
5975
- if (change.from >= sel.from && change.to <= sel.to && change.to - change.from >= (sel.to - sel.from) / 3 &&
5976
- (!newSel || newSel.main.empty && newSel.main.from == change.from + change.insert.length) &&
5977
- view.inputState.composing < 0) {
5978
- let before = sel.from < change.from ? startState.sliceDoc(sel.from, change.from) : "";
5979
- let after = sel.to > change.to ? startState.sliceDoc(change.to, sel.to) : "";
5980
- tr = startState.replaceSelection(view.state.toText(before + change.insert.sliceString(0, undefined, view.state.lineBreak) + after));
5981
- }
5982
- else {
5983
- let changes = startState.changes(change);
5984
- let mainSel = newSel && !startState.selection.main.eq(newSel.main) && newSel.main.to <= changes.newLength
5985
- ? newSel.main : undefined;
5986
- // Try to apply a composition change to all cursors
5987
- if (startState.selection.ranges.length > 1 && view.inputState.composing >= 0 &&
5988
- change.to <= sel.to && change.to >= sel.to - 10) {
5989
- let replaced = view.state.sliceDoc(change.from, change.to);
5990
- let compositionRange = compositionSurroundingNode(view) || view.state.doc.lineAt(sel.head);
5991
- let offset = sel.to - change.to, size = sel.to - sel.from;
5992
- tr = startState.changeByRange(range => {
5993
- if (range.from == sel.from && range.to == sel.to)
5994
- return { changes, range: mainSel || range.map(changes) };
5995
- let to = range.to - offset, from = to - replaced.length;
5996
- if (range.to - range.from != size || view.state.sliceDoc(from, to) != replaced ||
5997
- // Unfortunately, there's no way to make multiple
5998
- // changes in the same node work without aborting
5999
- // composition, so cursors in the composition range are
6000
- // ignored.
6001
- compositionRange && range.to >= compositionRange.from && range.from <= compositionRange.to)
6002
- return { range };
6003
- let rangeChanges = startState.changes({ from, to, insert: change.insert }), selOff = range.to - sel.to;
6004
- return {
6005
- changes: rangeChanges,
6006
- range: !mainSel ? range.map(rangeChanges) :
6007
- state.EditorSelection.range(Math.max(0, mainSel.anchor + selOff), Math.max(0, mainSel.head + selOff))
6008
- };
6009
- });
6010
- }
6011
- else {
6012
- tr = {
6013
- changes,
6014
- selection: mainSel && startState.selection.replaceRange(mainSel)
6015
- };
6016
- }
6017
- }
6018
- let userEvent = "input.type";
6019
- if (view.composing) {
6020
- userEvent += ".compose";
6021
- if (view.inputState.compositionFirstChange) {
6022
- userEvent += ".start";
6023
- view.inputState.compositionFirstChange = false;
6024
- }
6025
- }
6026
- view.dispatch(tr, { scrollIntoView: true, userEvent });
6027
- return true;
6028
- }
6029
- else if (newSel && !newSel.main.eq(sel)) {
6030
- let scrollIntoView = false, userEvent = "select";
6031
- if (view.inputState.lastSelectionTime > Date.now() - 50) {
6032
- if (view.inputState.lastSelectionOrigin == "select")
6033
- scrollIntoView = true;
6034
- userEvent = view.inputState.lastSelectionOrigin;
6035
- }
6036
- view.dispatch({ selection: newSel, scrollIntoView, userEvent });
6037
- return true;
6038
- }
6039
- else {
6040
- return false;
6041
- }
6042
- }
6043
- function findDiff(a, b, preferredPos, preferredSide) {
6044
- let minLen = Math.min(a.length, b.length);
6045
- let from = 0;
6046
- while (from < minLen && a.charCodeAt(from) == b.charCodeAt(from))
6047
- from++;
6048
- if (from == minLen && a.length == b.length)
6049
- return null;
6050
- let toA = a.length, toB = b.length;
6051
- while (toA > 0 && toB > 0 && a.charCodeAt(toA - 1) == b.charCodeAt(toB - 1)) {
6052
- toA--;
6053
- toB--;
6054
- }
6055
- if (preferredSide == "end") {
6056
- let adjust = Math.max(0, from - Math.min(toA, toB));
6057
- preferredPos -= toA + adjust - from;
6058
- }
6059
- if (toA < from && a.length < b.length) {
6060
- let move = preferredPos <= from && preferredPos >= toA ? from - preferredPos : 0;
6061
- from -= move;
6062
- toB = from + (toB - toA);
6063
- toA = from;
6064
- }
6065
- else if (toB < from) {
6066
- let move = preferredPos <= from && preferredPos >= toB ? from - preferredPos : 0;
6067
- from -= move;
6068
- toA = from + (toA - toB);
6069
- toB = from;
6070
- }
6071
- return { from, toA, toB };
6072
- }
6073
- function selectionPoints(view) {
6074
- let result = [];
6075
- if (view.root.activeElement != view.contentDOM)
6076
- return result;
6077
- let { anchorNode, anchorOffset, focusNode, focusOffset } = view.observer.selectionRange;
6078
- if (anchorNode) {
6079
- result.push(new DOMPoint(anchorNode, anchorOffset));
6080
- if (focusNode != anchorNode || focusOffset != anchorOffset)
6081
- result.push(new DOMPoint(focusNode, focusOffset));
6082
- }
6083
- return result;
6084
- }
6085
- function selectionFromPoints(points, base) {
6086
- if (points.length == 0)
6087
- return null;
6088
- let anchor = points[0].pos, head = points.length == 2 ? points[1].pos : anchor;
6089
- return anchor > -1 && head > -1 ? state.EditorSelection.single(anchor + base, head + base) : null;
6090
- }
6091
-
6092
6162
  // The editor's update state machine looks something like this:
6093
6163
  //
6094
6164
  // Idle → Updating ⇆ Idle (unchecked) → Measuring → Idle
@@ -6151,13 +6221,7 @@ class EditorView {
6151
6221
  this.plugins = this.state.facet(viewPlugin).map(spec => new PluginInstance(spec));
6152
6222
  for (let plugin of this.plugins)
6153
6223
  plugin.update(this);
6154
- this.observer = new DOMObserver(this, (from, to, typeOver) => {
6155
- return applyDOMChange(this, from, to, typeOver);
6156
- }, event => {
6157
- this.inputState.runScrollHandlers(this, event);
6158
- if (this.observer.intersecting)
6159
- this.measure();
6160
- });
6224
+ this.observer = new DOMObserver(this);
6161
6225
  this.inputState = new InputState(this);
6162
6226
  this.inputState.ensureHandlers(this, this.plugins);
6163
6227
  this.docView = new DocView(this);
@@ -6241,7 +6305,20 @@ class EditorView {
6241
6305
  this.viewState.state = state$1;
6242
6306
  return;
6243
6307
  }
6244
- this.observer.clear();
6308
+ // If there was a pending DOM change, eagerly read it and try to
6309
+ // apply it after the given transactions.
6310
+ let pendingKey = this.observer.delayedAndroidKey, domChange = null;
6311
+ if (pendingKey) {
6312
+ this.observer.clearDelayedAndroidKey();
6313
+ domChange = this.observer.readChange();
6314
+ // Only try to apply DOM changes if the transactions didn't
6315
+ // change the doc or selection.
6316
+ if (domChange && !this.state.doc.eq(state$1.doc) || !this.state.selection.eq(state$1.selection))
6317
+ domChange = null;
6318
+ }
6319
+ else {
6320
+ this.observer.clear();
6321
+ }
6245
6322
  // When the phrases change, redraw the editor
6246
6323
  if (state$1.facet(state.EditorState.phrases) != this.state.facet(state.EditorState.phrases))
6247
6324
  return this.setState(state$1);
@@ -6283,6 +6360,10 @@ class EditorView {
6283
6360
  if (!update.empty)
6284
6361
  for (let listener of this.state.facet(updateListener))
6285
6362
  listener(update);
6363
+ if (domChange) {
6364
+ if (!applyDOMChange(this, domChange) && pendingKey.force)
6365
+ dispatchKey(this.contentDOM, pendingKey.key, pendingKey.keyCode);
6366
+ }
6286
6367
  }
6287
6368
  /**
6288
6369
  Reset the view to the given state. (This will cause the entire