@codemirror/view 6.3.0 → 6.4.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/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,33 @@ 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
+ let before = null, beforePos = -1, after = null, afterPos = -1;
1054
+ function scan(view, pos) {
1055
+ for (let i = 0, off = 0; i < view.children.length && off <= pos; i++) {
1056
+ let child = view.children[i], end = off + child.length;
1057
+ if (end >= pos) {
1058
+ if (child.children.length) {
1059
+ scan(child, pos - off);
1060
+ }
1061
+ else if (!after && (end > pos || off == end && child.getSide() > 0)) {
1062
+ after = child;
1063
+ afterPos = pos - off;
1064
+ }
1065
+ else if (off < pos || (off == end && child.getSide() < 0)) {
1066
+ before = child;
1067
+ beforePos = pos - off;
1068
+ }
1063
1069
  }
1064
- let rect = child.coordsAt(Math.max(0, pos - off), side);
1065
- return flatten && rect ? flattenRect(rect, side < 0) : rect;
1070
+ off = end;
1066
1071
  }
1067
- off = end;
1068
1072
  }
1073
+ scan(view, pos);
1074
+ let target = (side < 0 ? before : after) || before || after;
1075
+ if (target)
1076
+ return target.coordsAt(Math.max(0, target == before ? beforePos : afterPos), side);
1077
+ return fallbackRect(view);
1078
+ }
1079
+ function fallbackRect(view) {
1069
1080
  let last = view.dom.lastChild;
1070
1081
  if (!last)
1071
1082
  return view.dom.getBoundingClientRect();
@@ -1683,7 +1694,7 @@ class ContentBuilder {
1683
1694
  this.addBlockWidget(new BlockWidgetView(deco.widget || new NullWidget("div"), len, type));
1684
1695
  }
1685
1696
  else {
1686
- let view = WidgetView.create(deco.widget || new NullWidget("span"), len, deco.startSide);
1697
+ let view = WidgetView.create(deco.widget || new NullWidget("span"), len, len ? 0 : deco.startSide);
1687
1698
  let cursorBefore = this.atCursorPos && !view.isEditable && openStart <= active.length && (from < to || deco.startSide > 0);
1688
1699
  let cursorAfter = !view.isEditable && (from < to || deco.startSide <= 0);
1689
1700
  let line = this.getLine();
@@ -4876,7 +4887,7 @@ class ViewState {
4876
4887
  refresh = true;
4877
4888
  if (refresh || oracle.lineWrapping && Math.abs(contentWidth - this.contentDOMWidth) > oracle.charWidth) {
4878
4889
  let { lineHeight, charWidth } = view.docView.measureTextSize();
4879
- refresh = oracle.refresh(whiteSpace, lineHeight, charWidth, contentWidth / charWidth, lineHeights);
4890
+ refresh = lineHeight > 0 && oracle.refresh(whiteSpace, lineHeight, charWidth, contentWidth / charWidth, lineHeights);
4880
4891
  if (refresh) {
4881
4892
  view.docView.minWidth = 0;
4882
4893
  result |= 8 /* UpdateFlag.Geometry */;
@@ -5020,8 +5031,10 @@ class ViewState {
5020
5031
  let marginHeight = (margin / this.heightOracle.lineLength) * this.heightOracle.lineHeight;
5021
5032
  let top, bot;
5022
5033
  if (target != null) {
5023
- top = Math.max(line.from, target - margin);
5024
- bot = Math.min(line.to, target + margin);
5034
+ let targetFrac = findFraction(structure, target);
5035
+ let spaceFrac = ((this.visibleBottom - this.visibleTop) / 2 + marginHeight) / line.height;
5036
+ top = targetFrac - spaceFrac;
5037
+ bot = targetFrac + spaceFrac;
5025
5038
  }
5026
5039
  else {
5027
5040
  top = (this.visibleTop - line.top - marginHeight) / line.height;
@@ -5031,14 +5044,16 @@ class ViewState {
5031
5044
  viewTo = findPosition(structure, bot);
5032
5045
  }
5033
5046
  else {
5047
+ let totalWidth = structure.total * this.heightOracle.charWidth;
5048
+ let marginWidth = margin * this.heightOracle.charWidth;
5034
5049
  let left, right;
5035
5050
  if (target != null) {
5036
- left = Math.max(line.from, target - doubleMargin);
5037
- right = Math.min(line.to, target + doubleMargin);
5051
+ let targetFrac = findFraction(structure, target);
5052
+ let spaceFrac = ((this.pixelViewport.right - this.pixelViewport.left) / 2 + marginWidth) / totalWidth;
5053
+ left = targetFrac - spaceFrac;
5054
+ right = targetFrac + spaceFrac;
5038
5055
  }
5039
5056
  else {
5040
- let totalWidth = structure.total * this.heightOracle.charWidth;
5041
- let marginWidth = margin * this.heightOracle.charWidth;
5042
5057
  left = (this.pixelViewport.left - marginWidth) / totalWidth;
5043
5058
  right = (this.pixelViewport.right + marginWidth) / totalWidth;
5044
5059
  }
@@ -5445,6 +5460,236 @@ const baseTheme$1 = /*@__PURE__*/buildTheme("." + baseThemeID, {
5445
5460
  }
5446
5461
  }, lightDarkIDs);
5447
5462
 
5463
+ class DOMChange {
5464
+ constructor(view, start, end, typeOver) {
5465
+ this.typeOver = typeOver;
5466
+ this.bounds = null;
5467
+ this.text = "";
5468
+ let { impreciseHead: iHead, impreciseAnchor: iAnchor } = view.docView;
5469
+ if (start > -1 && !view.state.readOnly && (this.bounds = view.docView.domBoundsAround(start, end, 0))) {
5470
+ let selPoints = iHead || iAnchor ? [] : selectionPoints(view);
5471
+ let reader = new DOMReader(selPoints, view.state);
5472
+ reader.readRange(this.bounds.startDOM, this.bounds.endDOM);
5473
+ this.text = reader.text;
5474
+ this.newSel = selectionFromPoints(selPoints, this.bounds.from);
5475
+ }
5476
+ else {
5477
+ let domSel = view.observer.selectionRange;
5478
+ let head = iHead && iHead.node == domSel.focusNode && iHead.offset == domSel.focusOffset ||
5479
+ !contains(view.contentDOM, domSel.focusNode)
5480
+ ? view.state.selection.main.head
5481
+ : view.docView.posFromDOM(domSel.focusNode, domSel.focusOffset);
5482
+ let anchor = iAnchor && iAnchor.node == domSel.anchorNode && iAnchor.offset == domSel.anchorOffset ||
5483
+ !contains(view.contentDOM, domSel.anchorNode)
5484
+ ? view.state.selection.main.anchor
5485
+ : view.docView.posFromDOM(domSel.anchorNode, domSel.anchorOffset);
5486
+ this.newSel = EditorSelection.single(anchor, head);
5487
+ }
5488
+ }
5489
+ }
5490
+ function applyDOMChange(view, domChange) {
5491
+ let change;
5492
+ let { newSel } = domChange, sel = view.state.selection.main;
5493
+ if (domChange.bounds) {
5494
+ let { from, to } = domChange.bounds;
5495
+ let preferredPos = sel.from, preferredSide = null;
5496
+ // Prefer anchoring to end when Backspace is pressed (or, on
5497
+ // Android, when something was deleted)
5498
+ if (view.inputState.lastKeyCode === 8 && view.inputState.lastKeyTime > Date.now() - 100 ||
5499
+ browser.android && domChange.text.length < to - from) {
5500
+ preferredPos = sel.to;
5501
+ preferredSide = "end";
5502
+ }
5503
+ let diff = findDiff(view.state.doc.sliceString(from, to, LineBreakPlaceholder), domChange.text, preferredPos - from, preferredSide);
5504
+ if (diff) {
5505
+ // Chrome inserts two newlines when pressing shift-enter at the
5506
+ // end of a line. DomChange drops one of those.
5507
+ if (browser.chrome && view.inputState.lastKeyCode == 13 &&
5508
+ diff.toB == diff.from + 2 && domChange.text.slice(diff.from, diff.toB) == LineBreakPlaceholder + LineBreakPlaceholder)
5509
+ diff.toB--;
5510
+ change = { from: from + diff.from, to: from + diff.toA,
5511
+ insert: Text.of(domChange.text.slice(diff.from, diff.toB).split(LineBreakPlaceholder)) };
5512
+ }
5513
+ }
5514
+ else if (newSel && (!view.hasFocus || !view.state.facet(editable) || newSel.main.eq(sel))) {
5515
+ newSel = null;
5516
+ }
5517
+ if (!change && !newSel)
5518
+ return false;
5519
+ if (!change && domChange.typeOver && !sel.empty && newSel && newSel.main.empty) {
5520
+ // Heuristic to notice typing over a selected character
5521
+ change = { from: sel.from, to: sel.to, insert: view.state.doc.slice(sel.from, sel.to) };
5522
+ }
5523
+ else if (change && change.from >= sel.from && change.to <= sel.to &&
5524
+ (change.from != sel.from || change.to != sel.to) &&
5525
+ (sel.to - sel.from) - (change.to - change.from) <= 4) {
5526
+ // If the change is inside the selection and covers most of it,
5527
+ // assume it is a selection replace (with identical characters at
5528
+ // the start/end not included in the diff)
5529
+ change = {
5530
+ from: sel.from, to: sel.to,
5531
+ insert: view.state.doc.slice(sel.from, change.from).append(change.insert).append(view.state.doc.slice(change.to, sel.to))
5532
+ };
5533
+ }
5534
+ else if ((browser.mac || browser.android) && change && change.from == change.to && change.from == sel.head - 1 &&
5535
+ /^\. ?$/.test(change.insert.toString())) {
5536
+ // Detect insert-period-on-double-space Mac and Android behavior,
5537
+ // and transform it into a regular space insert.
5538
+ if (newSel && change.insert.length == 2)
5539
+ newSel = EditorSelection.single(newSel.main.anchor - 1, newSel.main.head - 1);
5540
+ change = { from: sel.from, to: sel.to, insert: Text.of([" "]) };
5541
+ }
5542
+ else if (browser.chrome && change && change.from == change.to && change.from == sel.head &&
5543
+ change.insert.toString() == "\n " && view.lineWrapping) {
5544
+ // In Chrome, if you insert a space at the start of a wrapped
5545
+ // line, it will actually insert a newline and a space, causing a
5546
+ // bogus new line to be created in CodeMirror (#968)
5547
+ if (newSel)
5548
+ newSel = EditorSelection.single(newSel.main.anchor - 1, newSel.main.head - 1);
5549
+ change = { from: sel.from, to: sel.to, insert: Text.of([" "]) };
5550
+ }
5551
+ if (change) {
5552
+ let startState = view.state;
5553
+ if (browser.ios && view.inputState.flushIOSKey(view))
5554
+ return true;
5555
+ // Android browsers don't fire reasonable key events for enter,
5556
+ // backspace, or delete. So this detects changes that look like
5557
+ // they're caused by those keys, and reinterprets them as key
5558
+ // events. (Some of these keys are also handled by beforeinput
5559
+ // events and the pendingAndroidKey mechanism, but that's not
5560
+ // reliable in all situations.)
5561
+ if (browser.android &&
5562
+ ((change.from == sel.from && change.to == sel.to &&
5563
+ change.insert.length == 1 && change.insert.lines == 2 &&
5564
+ dispatchKey(view.contentDOM, "Enter", 13)) ||
5565
+ (change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 &&
5566
+ dispatchKey(view.contentDOM, "Backspace", 8)) ||
5567
+ (change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
5568
+ dispatchKey(view.contentDOM, "Delete", 46))))
5569
+ return true;
5570
+ let text = change.insert.toString();
5571
+ if (view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text)))
5572
+ return true;
5573
+ if (view.inputState.composing >= 0)
5574
+ view.inputState.composing++;
5575
+ let tr;
5576
+ if (change.from >= sel.from && change.to <= sel.to && change.to - change.from >= (sel.to - sel.from) / 3 &&
5577
+ (!newSel || newSel.main.empty && newSel.main.from == change.from + change.insert.length) &&
5578
+ view.inputState.composing < 0) {
5579
+ let before = sel.from < change.from ? startState.sliceDoc(sel.from, change.from) : "";
5580
+ let after = sel.to > change.to ? startState.sliceDoc(change.to, sel.to) : "";
5581
+ tr = startState.replaceSelection(view.state.toText(before + change.insert.sliceString(0, undefined, view.state.lineBreak) + after));
5582
+ }
5583
+ else {
5584
+ let changes = startState.changes(change);
5585
+ let mainSel = newSel && !startState.selection.main.eq(newSel.main) && newSel.main.to <= changes.newLength
5586
+ ? newSel.main : undefined;
5587
+ // Try to apply a composition change to all cursors
5588
+ if (startState.selection.ranges.length > 1 && view.inputState.composing >= 0 &&
5589
+ change.to <= sel.to && change.to >= sel.to - 10) {
5590
+ let replaced = view.state.sliceDoc(change.from, change.to);
5591
+ let compositionRange = compositionSurroundingNode(view) || view.state.doc.lineAt(sel.head);
5592
+ let offset = sel.to - change.to, size = sel.to - sel.from;
5593
+ tr = startState.changeByRange(range => {
5594
+ if (range.from == sel.from && range.to == sel.to)
5595
+ return { changes, range: mainSel || range.map(changes) };
5596
+ let to = range.to - offset, from = to - replaced.length;
5597
+ if (range.to - range.from != size || view.state.sliceDoc(from, to) != replaced ||
5598
+ // Unfortunately, there's no way to make multiple
5599
+ // changes in the same node work without aborting
5600
+ // composition, so cursors in the composition range are
5601
+ // ignored.
5602
+ compositionRange && range.to >= compositionRange.from && range.from <= compositionRange.to)
5603
+ return { range };
5604
+ let rangeChanges = startState.changes({ from, to, insert: change.insert }), selOff = range.to - sel.to;
5605
+ return {
5606
+ changes: rangeChanges,
5607
+ range: !mainSel ? range.map(rangeChanges) :
5608
+ EditorSelection.range(Math.max(0, mainSel.anchor + selOff), Math.max(0, mainSel.head + selOff))
5609
+ };
5610
+ });
5611
+ }
5612
+ else {
5613
+ tr = {
5614
+ changes,
5615
+ selection: mainSel && startState.selection.replaceRange(mainSel)
5616
+ };
5617
+ }
5618
+ }
5619
+ let userEvent = "input.type";
5620
+ if (view.composing) {
5621
+ userEvent += ".compose";
5622
+ if (view.inputState.compositionFirstChange) {
5623
+ userEvent += ".start";
5624
+ view.inputState.compositionFirstChange = false;
5625
+ }
5626
+ }
5627
+ view.dispatch(tr, { scrollIntoView: true, userEvent });
5628
+ return true;
5629
+ }
5630
+ else if (newSel && !newSel.main.eq(sel)) {
5631
+ let scrollIntoView = false, userEvent = "select";
5632
+ if (view.inputState.lastSelectionTime > Date.now() - 50) {
5633
+ if (view.inputState.lastSelectionOrigin == "select")
5634
+ scrollIntoView = true;
5635
+ userEvent = view.inputState.lastSelectionOrigin;
5636
+ }
5637
+ view.dispatch({ selection: newSel, scrollIntoView, userEvent });
5638
+ return true;
5639
+ }
5640
+ else {
5641
+ return false;
5642
+ }
5643
+ }
5644
+ function findDiff(a, b, preferredPos, preferredSide) {
5645
+ let minLen = Math.min(a.length, b.length);
5646
+ let from = 0;
5647
+ while (from < minLen && a.charCodeAt(from) == b.charCodeAt(from))
5648
+ from++;
5649
+ if (from == minLen && a.length == b.length)
5650
+ return null;
5651
+ let toA = a.length, toB = b.length;
5652
+ while (toA > 0 && toB > 0 && a.charCodeAt(toA - 1) == b.charCodeAt(toB - 1)) {
5653
+ toA--;
5654
+ toB--;
5655
+ }
5656
+ if (preferredSide == "end") {
5657
+ let adjust = Math.max(0, from - Math.min(toA, toB));
5658
+ preferredPos -= toA + adjust - from;
5659
+ }
5660
+ if (toA < from && a.length < b.length) {
5661
+ let move = preferredPos <= from && preferredPos >= toA ? from - preferredPos : 0;
5662
+ from -= move;
5663
+ toB = from + (toB - toA);
5664
+ toA = from;
5665
+ }
5666
+ else if (toB < from) {
5667
+ let move = preferredPos <= from && preferredPos >= toB ? from - preferredPos : 0;
5668
+ from -= move;
5669
+ toA = from + (toA - toB);
5670
+ toB = from;
5671
+ }
5672
+ return { from, toA, toB };
5673
+ }
5674
+ function selectionPoints(view) {
5675
+ let result = [];
5676
+ if (view.root.activeElement != view.contentDOM)
5677
+ return result;
5678
+ let { anchorNode, anchorOffset, focusNode, focusOffset } = view.observer.selectionRange;
5679
+ if (anchorNode) {
5680
+ result.push(new DOMPoint(anchorNode, anchorOffset));
5681
+ if (focusNode != anchorNode || focusOffset != anchorOffset)
5682
+ result.push(new DOMPoint(focusNode, focusOffset));
5683
+ }
5684
+ return result;
5685
+ }
5686
+ function selectionFromPoints(points, base) {
5687
+ if (points.length == 0)
5688
+ return null;
5689
+ let anchor = points[0].pos, head = points.length == 2 ? points[1].pos : anchor;
5690
+ return anchor > -1 && head > -1 ? EditorSelection.single(anchor + base, head + base) : null;
5691
+ }
5692
+
5448
5693
  const observeOptions = {
5449
5694
  childList: true,
5450
5695
  characterData: true,
@@ -5456,10 +5701,8 @@ const observeOptions = {
5456
5701
  // DOMCharacterDataModified there
5457
5702
  const useCharData = browser.ie && browser.ie_version <= 11;
5458
5703
  class DOMObserver {
5459
- constructor(view, onChange, onScrollChanged) {
5704
+ constructor(view) {
5460
5705
  this.view = view;
5461
- this.onChange = onChange;
5462
- this.onScrollChanged = onScrollChanged;
5463
5706
  this.active = false;
5464
5707
  // The known selection. Kept in our own object, as opposed to just
5465
5708
  // directly accessing the selection because:
@@ -5474,6 +5717,7 @@ class DOMObserver {
5474
5717
  this.resizeTimeout = -1;
5475
5718
  this.queue = [];
5476
5719
  this.delayedAndroidKey = null;
5720
+ this.flushingAndroidKey = -1;
5477
5721
  this.lastChange = 0;
5478
5722
  this.scrollTargets = [];
5479
5723
  this.intersection = null;
@@ -5542,6 +5786,11 @@ class DOMObserver {
5542
5786
  this.listenForScroll();
5543
5787
  this.readSelectionRange();
5544
5788
  }
5789
+ onScrollChanged(e) {
5790
+ this.view.inputState.runScrollHandlers(this.view, e);
5791
+ if (this.intersecting)
5792
+ this.view.measure();
5793
+ }
5545
5794
  onScroll(e) {
5546
5795
  if (this.intersecting)
5547
5796
  this.flush(false);
@@ -5701,14 +5950,17 @@ class DOMObserver {
5701
5950
  // them or, if that has no effect, dispatches the given key.
5702
5951
  delayAndroidKey(key, keyCode) {
5703
5952
  var _a;
5704
- if (!this.delayedAndroidKey)
5705
- this.view.win.requestAnimationFrame(() => {
5953
+ if (!this.delayedAndroidKey) {
5954
+ let flush = () => {
5706
5955
  let key = this.delayedAndroidKey;
5707
- this.delayedAndroidKey = null;
5708
- this.delayedFlush = -1;
5709
- if (!this.flush() && key.force)
5710
- dispatchKey(this.dom, key.key, key.keyCode);
5711
- });
5956
+ if (key) {
5957
+ this.clearDelayedAndroidKey();
5958
+ if (!this.flush() && key.force)
5959
+ dispatchKey(this.dom, key.key, key.keyCode);
5960
+ }
5961
+ };
5962
+ this.flushingAndroidKey = this.view.win.requestAnimationFrame(flush);
5963
+ }
5712
5964
  // Since backspace beforeinput is sometimes signalled spuriously,
5713
5965
  // Enter always takes precedence.
5714
5966
  if (!this.delayedAndroidKey || key == "Enter")
@@ -5721,6 +5973,11 @@ class DOMObserver {
5721
5973
  force: this.lastChange < Date.now() - 50 || !!((_a = this.delayedAndroidKey) === null || _a === void 0 ? void 0 : _a.force)
5722
5974
  };
5723
5975
  }
5976
+ clearDelayedAndroidKey() {
5977
+ this.win.cancelAnimationFrame(this.flushingAndroidKey);
5978
+ this.delayedAndroidKey = null;
5979
+ this.flushingAndroidKey = -1;
5980
+ }
5724
5981
  flushSoon() {
5725
5982
  if (this.delayedFlush < 0)
5726
5983
  this.delayedFlush = this.view.win.requestAnimationFrame(() => { this.delayedFlush = -1; this.flush(); });
@@ -5755,6 +6012,17 @@ class DOMObserver {
5755
6012
  }
5756
6013
  return { from, to, typeOver };
5757
6014
  }
6015
+ readChange() {
6016
+ let { from, to, typeOver } = this.processRecords();
6017
+ let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
6018
+ if (from < 0 && !newSel)
6019
+ return null;
6020
+ if (from > -1)
6021
+ this.lastChange = Date.now();
6022
+ this.view.inputState.lastFocusTime = 0;
6023
+ this.selectionChanged = false;
6024
+ return new DOMChange(this.view, from, to, typeOver);
6025
+ }
5758
6026
  // Apply pending changes, if any
5759
6027
  flush(readSelection = true) {
5760
6028
  // Completely hold off flushing when pending keys are set—the code
@@ -5764,16 +6032,11 @@ class DOMObserver {
5764
6032
  return false;
5765
6033
  if (readSelection)
5766
6034
  this.readSelectionRange();
5767
- let { from, to, typeOver } = this.processRecords();
5768
- let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
5769
- if (from < 0 && !newSel)
6035
+ let domChange = this.readChange();
6036
+ if (!domChange)
5770
6037
  return false;
5771
- if (from > -1)
5772
- this.lastChange = Date.now();
5773
- this.view.inputState.lastFocusTime = 0;
5774
- this.selectionChanged = false;
5775
6038
  let startState = this.view.state;
5776
- let handled = this.onChange(from, to, typeOver);
6039
+ let handled = applyDOMChange(this.view, domChange);
5777
6040
  // The view wasn't updated
5778
6041
  if (this.view.state == startState)
5779
6042
  this.view.update([]);
@@ -5829,6 +6092,8 @@ class DOMObserver {
5829
6092
  this.removeWindowListeners(this.win);
5830
6093
  clearTimeout(this.parentCheck);
5831
6094
  clearTimeout(this.resizeTimeout);
6095
+ this.win.cancelAnimationFrame(this.delayedFlush);
6096
+ this.win.cancelAnimationFrame(this.flushingAndroidKey);
5832
6097
  }
5833
6098
  }
5834
6099
  function findChild(cView, dom, dir) {
@@ -5870,218 +6135,6 @@ function safariSelectionRangeHack(view) {
5870
6135
  return { anchorNode, anchorOffset, focusNode, focusOffset };
5871
6136
  }
5872
6137
 
5873
- function applyDOMChange(view, start, end, typeOver) {
5874
- let change, newSel;
5875
- let sel = view.state.selection.main;
5876
- if (start > -1) {
5877
- let bounds = view.docView.domBoundsAround(start, end, 0);
5878
- if (!bounds || view.state.readOnly)
5879
- return false;
5880
- let { from, to } = bounds;
5881
- let selPoints = view.docView.impreciseHead || view.docView.impreciseAnchor ? [] : selectionPoints(view);
5882
- let reader = new DOMReader(selPoints, view.state);
5883
- reader.readRange(bounds.startDOM, bounds.endDOM);
5884
- let preferredPos = sel.from, preferredSide = null;
5885
- // Prefer anchoring to end when Backspace is pressed (or, on
5886
- // Android, when something was deleted)
5887
- if (view.inputState.lastKeyCode === 8 && view.inputState.lastKeyTime > Date.now() - 100 ||
5888
- browser.android && reader.text.length < to - from) {
5889
- preferredPos = sel.to;
5890
- preferredSide = "end";
5891
- }
5892
- let diff = findDiff(view.state.doc.sliceString(from, to, LineBreakPlaceholder), reader.text, preferredPos - from, preferredSide);
5893
- if (diff) {
5894
- // Chrome inserts two newlines when pressing shift-enter at the
5895
- // end of a line. This drops one of those.
5896
- if (browser.chrome && view.inputState.lastKeyCode == 13 &&
5897
- diff.toB == diff.from + 2 && reader.text.slice(diff.from, diff.toB) == LineBreakPlaceholder + LineBreakPlaceholder)
5898
- diff.toB--;
5899
- change = { from: from + diff.from, to: from + diff.toA,
5900
- insert: Text.of(reader.text.slice(diff.from, diff.toB).split(LineBreakPlaceholder)) };
5901
- }
5902
- newSel = selectionFromPoints(selPoints, from);
5903
- }
5904
- else if (view.hasFocus || !view.state.facet(editable)) {
5905
- let domSel = view.observer.selectionRange;
5906
- let { impreciseHead: iHead, impreciseAnchor: iAnchor } = view.docView;
5907
- let head = iHead && iHead.node == domSel.focusNode && iHead.offset == domSel.focusOffset ||
5908
- !contains(view.contentDOM, domSel.focusNode)
5909
- ? view.state.selection.main.head
5910
- : view.docView.posFromDOM(domSel.focusNode, domSel.focusOffset);
5911
- let anchor = iAnchor && iAnchor.node == domSel.anchorNode && iAnchor.offset == domSel.anchorOffset ||
5912
- !contains(view.contentDOM, domSel.anchorNode)
5913
- ? view.state.selection.main.anchor
5914
- : view.docView.posFromDOM(domSel.anchorNode, domSel.anchorOffset);
5915
- if (head != sel.head || anchor != sel.anchor)
5916
- newSel = EditorSelection.single(anchor, head);
5917
- }
5918
- if (!change && !newSel)
5919
- return false;
5920
- if (!change && typeOver && !sel.empty && newSel && newSel.main.empty) {
5921
- // Heuristic to notice typing over a selected character
5922
- change = { from: sel.from, to: sel.to, insert: view.state.doc.slice(sel.from, sel.to) };
5923
- }
5924
- else if (change && change.from >= sel.from && change.to <= sel.to &&
5925
- (change.from != sel.from || change.to != sel.to) &&
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)
5930
- change = {
5931
- from: sel.from, to: sel.to,
5932
- insert: view.state.doc.slice(sel.from, change.from).append(change.insert).append(view.state.doc.slice(change.to, sel.to))
5933
- };
5934
- }
5935
- else if ((browser.mac || browser.android) && change && change.from == change.to && change.from == sel.head - 1 &&
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);
5941
- change = { from: sel.from, to: sel.to, insert: Text.of([" "]) };
5942
- }
5943
- if (change) {
5944
- let startState = view.state;
5945
- if (browser.ios && view.inputState.flushIOSKey(view))
5946
- return true;
5947
- // Android browsers don't fire reasonable key events for enter,
5948
- // backspace, or delete. So this detects changes that look like
5949
- // they're caused by those keys, and reinterprets them as key
5950
- // events. (Some of these keys are also handled by beforeinput
5951
- // events and the pendingAndroidKey mechanism, but that's not
5952
- // reliable in all situations.)
5953
- if (browser.android &&
5954
- ((change.from == sel.from && change.to == sel.to &&
5955
- change.insert.length == 1 && change.insert.lines == 2 &&
5956
- dispatchKey(view.contentDOM, "Enter", 13)) ||
5957
- (change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 &&
5958
- dispatchKey(view.contentDOM, "Backspace", 8)) ||
5959
- (change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
5960
- dispatchKey(view.contentDOM, "Delete", 46))))
5961
- return true;
5962
- let text = change.insert.toString();
5963
- if (view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text)))
5964
- return true;
5965
- if (view.inputState.composing >= 0)
5966
- view.inputState.composing++;
5967
- let tr;
5968
- if (change.from >= sel.from && change.to <= sel.to && change.to - change.from >= (sel.to - sel.from) / 3 &&
5969
- (!newSel || newSel.main.empty && newSel.main.from == change.from + change.insert.length) &&
5970
- view.inputState.composing < 0) {
5971
- let before = sel.from < change.from ? startState.sliceDoc(sel.from, change.from) : "";
5972
- let after = sel.to > change.to ? startState.sliceDoc(change.to, sel.to) : "";
5973
- tr = startState.replaceSelection(view.state.toText(before + change.insert.sliceString(0, undefined, view.state.lineBreak) + after));
5974
- }
5975
- else {
5976
- let changes = startState.changes(change);
5977
- let mainSel = newSel && !startState.selection.main.eq(newSel.main) && newSel.main.to <= changes.newLength
5978
- ? newSel.main : undefined;
5979
- // Try to apply a composition change to all cursors
5980
- if (startState.selection.ranges.length > 1 && view.inputState.composing >= 0 &&
5981
- change.to <= sel.to && change.to >= sel.to - 10) {
5982
- let replaced = view.state.sliceDoc(change.from, change.to);
5983
- let compositionRange = compositionSurroundingNode(view) || view.state.doc.lineAt(sel.head);
5984
- let offset = sel.to - change.to, size = sel.to - sel.from;
5985
- tr = startState.changeByRange(range => {
5986
- if (range.from == sel.from && range.to == sel.to)
5987
- return { changes, range: mainSel || range.map(changes) };
5988
- let to = range.to - offset, from = to - replaced.length;
5989
- if (range.to - range.from != size || view.state.sliceDoc(from, to) != replaced ||
5990
- // Unfortunately, there's no way to make multiple
5991
- // changes in the same node work without aborting
5992
- // composition, so cursors in the composition range are
5993
- // ignored.
5994
- compositionRange && range.to >= compositionRange.from && range.from <= compositionRange.to)
5995
- return { range };
5996
- let rangeChanges = startState.changes({ from, to, insert: change.insert }), selOff = range.to - sel.to;
5997
- return {
5998
- changes: rangeChanges,
5999
- range: !mainSel ? range.map(rangeChanges) :
6000
- EditorSelection.range(Math.max(0, mainSel.anchor + selOff), Math.max(0, mainSel.head + selOff))
6001
- };
6002
- });
6003
- }
6004
- else {
6005
- tr = {
6006
- changes,
6007
- selection: mainSel && startState.selection.replaceRange(mainSel)
6008
- };
6009
- }
6010
- }
6011
- let userEvent = "input.type";
6012
- if (view.composing) {
6013
- userEvent += ".compose";
6014
- if (view.inputState.compositionFirstChange) {
6015
- userEvent += ".start";
6016
- view.inputState.compositionFirstChange = false;
6017
- }
6018
- }
6019
- view.dispatch(tr, { scrollIntoView: true, userEvent });
6020
- return true;
6021
- }
6022
- else if (newSel && !newSel.main.eq(sel)) {
6023
- let scrollIntoView = false, userEvent = "select";
6024
- if (view.inputState.lastSelectionTime > Date.now() - 50) {
6025
- if (view.inputState.lastSelectionOrigin == "select")
6026
- scrollIntoView = true;
6027
- userEvent = view.inputState.lastSelectionOrigin;
6028
- }
6029
- view.dispatch({ selection: newSel, scrollIntoView, userEvent });
6030
- return true;
6031
- }
6032
- else {
6033
- return false;
6034
- }
6035
- }
6036
- function findDiff(a, b, preferredPos, preferredSide) {
6037
- let minLen = Math.min(a.length, b.length);
6038
- let from = 0;
6039
- while (from < minLen && a.charCodeAt(from) == b.charCodeAt(from))
6040
- from++;
6041
- if (from == minLen && a.length == b.length)
6042
- return null;
6043
- let toA = a.length, toB = b.length;
6044
- while (toA > 0 && toB > 0 && a.charCodeAt(toA - 1) == b.charCodeAt(toB - 1)) {
6045
- toA--;
6046
- toB--;
6047
- }
6048
- if (preferredSide == "end") {
6049
- let adjust = Math.max(0, from - Math.min(toA, toB));
6050
- preferredPos -= toA + adjust - from;
6051
- }
6052
- if (toA < from && a.length < b.length) {
6053
- let move = preferredPos <= from && preferredPos >= toA ? from - preferredPos : 0;
6054
- from -= move;
6055
- toB = from + (toB - toA);
6056
- toA = from;
6057
- }
6058
- else if (toB < from) {
6059
- let move = preferredPos <= from && preferredPos >= toB ? from - preferredPos : 0;
6060
- from -= move;
6061
- toA = from + (toA - toB);
6062
- toB = from;
6063
- }
6064
- return { from, toA, toB };
6065
- }
6066
- function selectionPoints(view) {
6067
- let result = [];
6068
- if (view.root.activeElement != view.contentDOM)
6069
- return result;
6070
- let { anchorNode, anchorOffset, focusNode, focusOffset } = view.observer.selectionRange;
6071
- if (anchorNode) {
6072
- result.push(new DOMPoint(anchorNode, anchorOffset));
6073
- if (focusNode != anchorNode || focusOffset != anchorOffset)
6074
- result.push(new DOMPoint(focusNode, focusOffset));
6075
- }
6076
- return result;
6077
- }
6078
- function selectionFromPoints(points, base) {
6079
- if (points.length == 0)
6080
- return null;
6081
- let anchor = points[0].pos, head = points.length == 2 ? points[1].pos : anchor;
6082
- return anchor > -1 && head > -1 ? EditorSelection.single(anchor + base, head + base) : null;
6083
- }
6084
-
6085
6138
  // The editor's update state machine looks something like this:
6086
6139
  //
6087
6140
  // Idle → Updating ⇆ Idle (unchecked) → Measuring → Idle
@@ -6144,13 +6197,7 @@ class EditorView {
6144
6197
  this.plugins = this.state.facet(viewPlugin).map(spec => new PluginInstance(spec));
6145
6198
  for (let plugin of this.plugins)
6146
6199
  plugin.update(this);
6147
- this.observer = new DOMObserver(this, (from, to, typeOver) => {
6148
- return applyDOMChange(this, from, to, typeOver);
6149
- }, event => {
6150
- this.inputState.runScrollHandlers(this, event);
6151
- if (this.observer.intersecting)
6152
- this.measure();
6153
- });
6200
+ this.observer = new DOMObserver(this);
6154
6201
  this.inputState = new InputState(this);
6155
6202
  this.inputState.ensureHandlers(this, this.plugins);
6156
6203
  this.docView = new DocView(this);
@@ -6234,7 +6281,20 @@ class EditorView {
6234
6281
  this.viewState.state = state;
6235
6282
  return;
6236
6283
  }
6237
- this.observer.clear();
6284
+ // If there was a pending DOM change, eagerly read it and try to
6285
+ // apply it after the given transactions.
6286
+ let pendingKey = this.observer.delayedAndroidKey, domChange = null;
6287
+ if (pendingKey) {
6288
+ this.observer.clearDelayedAndroidKey();
6289
+ domChange = this.observer.readChange();
6290
+ // Only try to apply DOM changes if the transactions didn't
6291
+ // change the doc or selection.
6292
+ if (domChange && !this.state.doc.eq(state.doc) || !this.state.selection.eq(state.selection))
6293
+ domChange = null;
6294
+ }
6295
+ else {
6296
+ this.observer.clear();
6297
+ }
6238
6298
  // When the phrases change, redraw the editor
6239
6299
  if (state.facet(EditorState.phrases) != this.state.facet(EditorState.phrases))
6240
6300
  return this.setState(state);
@@ -6276,6 +6336,10 @@ class EditorView {
6276
6336
  if (!update.empty)
6277
6337
  for (let listener of this.state.facet(updateListener))
6278
6338
  listener(update);
6339
+ if (domChange) {
6340
+ if (!applyDOMChange(this, domChange) && pendingKey.force)
6341
+ dispatchKey(this.contentDOM, pendingKey.key, pendingKey.keyCode);
6342
+ }
6279
6343
  }
6280
6344
  /**
6281
6345
  Reset the view to the given state. (This will cause the entire
@@ -6408,17 +6472,19 @@ class EditorView {
6408
6472
  logException(this.state, e);
6409
6473
  }
6410
6474
  }
6411
- if (this.viewState.scrollTarget) {
6412
- this.docView.scrollIntoView(this.viewState.scrollTarget);
6413
- this.viewState.scrollTarget = null;
6414
- scrolled = true;
6415
- }
6416
- else {
6417
- let diff = this.viewState.lineBlockAt(refBlock.from).top - refBlock.top;
6418
- if (diff > 1 || diff < -1) {
6419
- this.scrollDOM.scrollTop += diff;
6475
+ if (this.viewState.editorHeight) {
6476
+ if (this.viewState.scrollTarget) {
6477
+ this.docView.scrollIntoView(this.viewState.scrollTarget);
6478
+ this.viewState.scrollTarget = null;
6420
6479
  scrolled = true;
6421
6480
  }
6481
+ else {
6482
+ let diff = this.viewState.lineBlockAt(refBlock.from).top - refBlock.top;
6483
+ if (diff > 1 || diff < -1) {
6484
+ this.scrollDOM.scrollTop += diff;
6485
+ scrolled = true;
6486
+ }
6487
+ }
6422
6488
  }
6423
6489
  if (redrawn)
6424
6490
  this.docView.updateSelection(true);
@@ -7824,7 +7890,8 @@ const plugin = /*@__PURE__*/ViewPlugin.fromClass(class {
7824
7890
  this.attrs = { style: "padding-bottom: 1000px" };
7825
7891
  }
7826
7892
  update(update) {
7827
- let height = update.view.viewState.editorHeight - update.view.defaultLineHeight;
7893
+ let { view } = update;
7894
+ let height = view.viewState.editorHeight - view.defaultLineHeight - view.documentPadding.top - 0.5;
7828
7895
  if (height != this.height) {
7829
7896
  this.height = height;
7830
7897
  this.attrs = { style: `padding-bottom: ${height}px` };
@@ -7926,7 +7993,10 @@ function rectangleFor(state, a, b) {
7926
7993
  for (let i = startLine; i <= endLine; i++) {
7927
7994
  let line = state.doc.line(i);
7928
7995
  let start = findColumn(line.text, startCol, state.tabSize, true);
7929
- if (start > -1) {
7996
+ if (start < 0) {
7997
+ ranges.push(EditorSelection.cursor(line.to));
7998
+ }
7999
+ else {
7930
8000
  let end = findColumn(line.text, endCol, state.tabSize);
7931
8001
  ranges.push(EditorSelection.range(line.from + start, line.from + end));
7932
8002
  }
@@ -8038,6 +8108,7 @@ class TooltipViewManager {
8038
8108
  this.tooltipViews = this.tooltips.map(createTooltipView);
8039
8109
  }
8040
8110
  update(update) {
8111
+ var _a;
8041
8112
  let input = update.state.facet(this.facet);
8042
8113
  let tooltips = input.filter(x => x);
8043
8114
  if (input === this.input) {
@@ -8066,8 +8137,10 @@ class TooltipViewManager {
8066
8137
  }
8067
8138
  }
8068
8139
  for (let t of this.tooltipViews)
8069
- if (tooltipViews.indexOf(t) < 0)
8140
+ if (tooltipViews.indexOf(t) < 0) {
8070
8141
  t.dom.remove();
8142
+ (_a = t.destroy) === null || _a === void 0 ? void 0 : _a.call(t);
8143
+ }
8071
8144
  this.input = input;
8072
8145
  this.tooltips = tooltips;
8073
8146
  this.tooltipViews = tooltipViews;
@@ -8186,11 +8259,13 @@ const tooltipPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
8186
8259
  return tooltipView;
8187
8260
  }
8188
8261
  destroy() {
8189
- var _a;
8262
+ var _a, _b;
8190
8263
  this.view.win.removeEventListener("resize", this.measureSoon);
8191
- for (let { dom } of this.manager.tooltipViews)
8192
- dom.remove();
8193
- (_a = this.intersectionObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
8264
+ for (let tooltipView of this.manager.tooltipViews) {
8265
+ tooltipView.dom.remove();
8266
+ (_a = tooltipView.destroy) === null || _a === void 0 ? void 0 : _a.call(tooltipView);
8267
+ }
8268
+ (_b = this.intersectionObserver) === null || _b === void 0 ? void 0 : _b.disconnect();
8194
8269
  clearTimeout(this.measureTimeout);
8195
8270
  }
8196
8271
  readMeasure() {