@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.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,33 @@ 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
+ let before = null, beforePos = -1, after = null, afterPos = -1;
1058
+ function scan(view, pos) {
1059
+ for (let i = 0, off = 0; i < view.children.length && off <= pos; i++) {
1060
+ let child = view.children[i], end = off + child.length;
1061
+ if (end >= pos) {
1062
+ if (child.children.length) {
1063
+ scan(child, pos - off);
1064
+ }
1065
+ else if (!after && (end > pos || off == end && child.getSide() > 0)) {
1066
+ after = child;
1067
+ afterPos = pos - off;
1068
+ }
1069
+ else if (off < pos || (off == end && child.getSide() < 0)) {
1070
+ before = child;
1071
+ beforePos = pos - off;
1072
+ }
1067
1073
  }
1068
- let rect = child.coordsAt(Math.max(0, pos - off), side);
1069
- return flatten && rect ? flattenRect(rect, side < 0) : rect;
1074
+ off = end;
1070
1075
  }
1071
- off = end;
1072
1076
  }
1077
+ scan(view, pos);
1078
+ let target = (side < 0 ? before : after) || before || after;
1079
+ if (target)
1080
+ return target.coordsAt(Math.max(0, target == before ? beforePos : afterPos), side);
1081
+ return fallbackRect(view);
1082
+ }
1083
+ function fallbackRect(view) {
1073
1084
  let last = view.dom.lastChild;
1074
1085
  if (!last)
1075
1086
  return view.dom.getBoundingClientRect();
@@ -1688,7 +1699,7 @@ class ContentBuilder {
1688
1699
  this.addBlockWidget(new BlockWidgetView(deco.widget || new NullWidget("div"), len, type));
1689
1700
  }
1690
1701
  else {
1691
- let view = WidgetView.create(deco.widget || new NullWidget("span"), len, deco.startSide);
1702
+ let view = WidgetView.create(deco.widget || new NullWidget("span"), len, len ? 0 : deco.startSide);
1692
1703
  let cursorBefore = this.atCursorPos && !view.isEditable && openStart <= active.length && (from < to || deco.startSide > 0);
1693
1704
  let cursorAfter = !view.isEditable && (from < to || deco.startSide <= 0);
1694
1705
  let line = this.getLine();
@@ -4883,7 +4894,7 @@ class ViewState {
4883
4894
  refresh = true;
4884
4895
  if (refresh || oracle.lineWrapping && Math.abs(contentWidth - this.contentDOMWidth) > oracle.charWidth) {
4885
4896
  let { lineHeight, charWidth } = view.docView.measureTextSize();
4886
- refresh = oracle.refresh(whiteSpace, lineHeight, charWidth, contentWidth / charWidth, lineHeights);
4897
+ refresh = lineHeight > 0 && oracle.refresh(whiteSpace, lineHeight, charWidth, contentWidth / charWidth, lineHeights);
4887
4898
  if (refresh) {
4888
4899
  view.docView.minWidth = 0;
4889
4900
  result |= 8 /* UpdateFlag.Geometry */;
@@ -5027,8 +5038,10 @@ class ViewState {
5027
5038
  let marginHeight = (margin / this.heightOracle.lineLength) * this.heightOracle.lineHeight;
5028
5039
  let top, bot;
5029
5040
  if (target != null) {
5030
- top = Math.max(line.from, target - margin);
5031
- bot = Math.min(line.to, target + margin);
5041
+ let targetFrac = findFraction(structure, target);
5042
+ let spaceFrac = ((this.visibleBottom - this.visibleTop) / 2 + marginHeight) / line.height;
5043
+ top = targetFrac - spaceFrac;
5044
+ bot = targetFrac + spaceFrac;
5032
5045
  }
5033
5046
  else {
5034
5047
  top = (this.visibleTop - line.top - marginHeight) / line.height;
@@ -5038,14 +5051,16 @@ class ViewState {
5038
5051
  viewTo = findPosition(structure, bot);
5039
5052
  }
5040
5053
  else {
5054
+ let totalWidth = structure.total * this.heightOracle.charWidth;
5055
+ let marginWidth = margin * this.heightOracle.charWidth;
5041
5056
  let left, right;
5042
5057
  if (target != null) {
5043
- left = Math.max(line.from, target - doubleMargin);
5044
- right = Math.min(line.to, target + doubleMargin);
5058
+ let targetFrac = findFraction(structure, target);
5059
+ let spaceFrac = ((this.pixelViewport.right - this.pixelViewport.left) / 2 + marginWidth) / totalWidth;
5060
+ left = targetFrac - spaceFrac;
5061
+ right = targetFrac + spaceFrac;
5045
5062
  }
5046
5063
  else {
5047
- let totalWidth = structure.total * this.heightOracle.charWidth;
5048
- let marginWidth = margin * this.heightOracle.charWidth;
5049
5064
  left = (this.pixelViewport.left - marginWidth) / totalWidth;
5050
5065
  right = (this.pixelViewport.right + marginWidth) / totalWidth;
5051
5066
  }
@@ -5452,6 +5467,236 @@ const baseTheme$1 = buildTheme("." + baseThemeID, {
5452
5467
  }
5453
5468
  }, lightDarkIDs);
5454
5469
 
5470
+ class DOMChange {
5471
+ constructor(view, start, end, typeOver) {
5472
+ this.typeOver = typeOver;
5473
+ this.bounds = null;
5474
+ this.text = "";
5475
+ let { impreciseHead: iHead, impreciseAnchor: iAnchor } = view.docView;
5476
+ if (start > -1 && !view.state.readOnly && (this.bounds = view.docView.domBoundsAround(start, end, 0))) {
5477
+ let selPoints = iHead || iAnchor ? [] : selectionPoints(view);
5478
+ let reader = new DOMReader(selPoints, view.state);
5479
+ reader.readRange(this.bounds.startDOM, this.bounds.endDOM);
5480
+ this.text = reader.text;
5481
+ this.newSel = selectionFromPoints(selPoints, this.bounds.from);
5482
+ }
5483
+ else {
5484
+ let domSel = view.observer.selectionRange;
5485
+ let head = iHead && iHead.node == domSel.focusNode && iHead.offset == domSel.focusOffset ||
5486
+ !contains(view.contentDOM, domSel.focusNode)
5487
+ ? view.state.selection.main.head
5488
+ : view.docView.posFromDOM(domSel.focusNode, domSel.focusOffset);
5489
+ let anchor = iAnchor && iAnchor.node == domSel.anchorNode && iAnchor.offset == domSel.anchorOffset ||
5490
+ !contains(view.contentDOM, domSel.anchorNode)
5491
+ ? view.state.selection.main.anchor
5492
+ : view.docView.posFromDOM(domSel.anchorNode, domSel.anchorOffset);
5493
+ this.newSel = state.EditorSelection.single(anchor, head);
5494
+ }
5495
+ }
5496
+ }
5497
+ function applyDOMChange(view, domChange) {
5498
+ let change;
5499
+ let { newSel } = domChange, sel = view.state.selection.main;
5500
+ if (domChange.bounds) {
5501
+ let { from, to } = domChange.bounds;
5502
+ let preferredPos = sel.from, preferredSide = null;
5503
+ // Prefer anchoring to end when Backspace is pressed (or, on
5504
+ // Android, when something was deleted)
5505
+ if (view.inputState.lastKeyCode === 8 && view.inputState.lastKeyTime > Date.now() - 100 ||
5506
+ browser.android && domChange.text.length < to - from) {
5507
+ preferredPos = sel.to;
5508
+ preferredSide = "end";
5509
+ }
5510
+ let diff = findDiff(view.state.doc.sliceString(from, to, LineBreakPlaceholder), domChange.text, preferredPos - from, preferredSide);
5511
+ if (diff) {
5512
+ // Chrome inserts two newlines when pressing shift-enter at the
5513
+ // end of a line. DomChange drops one of those.
5514
+ if (browser.chrome && view.inputState.lastKeyCode == 13 &&
5515
+ diff.toB == diff.from + 2 && domChange.text.slice(diff.from, diff.toB) == LineBreakPlaceholder + LineBreakPlaceholder)
5516
+ diff.toB--;
5517
+ change = { from: from + diff.from, to: from + diff.toA,
5518
+ insert: state.Text.of(domChange.text.slice(diff.from, diff.toB).split(LineBreakPlaceholder)) };
5519
+ }
5520
+ }
5521
+ else if (newSel && (!view.hasFocus || !view.state.facet(editable) || newSel.main.eq(sel))) {
5522
+ newSel = null;
5523
+ }
5524
+ if (!change && !newSel)
5525
+ return false;
5526
+ if (!change && domChange.typeOver && !sel.empty && newSel && newSel.main.empty) {
5527
+ // Heuristic to notice typing over a selected character
5528
+ change = { from: sel.from, to: sel.to, insert: view.state.doc.slice(sel.from, sel.to) };
5529
+ }
5530
+ else if (change && change.from >= sel.from && change.to <= sel.to &&
5531
+ (change.from != sel.from || change.to != sel.to) &&
5532
+ (sel.to - sel.from) - (change.to - change.from) <= 4) {
5533
+ // If the change is inside the selection and covers most of it,
5534
+ // assume it is a selection replace (with identical characters at
5535
+ // the start/end not included in the diff)
5536
+ change = {
5537
+ from: sel.from, to: sel.to,
5538
+ insert: view.state.doc.slice(sel.from, change.from).append(change.insert).append(view.state.doc.slice(change.to, sel.to))
5539
+ };
5540
+ }
5541
+ else if ((browser.mac || browser.android) && change && change.from == change.to && change.from == sel.head - 1 &&
5542
+ /^\. ?$/.test(change.insert.toString())) {
5543
+ // Detect insert-period-on-double-space Mac and Android behavior,
5544
+ // and transform it into a regular space insert.
5545
+ if (newSel && change.insert.length == 2)
5546
+ newSel = state.EditorSelection.single(newSel.main.anchor - 1, newSel.main.head - 1);
5547
+ change = { from: sel.from, to: sel.to, insert: state.Text.of([" "]) };
5548
+ }
5549
+ else if (browser.chrome && change && change.from == change.to && change.from == sel.head &&
5550
+ change.insert.toString() == "\n " && view.lineWrapping) {
5551
+ // In Chrome, if you insert a space at the start of a wrapped
5552
+ // line, it will actually insert a newline and a space, causing a
5553
+ // bogus new line to be created in CodeMirror (#968)
5554
+ if (newSel)
5555
+ newSel = state.EditorSelection.single(newSel.main.anchor - 1, newSel.main.head - 1);
5556
+ change = { from: sel.from, to: sel.to, insert: state.Text.of([" "]) };
5557
+ }
5558
+ if (change) {
5559
+ let startState = view.state;
5560
+ if (browser.ios && view.inputState.flushIOSKey(view))
5561
+ return true;
5562
+ // Android browsers don't fire reasonable key events for enter,
5563
+ // backspace, or delete. So this detects changes that look like
5564
+ // they're caused by those keys, and reinterprets them as key
5565
+ // events. (Some of these keys are also handled by beforeinput
5566
+ // events and the pendingAndroidKey mechanism, but that's not
5567
+ // reliable in all situations.)
5568
+ if (browser.android &&
5569
+ ((change.from == sel.from && change.to == sel.to &&
5570
+ change.insert.length == 1 && change.insert.lines == 2 &&
5571
+ dispatchKey(view.contentDOM, "Enter", 13)) ||
5572
+ (change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 &&
5573
+ dispatchKey(view.contentDOM, "Backspace", 8)) ||
5574
+ (change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
5575
+ dispatchKey(view.contentDOM, "Delete", 46))))
5576
+ return true;
5577
+ let text = change.insert.toString();
5578
+ if (view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text)))
5579
+ return true;
5580
+ if (view.inputState.composing >= 0)
5581
+ view.inputState.composing++;
5582
+ let tr;
5583
+ if (change.from >= sel.from && change.to <= sel.to && change.to - change.from >= (sel.to - sel.from) / 3 &&
5584
+ (!newSel || newSel.main.empty && newSel.main.from == change.from + change.insert.length) &&
5585
+ view.inputState.composing < 0) {
5586
+ let before = sel.from < change.from ? startState.sliceDoc(sel.from, change.from) : "";
5587
+ let after = sel.to > change.to ? startState.sliceDoc(change.to, sel.to) : "";
5588
+ tr = startState.replaceSelection(view.state.toText(before + change.insert.sliceString(0, undefined, view.state.lineBreak) + after));
5589
+ }
5590
+ else {
5591
+ let changes = startState.changes(change);
5592
+ let mainSel = newSel && !startState.selection.main.eq(newSel.main) && newSel.main.to <= changes.newLength
5593
+ ? newSel.main : undefined;
5594
+ // Try to apply a composition change to all cursors
5595
+ if (startState.selection.ranges.length > 1 && view.inputState.composing >= 0 &&
5596
+ change.to <= sel.to && change.to >= sel.to - 10) {
5597
+ let replaced = view.state.sliceDoc(change.from, change.to);
5598
+ let compositionRange = compositionSurroundingNode(view) || view.state.doc.lineAt(sel.head);
5599
+ let offset = sel.to - change.to, size = sel.to - sel.from;
5600
+ tr = startState.changeByRange(range => {
5601
+ if (range.from == sel.from && range.to == sel.to)
5602
+ return { changes, range: mainSel || range.map(changes) };
5603
+ let to = range.to - offset, from = to - replaced.length;
5604
+ if (range.to - range.from != size || view.state.sliceDoc(from, to) != replaced ||
5605
+ // Unfortunately, there's no way to make multiple
5606
+ // changes in the same node work without aborting
5607
+ // composition, so cursors in the composition range are
5608
+ // ignored.
5609
+ compositionRange && range.to >= compositionRange.from && range.from <= compositionRange.to)
5610
+ return { range };
5611
+ let rangeChanges = startState.changes({ from, to, insert: change.insert }), selOff = range.to - sel.to;
5612
+ return {
5613
+ changes: rangeChanges,
5614
+ range: !mainSel ? range.map(rangeChanges) :
5615
+ state.EditorSelection.range(Math.max(0, mainSel.anchor + selOff), Math.max(0, mainSel.head + selOff))
5616
+ };
5617
+ });
5618
+ }
5619
+ else {
5620
+ tr = {
5621
+ changes,
5622
+ selection: mainSel && startState.selection.replaceRange(mainSel)
5623
+ };
5624
+ }
5625
+ }
5626
+ let userEvent = "input.type";
5627
+ if (view.composing) {
5628
+ userEvent += ".compose";
5629
+ if (view.inputState.compositionFirstChange) {
5630
+ userEvent += ".start";
5631
+ view.inputState.compositionFirstChange = false;
5632
+ }
5633
+ }
5634
+ view.dispatch(tr, { scrollIntoView: true, userEvent });
5635
+ return true;
5636
+ }
5637
+ else if (newSel && !newSel.main.eq(sel)) {
5638
+ let scrollIntoView = false, userEvent = "select";
5639
+ if (view.inputState.lastSelectionTime > Date.now() - 50) {
5640
+ if (view.inputState.lastSelectionOrigin == "select")
5641
+ scrollIntoView = true;
5642
+ userEvent = view.inputState.lastSelectionOrigin;
5643
+ }
5644
+ view.dispatch({ selection: newSel, scrollIntoView, userEvent });
5645
+ return true;
5646
+ }
5647
+ else {
5648
+ return false;
5649
+ }
5650
+ }
5651
+ function findDiff(a, b, preferredPos, preferredSide) {
5652
+ let minLen = Math.min(a.length, b.length);
5653
+ let from = 0;
5654
+ while (from < minLen && a.charCodeAt(from) == b.charCodeAt(from))
5655
+ from++;
5656
+ if (from == minLen && a.length == b.length)
5657
+ return null;
5658
+ let toA = a.length, toB = b.length;
5659
+ while (toA > 0 && toB > 0 && a.charCodeAt(toA - 1) == b.charCodeAt(toB - 1)) {
5660
+ toA--;
5661
+ toB--;
5662
+ }
5663
+ if (preferredSide == "end") {
5664
+ let adjust = Math.max(0, from - Math.min(toA, toB));
5665
+ preferredPos -= toA + adjust - from;
5666
+ }
5667
+ if (toA < from && a.length < b.length) {
5668
+ let move = preferredPos <= from && preferredPos >= toA ? from - preferredPos : 0;
5669
+ from -= move;
5670
+ toB = from + (toB - toA);
5671
+ toA = from;
5672
+ }
5673
+ else if (toB < from) {
5674
+ let move = preferredPos <= from && preferredPos >= toB ? from - preferredPos : 0;
5675
+ from -= move;
5676
+ toA = from + (toA - toB);
5677
+ toB = from;
5678
+ }
5679
+ return { from, toA, toB };
5680
+ }
5681
+ function selectionPoints(view) {
5682
+ let result = [];
5683
+ if (view.root.activeElement != view.contentDOM)
5684
+ return result;
5685
+ let { anchorNode, anchorOffset, focusNode, focusOffset } = view.observer.selectionRange;
5686
+ if (anchorNode) {
5687
+ result.push(new DOMPoint(anchorNode, anchorOffset));
5688
+ if (focusNode != anchorNode || focusOffset != anchorOffset)
5689
+ result.push(new DOMPoint(focusNode, focusOffset));
5690
+ }
5691
+ return result;
5692
+ }
5693
+ function selectionFromPoints(points, base) {
5694
+ if (points.length == 0)
5695
+ return null;
5696
+ let anchor = points[0].pos, head = points.length == 2 ? points[1].pos : anchor;
5697
+ return anchor > -1 && head > -1 ? state.EditorSelection.single(anchor + base, head + base) : null;
5698
+ }
5699
+
5455
5700
  const observeOptions = {
5456
5701
  childList: true,
5457
5702
  characterData: true,
@@ -5463,10 +5708,8 @@ const observeOptions = {
5463
5708
  // DOMCharacterDataModified there
5464
5709
  const useCharData = browser.ie && browser.ie_version <= 11;
5465
5710
  class DOMObserver {
5466
- constructor(view, onChange, onScrollChanged) {
5711
+ constructor(view) {
5467
5712
  this.view = view;
5468
- this.onChange = onChange;
5469
- this.onScrollChanged = onScrollChanged;
5470
5713
  this.active = false;
5471
5714
  // The known selection. Kept in our own object, as opposed to just
5472
5715
  // directly accessing the selection because:
@@ -5481,6 +5724,7 @@ class DOMObserver {
5481
5724
  this.resizeTimeout = -1;
5482
5725
  this.queue = [];
5483
5726
  this.delayedAndroidKey = null;
5727
+ this.flushingAndroidKey = -1;
5484
5728
  this.lastChange = 0;
5485
5729
  this.scrollTargets = [];
5486
5730
  this.intersection = null;
@@ -5549,6 +5793,11 @@ class DOMObserver {
5549
5793
  this.listenForScroll();
5550
5794
  this.readSelectionRange();
5551
5795
  }
5796
+ onScrollChanged(e) {
5797
+ this.view.inputState.runScrollHandlers(this.view, e);
5798
+ if (this.intersecting)
5799
+ this.view.measure();
5800
+ }
5552
5801
  onScroll(e) {
5553
5802
  if (this.intersecting)
5554
5803
  this.flush(false);
@@ -5708,14 +5957,17 @@ class DOMObserver {
5708
5957
  // them or, if that has no effect, dispatches the given key.
5709
5958
  delayAndroidKey(key, keyCode) {
5710
5959
  var _a;
5711
- if (!this.delayedAndroidKey)
5712
- this.view.win.requestAnimationFrame(() => {
5960
+ if (!this.delayedAndroidKey) {
5961
+ let flush = () => {
5713
5962
  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
- });
5963
+ if (key) {
5964
+ this.clearDelayedAndroidKey();
5965
+ if (!this.flush() && key.force)
5966
+ dispatchKey(this.dom, key.key, key.keyCode);
5967
+ }
5968
+ };
5969
+ this.flushingAndroidKey = this.view.win.requestAnimationFrame(flush);
5970
+ }
5719
5971
  // Since backspace beforeinput is sometimes signalled spuriously,
5720
5972
  // Enter always takes precedence.
5721
5973
  if (!this.delayedAndroidKey || key == "Enter")
@@ -5728,6 +5980,11 @@ class DOMObserver {
5728
5980
  force: this.lastChange < Date.now() - 50 || !!((_a = this.delayedAndroidKey) === null || _a === void 0 ? void 0 : _a.force)
5729
5981
  };
5730
5982
  }
5983
+ clearDelayedAndroidKey() {
5984
+ this.win.cancelAnimationFrame(this.flushingAndroidKey);
5985
+ this.delayedAndroidKey = null;
5986
+ this.flushingAndroidKey = -1;
5987
+ }
5731
5988
  flushSoon() {
5732
5989
  if (this.delayedFlush < 0)
5733
5990
  this.delayedFlush = this.view.win.requestAnimationFrame(() => { this.delayedFlush = -1; this.flush(); });
@@ -5762,6 +6019,17 @@ class DOMObserver {
5762
6019
  }
5763
6020
  return { from, to, typeOver };
5764
6021
  }
6022
+ readChange() {
6023
+ let { from, to, typeOver } = this.processRecords();
6024
+ let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
6025
+ if (from < 0 && !newSel)
6026
+ return null;
6027
+ if (from > -1)
6028
+ this.lastChange = Date.now();
6029
+ this.view.inputState.lastFocusTime = 0;
6030
+ this.selectionChanged = false;
6031
+ return new DOMChange(this.view, from, to, typeOver);
6032
+ }
5765
6033
  // Apply pending changes, if any
5766
6034
  flush(readSelection = true) {
5767
6035
  // Completely hold off flushing when pending keys are set—the code
@@ -5771,16 +6039,11 @@ class DOMObserver {
5771
6039
  return false;
5772
6040
  if (readSelection)
5773
6041
  this.readSelectionRange();
5774
- let { from, to, typeOver } = this.processRecords();
5775
- let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
5776
- if (from < 0 && !newSel)
6042
+ let domChange = this.readChange();
6043
+ if (!domChange)
5777
6044
  return false;
5778
- if (from > -1)
5779
- this.lastChange = Date.now();
5780
- this.view.inputState.lastFocusTime = 0;
5781
- this.selectionChanged = false;
5782
6045
  let startState = this.view.state;
5783
- let handled = this.onChange(from, to, typeOver);
6046
+ let handled = applyDOMChange(this.view, domChange);
5784
6047
  // The view wasn't updated
5785
6048
  if (this.view.state == startState)
5786
6049
  this.view.update([]);
@@ -5836,6 +6099,8 @@ class DOMObserver {
5836
6099
  this.removeWindowListeners(this.win);
5837
6100
  clearTimeout(this.parentCheck);
5838
6101
  clearTimeout(this.resizeTimeout);
6102
+ this.win.cancelAnimationFrame(this.delayedFlush);
6103
+ this.win.cancelAnimationFrame(this.flushingAndroidKey);
5839
6104
  }
5840
6105
  }
5841
6106
  function findChild(cView, dom, dir) {
@@ -5877,218 +6142,6 @@ function safariSelectionRangeHack(view) {
5877
6142
  return { anchorNode, anchorOffset, focusNode, focusOffset };
5878
6143
  }
5879
6144
 
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
6145
  // The editor's update state machine looks something like this:
6093
6146
  //
6094
6147
  // Idle → Updating ⇆ Idle (unchecked) → Measuring → Idle
@@ -6151,13 +6204,7 @@ class EditorView {
6151
6204
  this.plugins = this.state.facet(viewPlugin).map(spec => new PluginInstance(spec));
6152
6205
  for (let plugin of this.plugins)
6153
6206
  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
- });
6207
+ this.observer = new DOMObserver(this);
6161
6208
  this.inputState = new InputState(this);
6162
6209
  this.inputState.ensureHandlers(this, this.plugins);
6163
6210
  this.docView = new DocView(this);
@@ -6241,7 +6288,20 @@ class EditorView {
6241
6288
  this.viewState.state = state$1;
6242
6289
  return;
6243
6290
  }
6244
- this.observer.clear();
6291
+ // If there was a pending DOM change, eagerly read it and try to
6292
+ // apply it after the given transactions.
6293
+ let pendingKey = this.observer.delayedAndroidKey, domChange = null;
6294
+ if (pendingKey) {
6295
+ this.observer.clearDelayedAndroidKey();
6296
+ domChange = this.observer.readChange();
6297
+ // Only try to apply DOM changes if the transactions didn't
6298
+ // change the doc or selection.
6299
+ if (domChange && !this.state.doc.eq(state$1.doc) || !this.state.selection.eq(state$1.selection))
6300
+ domChange = null;
6301
+ }
6302
+ else {
6303
+ this.observer.clear();
6304
+ }
6245
6305
  // When the phrases change, redraw the editor
6246
6306
  if (state$1.facet(state.EditorState.phrases) != this.state.facet(state.EditorState.phrases))
6247
6307
  return this.setState(state$1);
@@ -6283,6 +6343,10 @@ class EditorView {
6283
6343
  if (!update.empty)
6284
6344
  for (let listener of this.state.facet(updateListener))
6285
6345
  listener(update);
6346
+ if (domChange) {
6347
+ if (!applyDOMChange(this, domChange) && pendingKey.force)
6348
+ dispatchKey(this.contentDOM, pendingKey.key, pendingKey.keyCode);
6349
+ }
6286
6350
  }
6287
6351
  /**
6288
6352
  Reset the view to the given state. (This will cause the entire
@@ -6415,17 +6479,19 @@ class EditorView {
6415
6479
  logException(this.state, e);
6416
6480
  }
6417
6481
  }
6418
- if (this.viewState.scrollTarget) {
6419
- this.docView.scrollIntoView(this.viewState.scrollTarget);
6420
- this.viewState.scrollTarget = null;
6421
- scrolled = true;
6422
- }
6423
- else {
6424
- let diff = this.viewState.lineBlockAt(refBlock.from).top - refBlock.top;
6425
- if (diff > 1 || diff < -1) {
6426
- this.scrollDOM.scrollTop += diff;
6482
+ if (this.viewState.editorHeight) {
6483
+ if (this.viewState.scrollTarget) {
6484
+ this.docView.scrollIntoView(this.viewState.scrollTarget);
6485
+ this.viewState.scrollTarget = null;
6427
6486
  scrolled = true;
6428
6487
  }
6488
+ else {
6489
+ let diff = this.viewState.lineBlockAt(refBlock.from).top - refBlock.top;
6490
+ if (diff > 1 || diff < -1) {
6491
+ this.scrollDOM.scrollTop += diff;
6492
+ scrolled = true;
6493
+ }
6494
+ }
6429
6495
  }
6430
6496
  if (redrawn)
6431
6497
  this.docView.updateSelection(true);
@@ -7831,7 +7897,8 @@ const plugin = ViewPlugin.fromClass(class {
7831
7897
  this.attrs = { style: "padding-bottom: 1000px" };
7832
7898
  }
7833
7899
  update(update) {
7834
- let height = update.view.viewState.editorHeight - update.view.defaultLineHeight;
7900
+ let { view } = update;
7901
+ let height = view.viewState.editorHeight - view.defaultLineHeight - view.documentPadding.top - 0.5;
7835
7902
  if (height != this.height) {
7836
7903
  this.height = height;
7837
7904
  this.attrs = { style: `padding-bottom: ${height}px` };
@@ -7933,7 +8000,10 @@ function rectangleFor(state$1, a, b) {
7933
8000
  for (let i = startLine; i <= endLine; i++) {
7934
8001
  let line = state$1.doc.line(i);
7935
8002
  let start = state.findColumn(line.text, startCol, state$1.tabSize, true);
7936
- if (start > -1) {
8003
+ if (start < 0) {
8004
+ ranges.push(state.EditorSelection.cursor(line.to));
8005
+ }
8006
+ else {
7937
8007
  let end = state.findColumn(line.text, endCol, state$1.tabSize);
7938
8008
  ranges.push(state.EditorSelection.range(line.from + start, line.from + end));
7939
8009
  }
@@ -8045,6 +8115,7 @@ class TooltipViewManager {
8045
8115
  this.tooltipViews = this.tooltips.map(createTooltipView);
8046
8116
  }
8047
8117
  update(update) {
8118
+ var _a;
8048
8119
  let input = update.state.facet(this.facet);
8049
8120
  let tooltips = input.filter(x => x);
8050
8121
  if (input === this.input) {
@@ -8073,8 +8144,10 @@ class TooltipViewManager {
8073
8144
  }
8074
8145
  }
8075
8146
  for (let t of this.tooltipViews)
8076
- if (tooltipViews.indexOf(t) < 0)
8147
+ if (tooltipViews.indexOf(t) < 0) {
8077
8148
  t.dom.remove();
8149
+ (_a = t.destroy) === null || _a === void 0 ? void 0 : _a.call(t);
8150
+ }
8078
8151
  this.input = input;
8079
8152
  this.tooltips = tooltips;
8080
8153
  this.tooltipViews = tooltipViews;
@@ -8193,11 +8266,13 @@ const tooltipPlugin = ViewPlugin.fromClass(class {
8193
8266
  return tooltipView;
8194
8267
  }
8195
8268
  destroy() {
8196
- var _a;
8269
+ var _a, _b;
8197
8270
  this.view.win.removeEventListener("resize", this.measureSoon);
8198
- for (let { dom } of this.manager.tooltipViews)
8199
- dom.remove();
8200
- (_a = this.intersectionObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
8271
+ for (let tooltipView of this.manager.tooltipViews) {
8272
+ tooltipView.dom.remove();
8273
+ (_a = tooltipView.destroy) === null || _a === void 0 ? void 0 : _a.call(tooltipView);
8274
+ }
8275
+ (_b = this.intersectionObserver) === null || _b === void 0 ? void 0 : _b.disconnect();
8201
8276
  clearTimeout(this.measureTimeout);
8202
8277
  }
8203
8278
  readMeasure() {