@codemirror/view 0.19.18 → 0.19.22

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
@@ -183,7 +183,7 @@ function scrollRectIntoView(dom, rect, side, center) {
183
183
  }
184
184
  }
185
185
  }
186
- class DOMSelection {
186
+ class DOMSelectionState {
187
187
  constructor() {
188
188
  this.anchorNode = null;
189
189
  this.anchorOffset = 0;
@@ -194,11 +194,14 @@ class DOMSelection {
194
194
  return this.anchorNode == domSel.anchorNode && this.anchorOffset == domSel.anchorOffset &&
195
195
  this.focusNode == domSel.focusNode && this.focusOffset == domSel.focusOffset;
196
196
  }
197
- set(domSel) {
198
- this.anchorNode = domSel.anchorNode;
199
- this.anchorOffset = domSel.anchorOffset;
200
- this.focusNode = domSel.focusNode;
201
- this.focusOffset = domSel.focusOffset;
197
+ setRange(range) {
198
+ this.set(range.anchorNode, range.anchorOffset, range.focusNode, range.focusOffset);
199
+ }
200
+ set(anchorNode, anchorOffset, focusNode, focusOffset) {
201
+ this.anchorNode = anchorNode;
202
+ this.anchorOffset = anchorOffset;
203
+ this.focusNode = focusNode;
204
+ this.focusOffset = focusOffset;
202
205
  }
203
206
  }
204
207
  let preventScrollSupported = null;
@@ -1767,11 +1770,17 @@ class PluginInstance {
1767
1770
  }
1768
1771
  }
1769
1772
  PluginInstance.dummy = /*@__PURE__*/new PluginInstance(/*@__PURE__*/ViewPlugin.define(() => ({})));
1773
+ function combineFacetAttrs(values) {
1774
+ let result = {};
1775
+ for (let i = values.length - 1; i >= 0; i--)
1776
+ combineAttrs(values[i], result);
1777
+ return result;
1778
+ }
1770
1779
  const editorAttributes = /*@__PURE__*/Facet.define({
1771
- combine: values => values.reduce((a, b) => combineAttrs(b, a), {})
1780
+ combine: combineFacetAttrs
1772
1781
  });
1773
1782
  const contentAttributes = /*@__PURE__*/Facet.define({
1774
- combine: values => values.reduce((a, b) => combineAttrs(b, a), {})
1783
+ combine: combineFacetAttrs
1775
1784
  });
1776
1785
  // Provide decorations
1777
1786
  const decorations = /*@__PURE__*/Facet.define();
@@ -1933,6 +1942,10 @@ class DocView extends ContentView {
1933
1942
  // we don't mess it up when reading it back it
1934
1943
  this.impreciseAnchor = null;
1935
1944
  this.impreciseHead = null;
1945
+ this.forceSelection = false;
1946
+ // Used by the resize observer to ignore resizes that we caused
1947
+ // ourselves
1948
+ this.lastUpdate = Date.now();
1936
1949
  this.setDOM(view.contentDOM);
1937
1950
  this.children = [new LineView];
1938
1951
  this.children[0].setParent(this);
@@ -1965,21 +1978,19 @@ class DocView extends ContentView {
1965
1978
  // getSelection than the one that it actually shows to the user.
1966
1979
  // This forces a selection update when lines are joined to work
1967
1980
  // around that. Issue #54
1968
- let forceSelection = (browser.ie || browser.chrome) && !this.compositionDeco.size && update &&
1969
- update.state.doc.lines != update.startState.doc.lines;
1981
+ if ((browser.ie || browser.chrome) && !this.compositionDeco.size && update &&
1982
+ update.state.doc.lines != update.startState.doc.lines)
1983
+ this.forceSelection = true;
1970
1984
  let prevDeco = this.decorations, deco = this.updateDeco();
1971
1985
  let decoDiff = findChangedDeco(prevDeco, deco, update.changes);
1972
1986
  changedRanges = ChangedRange.extendWithRanges(changedRanges, decoDiff);
1973
- let pointerSel = update.transactions.some(tr => tr.isUserEvent("select.pointer"));
1974
- if (this.dirty == 0 /* Not */ && changedRanges.length == 0 &&
1975
- !(update.flags & 4 /* Viewport */) &&
1976
- update.state.selection.main.from >= this.view.viewport.from &&
1977
- update.state.selection.main.to <= this.view.viewport.to) {
1978
- this.updateSelection(forceSelection, pointerSel);
1987
+ if (this.dirty == 0 /* Not */ && changedRanges.length == 0) {
1979
1988
  return false;
1980
1989
  }
1981
1990
  else {
1982
- this.updateInner(changedRanges, deco, update.startState.doc.length, forceSelection, pointerSel);
1991
+ this.updateInner(changedRanges, deco, update.startState.doc.length);
1992
+ if (update.transactions.length)
1993
+ this.lastUpdate = Date.now();
1983
1994
  return true;
1984
1995
  }
1985
1996
  }
@@ -1987,13 +1998,16 @@ class DocView extends ContentView {
1987
1998
  if (this.dirty) {
1988
1999
  this.view.observer.ignore(() => this.view.docView.sync());
1989
2000
  this.dirty = 0 /* Not */;
2001
+ this.updateSelection(true);
1990
2002
  }
1991
- if (sel)
2003
+ else {
1992
2004
  this.updateSelection();
2005
+ }
1993
2006
  }
1994
2007
  // Used both by update and checkLayout do perform the actual DOM
1995
2008
  // update
1996
- updateInner(changes, deco, oldLength, forceSelection = false, pointerSel = false) {
2009
+ updateInner(changes, deco, oldLength) {
2010
+ this.view.viewState.mustMeasureContent = true;
1997
2011
  this.updateChildren(changes, deco, oldLength);
1998
2012
  let { observer } = this.view;
1999
2013
  observer.ignore(() => {
@@ -2001,7 +2015,7 @@ class DocView extends ContentView {
2001
2015
  // messes with the scroll position during DOM mutation (though
2002
2016
  // no relayout is triggered and I cannot imagine how it can
2003
2017
  // recompute the scroll position without a layout)
2004
- this.dom.style.height = this.view.viewState.domHeight + "px";
2018
+ this.dom.style.height = this.view.viewState.contentHeight + "px";
2005
2019
  this.dom.style.minWidth = this.minWidth ? this.minWidth + "px" : "";
2006
2020
  // Chrome will sometimes, when DOM mutations occur directly
2007
2021
  // around the selection, get confused and report a different
@@ -2011,8 +2025,7 @@ class DocView extends ContentView {
2011
2025
  this.sync(track);
2012
2026
  this.dirty = 0 /* Not */;
2013
2027
  if (track && (track.written || observer.selectionRange.focusNode != track.node))
2014
- forceSelection = true;
2015
- this.updateSelection(forceSelection, pointerSel);
2028
+ this.forceSelection = true;
2016
2029
  this.dom.style.height = "";
2017
2030
  });
2018
2031
  let gaps = [];
@@ -2098,10 +2111,14 @@ class DocView extends ContentView {
2098
2111
  this.replaceChildren(fromI, toI, content);
2099
2112
  }
2100
2113
  // Sync the DOM selection to this.state.selection
2101
- updateSelection(force = false, fromPointer = false) {
2114
+ updateSelection(mustRead = false, fromPointer = false) {
2115
+ if (mustRead)
2116
+ this.view.observer.readSelectionRange();
2102
2117
  if (!(fromPointer || this.mayControlSelection()) ||
2103
2118
  browser.ios && this.view.inputState.rapidCompositionStart)
2104
2119
  return;
2120
+ let force = this.forceSelection;
2121
+ this.forceSelection = false;
2105
2122
  let main = this.view.state.selection.main;
2106
2123
  // FIXME need to handle the case where the selection falls inside a block range
2107
2124
  let anchor = this.domAtPos(main.anchor);
@@ -2283,7 +2300,7 @@ class DocView extends ContentView {
2283
2300
  let next = i == vs.viewports.length ? null : vs.viewports[i];
2284
2301
  let end = next ? next.from - 1 : this.length;
2285
2302
  if (end > pos) {
2286
- let height = vs.lineAt(end, 0).bottom - vs.lineAt(pos, 0).top;
2303
+ let height = vs.lineBlockAt(end).bottom - vs.lineBlockAt(pos).top;
2287
2304
  deco.push(Decoration.replace({ widget: new BlockGapWidget(height), block: true, inclusive: true }).range(pos, end));
2288
2305
  }
2289
2306
  if (!next)
@@ -2889,13 +2906,14 @@ function domPosInText(node, x, y) {
2889
2906
  }
2890
2907
  function posAtCoords(view, { x, y }, precise, bias = -1) {
2891
2908
  var _a;
2892
- let content = view.contentDOM.getBoundingClientRect(), block;
2909
+ let content = view.contentDOM.getBoundingClientRect(), docTop = content.top + view.viewState.paddingTop;
2893
2910
  let halfLine = view.defaultLineHeight / 2;
2911
+ let block, yOffset = y - docTop;
2894
2912
  for (let bounced = false;;) {
2895
- block = view.blockAtHeight(y, content.top);
2896
- if (block.top > y || block.bottom < y) {
2897
- bias = block.top > y ? -1 : 1;
2898
- y = Math.min(block.bottom - halfLine, Math.max(block.top + halfLine, y));
2913
+ block = view.elementAtHeight(yOffset);
2914
+ if (block.top > yOffset || block.bottom < yOffset) {
2915
+ bias = block.top > yOffset ? -1 : 1;
2916
+ yOffset = Math.min(block.bottom - halfLine, Math.max(block.top + halfLine, yOffset));
2899
2917
  if (bounced)
2900
2918
  return precise ? null : 0;
2901
2919
  else
@@ -2903,8 +2921,9 @@ function posAtCoords(view, { x, y }, precise, bias = -1) {
2903
2921
  }
2904
2922
  if (block.type == BlockType.Text)
2905
2923
  break;
2906
- y = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
2924
+ yOffset = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
2907
2925
  }
2926
+ y = docTop + yOffset;
2908
2927
  let lineStart = block.from;
2909
2928
  // Clip x to the viewport sides
2910
2929
  x = Math.max(content.left + 1, Math.min(content.right - 1, x));
@@ -3017,17 +3036,17 @@ function moveVertically(view, start, forward, distance) {
3017
3036
  return EditorSelection.cursor(startPos);
3018
3037
  let goal = start.goalColumn, startY;
3019
3038
  let rect = view.contentDOM.getBoundingClientRect();
3020
- let startCoords = view.coordsAtPos(startPos);
3039
+ let startCoords = view.coordsAtPos(startPos), docTop = view.documentTop;
3021
3040
  if (startCoords) {
3022
3041
  if (goal == null)
3023
3042
  goal = startCoords.left - rect.left;
3024
3043
  startY = dir < 0 ? startCoords.top : startCoords.bottom;
3025
3044
  }
3026
3045
  else {
3027
- let line = view.viewState.lineAt(startPos, view.dom.getBoundingClientRect().top);
3046
+ let line = view.viewState.lineBlockAt(startPos - docTop);
3028
3047
  if (goal == null)
3029
3048
  goal = Math.min(rect.right - rect.left, view.defaultCharacterWidth * (startPos - line.from));
3030
- startY = dir < 0 ? line.top : line.bottom;
3049
+ startY = (dir < 0 ? line.top : line.bottom) + docTop;
3031
3050
  }
3032
3051
  let resolvedGoal = rect.left + goal;
3033
3052
  let dist = distance !== null && distance !== void 0 ? distance : (view.defaultLineHeight >> 1);
@@ -3278,7 +3297,7 @@ class MouseSelection {
3278
3297
  this.extend = startEvent.shiftKey;
3279
3298
  this.multiple = view.state.facet(EditorState.allowMultipleSelections) && addsSelectionRange(view, startEvent);
3280
3299
  this.dragMove = dragMovesSelection(view, startEvent);
3281
- this.dragging = isInPrimarySelection(view, startEvent) ? null : false;
3300
+ this.dragging = isInPrimarySelection(view, startEvent) && getClickType(startEvent) == 1 ? null : false;
3282
3301
  // When clicking outside of the selection, immediately apply the
3283
3302
  // effect of starting the selection
3284
3303
  if (this.dragging === false) {
@@ -3499,7 +3518,7 @@ function basicMouseSelection(view, event) {
3499
3518
  let last = start, lastEvent = event;
3500
3519
  return {
3501
3520
  update(update) {
3502
- if (update.changes) {
3521
+ if (update.docChanged) {
3503
3522
  if (start)
3504
3523
  start.pos = update.changes.mapPos(start.pos);
3505
3524
  startSel = startSel.map(update.changes);
@@ -3763,7 +3782,10 @@ class HeightOracle {
3763
3782
  return lines * this.lineHeight;
3764
3783
  }
3765
3784
  setDoc(doc) { this.doc = doc; return this; }
3766
- mustRefresh(lineHeights, whiteSpace, direction) {
3785
+ mustRefreshForStyle(whiteSpace, direction) {
3786
+ return (wrappingWhiteSpace.indexOf(whiteSpace) > -1) != this.lineWrapping || this.direction != direction;
3787
+ }
3788
+ mustRefreshForHeights(lineHeights) {
3767
3789
  let newHeight = false;
3768
3790
  for (let i = 0; i < lineHeights.length; i++) {
3769
3791
  let h = lineHeights[i];
@@ -3775,7 +3797,7 @@ class HeightOracle {
3775
3797
  this.heightSamples[Math.floor(h * 10)] = true;
3776
3798
  }
3777
3799
  }
3778
- return newHeight || (wrappingWhiteSpace.indexOf(whiteSpace) > -1) != this.lineWrapping || this.direction != direction;
3800
+ return newHeight;
3779
3801
  }
3780
3802
  refresh(whiteSpace, direction, lineHeight, charWidth, lineLength, knownHeights) {
3781
3803
  let lineWrapping = wrappingWhiteSpace.indexOf(whiteSpace) > -1;
@@ -3829,7 +3851,8 @@ class BlockInfo {
3829
3851
  */
3830
3852
  length,
3831
3853
  /**
3832
- The top position of the element.
3854
+ The top position of the element (relative to the top of the
3855
+ document).
3833
3856
  */
3834
3857
  top,
3835
3858
  /**
@@ -3863,13 +3886,19 @@ class BlockInfo {
3863
3886
  .concat(Array.isArray(other.type) ? other.type : [other]);
3864
3887
  return new BlockInfo(this.from, this.length + other.length, this.top, this.height + other.height, detail);
3865
3888
  }
3889
+ /**
3890
+ FIXME remove on next breaking release @internal
3891
+ */
3892
+ moveY(offset) {
3893
+ return !offset ? this : new BlockInfo(this.from, this.length, this.top + offset, this.height, Array.isArray(this.type) ? this.type.map(b => b.moveY(offset)) : this.type);
3894
+ }
3866
3895
  }
3867
3896
  var QueryType = /*@__PURE__*/(function (QueryType) {
3868
3897
  QueryType[QueryType["ByPos"] = 0] = "ByPos";
3869
3898
  QueryType[QueryType["ByHeight"] = 1] = "ByHeight";
3870
3899
  QueryType[QueryType["ByPosNoHeight"] = 2] = "ByPosNoHeight";
3871
3900
  return QueryType})(QueryType || (QueryType = {}));
3872
- const Epsilon = 1e-4;
3901
+ const Epsilon = 1e-3;
3873
3902
  class HeightMap {
3874
3903
  constructor(length, // The number of characters covered
3875
3904
  height, // Height of this part of the document
@@ -4096,22 +4125,30 @@ class HeightMapGap extends HeightMap {
4096
4125
  // can't be widgets or collapsed ranges in those lines, because
4097
4126
  // they would already have been added to the heightmap (gaps
4098
4127
  // only contain plain text).
4099
- let nodes = [], pos = Math.max(offset, measured.from);
4128
+ let nodes = [], pos = Math.max(offset, measured.from), singleHeight = -1;
4129
+ let wasChanged = oracle.heightChanged;
4100
4130
  if (measured.from > offset)
4101
4131
  nodes.push(new HeightMapGap(measured.from - offset - 1).updateHeight(oracle, offset));
4102
4132
  while (pos <= end && measured.more) {
4103
4133
  let len = oracle.doc.lineAt(pos).length;
4104
4134
  if (nodes.length)
4105
4135
  nodes.push(null);
4106
- let line = new HeightMapText(len, measured.heights[measured.index++]);
4136
+ let height = measured.heights[measured.index++];
4137
+ if (singleHeight == -1)
4138
+ singleHeight = height;
4139
+ else if (Math.abs(height - singleHeight) >= Epsilon)
4140
+ singleHeight = -2;
4141
+ let line = new HeightMapText(len, height);
4107
4142
  line.outdated = false;
4108
4143
  nodes.push(line);
4109
4144
  pos += len + 1;
4110
4145
  }
4111
4146
  if (pos <= end)
4112
4147
  nodes.push(null, new HeightMapGap(end - pos).updateHeight(oracle, pos));
4113
- oracle.heightChanged = true;
4114
- return HeightMap.of(nodes);
4148
+ let result = HeightMap.of(nodes);
4149
+ oracle.heightChanged = wasChanged || singleHeight < 0 || Math.abs(result.height - this.height) >= Epsilon ||
4150
+ Math.abs(singleHeight - this.lines(oracle.doc, offset).lineHeight) >= Epsilon;
4151
+ return result;
4115
4152
  }
4116
4153
  else if (force || this.outdated) {
4117
4154
  this.setHeight(oracle, oracle.heightForGap(offset, offset + this.length));
@@ -4469,13 +4506,18 @@ class ViewState {
4469
4506
  this.inView = true;
4470
4507
  this.paddingTop = 0;
4471
4508
  this.paddingBottom = 0;
4472
- this.contentWidth = 0;
4509
+ this.contentDOMWidth = 0;
4510
+ this.contentDOMHeight = 0;
4511
+ this.editorHeight = 0;
4473
4512
  this.heightOracle = new HeightOracle;
4474
4513
  // See VP.MaxDOMHeight
4475
4514
  this.scaler = IdScaler;
4476
4515
  this.scrollTarget = null;
4477
4516
  // Briefly set to true when printing, to disable viewport limiting
4478
4517
  this.printing = false;
4518
+ // Flag set when editor content was redrawn, so that the next
4519
+ // measure stage knows it must read DOM layout
4520
+ this.mustMeasureContent = true;
4479
4521
  this.visibleRanges = [];
4480
4522
  // Cursor 'assoc' is only significant when the cursor is on a line
4481
4523
  // wrap point, where it must stick to the character that it is
@@ -4488,6 +4530,7 @@ class ViewState {
4488
4530
  this.mustEnforceCursorAssoc = false;
4489
4531
  this.heightMap = HeightMap.empty().applyChanges(state.facet(decorations), Text.empty, this.heightOracle.setDoc(state.doc), [new ChangedRange(0, 0, 0, state.doc.length)]);
4490
4532
  this.viewport = this.getViewport(0, null);
4533
+ this.updateViewportLines();
4491
4534
  this.updateForViewport();
4492
4535
  this.lineGaps = this.ensureLineGaps([]);
4493
4536
  this.lineGapDeco = Decoration.set(this.lineGaps.map(gap => gap.draw(false)));
@@ -4498,7 +4541,7 @@ class ViewState {
4498
4541
  for (let i = 0; i <= 1; i++) {
4499
4542
  let pos = i ? main.head : main.anchor;
4500
4543
  if (!viewports.some(({ from, to }) => pos >= from && pos <= to)) {
4501
- let { from, to } = this.lineAt(pos, 0);
4544
+ let { from, to } = this.lineBlockAt(pos);
4502
4545
  viewports.push(new Viewport(from, to));
4503
4546
  }
4504
4547
  }
@@ -4506,6 +4549,12 @@ class ViewState {
4506
4549
  this.scaler = this.heightMap.height <= 7000000 /* MaxDOMHeight */ ? IdScaler :
4507
4550
  new BigScaler(this.heightOracle.doc, this.heightMap, this.viewports);
4508
4551
  }
4552
+ updateViewportLines() {
4553
+ this.viewportLines = [];
4554
+ this.heightMap.forEachLine(this.viewport.from, this.viewport.to, this.state.doc, 0, 0, block => {
4555
+ this.viewportLines.push(this.scaler.scale == 1 ? block : scaleBlock(block, this.scaler));
4556
+ });
4557
+ }
4509
4558
  update(update, scrollTarget = null) {
4510
4559
  let prev = this.state;
4511
4560
  this.state = update.state;
@@ -4520,6 +4569,9 @@ class ViewState {
4520
4569
  if (scrollTarget && (scrollTarget.range.head < viewport.from || scrollTarget.range.head > viewport.to) ||
4521
4570
  !this.viewportIsAppropriate(viewport))
4522
4571
  viewport = this.getViewport(0, scrollTarget);
4572
+ if (!update.changes.empty || (update.flags & 2 /* Height */) ||
4573
+ viewport.from != this.viewport.from || viewport.to != this.viewport.to)
4574
+ this.updateViewportLines();
4523
4575
  this.viewport = viewport;
4524
4576
  this.updateForViewport();
4525
4577
  if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
@@ -4531,13 +4583,17 @@ class ViewState {
4531
4583
  update.state.selection.main.empty && update.state.selection.main.assoc)
4532
4584
  this.mustEnforceCursorAssoc = true;
4533
4585
  }
4534
- measure(docView, repeated) {
4535
- let dom = docView.dom, whiteSpace = "", direction = Direction.LTR;
4536
- let result = 0;
4537
- if (!repeated) {
4586
+ measure(view) {
4587
+ let dom = view.contentDOM, style = window.getComputedStyle(dom);
4588
+ let oracle = this.heightOracle;
4589
+ let whiteSpace = style.whiteSpace, direction = style.direction == "rtl" ? Direction.RTL : Direction.LTR;
4590
+ let refresh = this.heightOracle.mustRefreshForStyle(whiteSpace, direction);
4591
+ let measureContent = refresh || this.mustMeasureContent || this.contentDOMHeight != dom.clientHeight;
4592
+ let result = 0, bias = 0;
4593
+ if (measureContent) {
4594
+ this.mustMeasureContent = false;
4595
+ this.contentDOMHeight = dom.clientHeight;
4538
4596
  // Vertical padding
4539
- let style = window.getComputedStyle(dom);
4540
- whiteSpace = style.whiteSpace, direction = (style.direction == "rtl" ? Direction.RTL : Direction.LTR);
4541
4597
  let paddingTop = parseInt(style.paddingTop) || 0, paddingBottom = parseInt(style.paddingBottom) || 0;
4542
4598
  if (this.paddingTop != paddingTop || this.paddingBottom != paddingBottom) {
4543
4599
  result |= 8 /* Geometry */;
@@ -4552,35 +4608,42 @@ class ViewState {
4552
4608
  this.inView = this.pixelViewport.bottom > this.pixelViewport.top && this.pixelViewport.right > this.pixelViewport.left;
4553
4609
  if (!this.inView)
4554
4610
  return 0;
4555
- let lineHeights = docView.measureVisibleLineHeights();
4556
- let refresh = false, bias = 0, oracle = this.heightOracle;
4557
- if (!repeated) {
4558
- let contentWidth = docView.dom.clientWidth;
4559
- if (oracle.mustRefresh(lineHeights, whiteSpace, direction) ||
4560
- oracle.lineWrapping && Math.abs(contentWidth - this.contentWidth) > oracle.charWidth) {
4561
- let { lineHeight, charWidth } = docView.measureTextSize();
4611
+ if (measureContent) {
4612
+ let lineHeights = view.docView.measureVisibleLineHeights();
4613
+ if (oracle.mustRefreshForHeights(lineHeights))
4614
+ refresh = true;
4615
+ let contentWidth = dom.clientWidth;
4616
+ if (refresh || oracle.lineWrapping && Math.abs(contentWidth - this.contentDOMWidth) > oracle.charWidth) {
4617
+ let { lineHeight, charWidth } = view.docView.measureTextSize();
4562
4618
  refresh = oracle.refresh(whiteSpace, direction, lineHeight, charWidth, contentWidth / charWidth, lineHeights);
4563
4619
  if (refresh) {
4564
- docView.minWidth = 0;
4620
+ view.docView.minWidth = 0;
4565
4621
  result |= 8 /* Geometry */;
4566
4622
  }
4567
4623
  }
4568
- if (this.contentWidth != contentWidth) {
4569
- this.contentWidth = contentWidth;
4624
+ if (this.contentDOMWidth != contentWidth) {
4625
+ this.contentDOMWidth = contentWidth;
4626
+ result |= 8 /* Geometry */;
4627
+ }
4628
+ if (this.editorHeight != view.scrollDOM.clientHeight) {
4629
+ this.editorHeight = view.scrollDOM.clientHeight;
4570
4630
  result |= 8 /* Geometry */;
4571
4631
  }
4572
4632
  if (dTop > 0 && dBottom > 0)
4573
4633
  bias = Math.max(dTop, dBottom);
4574
4634
  else if (dTop < 0 && dBottom < 0)
4575
4635
  bias = Math.min(dTop, dBottom);
4576
- }
4577
- oracle.heightChanged = false;
4578
- this.heightMap = this.heightMap.updateHeight(oracle, 0, refresh, new MeasuredHeights(this.viewport.from, lineHeights));
4579
- if (oracle.heightChanged)
4580
- result |= 2 /* Height */;
4581
- if (!this.viewportIsAppropriate(this.viewport, bias) ||
4582
- this.scrollTarget && (this.scrollTarget.range.head < this.viewport.from || this.scrollTarget.range.head > this.viewport.to))
4636
+ oracle.heightChanged = false;
4637
+ this.heightMap = this.heightMap.updateHeight(oracle, 0, refresh, new MeasuredHeights(this.viewport.from, lineHeights));
4638
+ if (oracle.heightChanged)
4639
+ result |= 2 /* Height */;
4640
+ }
4641
+ let viewportChange = !this.viewportIsAppropriate(this.viewport, bias) ||
4642
+ this.scrollTarget && (this.scrollTarget.range.head < this.viewport.from || this.scrollTarget.range.head > this.viewport.to);
4643
+ if (viewportChange)
4583
4644
  this.viewport = this.getViewport(bias, this.scrollTarget);
4645
+ if ((result & 2 /* Height */) || viewportChange)
4646
+ this.updateViewportLines();
4584
4647
  this.updateForViewport();
4585
4648
  if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
4586
4649
  this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps));
@@ -4591,12 +4654,12 @@ class ViewState {
4591
4654
  // to a line end is going to trigger a layout anyway, so it
4592
4655
  // can't be a pure write. It should be rare that it does any
4593
4656
  // writing.
4594
- docView.enforceCursorAssoc();
4657
+ view.docView.enforceCursorAssoc();
4595
4658
  }
4596
4659
  return result;
4597
4660
  }
4598
- get visibleTop() { return this.scaler.fromDOM(this.pixelViewport.top, 0); }
4599
- get visibleBottom() { return this.scaler.fromDOM(this.pixelViewport.bottom, 0); }
4661
+ get visibleTop() { return this.scaler.fromDOM(this.pixelViewport.top); }
4662
+ get visibleBottom() { return this.scaler.fromDOM(this.pixelViewport.bottom); }
4600
4663
  getViewport(bias, scrollTarget) {
4601
4664
  // This will divide VP.Margin between the top and the
4602
4665
  // bottom, depending on the bias (the change in viewport position
@@ -4656,12 +4719,12 @@ class ViewState {
4656
4719
  // This won't work at all in predominantly right-to-left text.
4657
4720
  if (this.heightOracle.direction != Direction.LTR)
4658
4721
  return gaps;
4659
- this.heightMap.forEachLine(this.viewport.from, this.viewport.to, this.state.doc, 0, 0, line => {
4722
+ for (let line of this.viewportLines) {
4660
4723
  if (line.length < 4000 /* DoubleMargin */)
4661
- return;
4724
+ continue;
4662
4725
  let structure = lineStructure(line.from, line.to, this.state);
4663
4726
  if (structure.total < 4000 /* DoubleMargin */)
4664
- return;
4727
+ continue;
4665
4728
  let viewFrom, viewTo;
4666
4729
  if (this.heightOracle.lineWrapping) {
4667
4730
  let marginHeight = (2000 /* Margin */ / this.heightOracle.lineLength) * this.heightOracle.lineHeight;
@@ -4691,7 +4754,7 @@ class ViewState {
4691
4754
  Math.abs(gap.from - from) < 1000 /* HalfMargin */ && Math.abs(gap.to - to) < 1000 /* HalfMargin */) ||
4692
4755
  new LineGap(from, to, this.gapSize(line, from, to, structure)));
4693
4756
  }
4694
- });
4757
+ }
4695
4758
  return gaps;
4696
4759
  }
4697
4760
  gapSize(line, from, to, structure) {
@@ -4723,27 +4786,18 @@ class ViewState {
4723
4786
  this.visibleRanges = ranges;
4724
4787
  return changed ? 4 /* Viewport */ : 0;
4725
4788
  }
4726
- lineAt(pos, editorTop) {
4727
- editorTop += this.paddingTop;
4728
- return scaleBlock(this.heightMap.lineAt(pos, QueryType.ByPos, this.state.doc, editorTop, 0), this.scaler, editorTop);
4729
- }
4730
- lineAtHeight(height, editorTop) {
4731
- editorTop += this.paddingTop;
4732
- return scaleBlock(this.heightMap.lineAt(this.scaler.fromDOM(height, editorTop), QueryType.ByHeight, this.state.doc, editorTop, 0), this.scaler, editorTop);
4789
+ lineBlockAt(pos) {
4790
+ return (pos >= this.viewport.from && pos <= this.viewport.to && this.viewportLines.find(b => b.from <= pos && b.to <= pos)) ||
4791
+ scaleBlock(this.heightMap.lineAt(pos, QueryType.ByPos, this.state.doc, 0, 0), this.scaler);
4733
4792
  }
4734
- blockAtHeight(height, editorTop) {
4735
- editorTop += this.paddingTop;
4736
- return scaleBlock(this.heightMap.blockAt(this.scaler.fromDOM(height, editorTop), this.state.doc, editorTop, 0), this.scaler, editorTop);
4793
+ lineBlockAtHeight(height) {
4794
+ return scaleBlock(this.heightMap.lineAt(this.scaler.fromDOM(height), QueryType.ByHeight, this.state.doc, 0, 0), this.scaler);
4737
4795
  }
4738
- forEachLine(from, to, f, editorTop) {
4739
- editorTop += this.paddingTop;
4740
- return this.heightMap.forEachLine(from, to, this.state.doc, editorTop, 0, this.scaler.scale == 1 ? f : b => f(scaleBlock(b, this.scaler, editorTop)));
4796
+ elementAtHeight(height) {
4797
+ return scaleBlock(this.heightMap.blockAt(this.scaler.fromDOM(height), this.state.doc, 0, 0), this.scaler);
4741
4798
  }
4742
4799
  get contentHeight() {
4743
- return this.domHeight + this.paddingTop + this.paddingBottom;
4744
- }
4745
- get domHeight() {
4746
- return this.scaler.toDOM(this.heightMap.height, this.paddingTop);
4800
+ return this.scaler.toDOM(this.heightMap.height) + this.paddingTop + this.paddingBottom;
4747
4801
  }
4748
4802
  }
4749
4803
  class Viewport {
@@ -4840,36 +4894,34 @@ class BigScaler {
4840
4894
  base = obj.bottom;
4841
4895
  }
4842
4896
  }
4843
- toDOM(n, top) {
4844
- n -= top;
4897
+ toDOM(n) {
4845
4898
  for (let i = 0, base = 0, domBase = 0;; i++) {
4846
4899
  let vp = i < this.viewports.length ? this.viewports[i] : null;
4847
4900
  if (!vp || n < vp.top)
4848
- return domBase + (n - base) * this.scale + top;
4901
+ return domBase + (n - base) * this.scale;
4849
4902
  if (n <= vp.bottom)
4850
- return vp.domTop + (n - vp.top) + top;
4903
+ return vp.domTop + (n - vp.top);
4851
4904
  base = vp.bottom;
4852
4905
  domBase = vp.domBottom;
4853
4906
  }
4854
4907
  }
4855
- fromDOM(n, top) {
4856
- n -= top;
4908
+ fromDOM(n) {
4857
4909
  for (let i = 0, base = 0, domBase = 0;; i++) {
4858
4910
  let vp = i < this.viewports.length ? this.viewports[i] : null;
4859
4911
  if (!vp || n < vp.domTop)
4860
- return base + (n - domBase) / this.scale + top;
4912
+ return base + (n - domBase) / this.scale;
4861
4913
  if (n <= vp.domBottom)
4862
- return vp.top + (n - vp.domTop) + top;
4914
+ return vp.top + (n - vp.domTop);
4863
4915
  base = vp.bottom;
4864
4916
  domBase = vp.domBottom;
4865
4917
  }
4866
4918
  }
4867
4919
  }
4868
- function scaleBlock(block, scaler, top) {
4920
+ function scaleBlock(block, scaler) {
4869
4921
  if (scaler.scale == 1)
4870
4922
  return block;
4871
- let bTop = scaler.toDOM(block.top, top), bBottom = scaler.toDOM(block.bottom, top);
4872
- return new BlockInfo(block.from, block.length, bTop, bBottom - bTop, Array.isArray(block.type) ? block.type.map(b => scaleBlock(b, scaler, top)) : block.type);
4923
+ let bTop = scaler.toDOM(block.top), bBottom = scaler.toDOM(block.bottom);
4924
+ return new BlockInfo(block.from, block.length, bTop, bBottom - bTop, Array.isArray(block.type) ? block.type.map(b => scaleBlock(b, scaler)) : block.type);
4873
4925
  }
4874
4926
 
4875
4927
  const theme = /*@__PURE__*/Facet.define({ combine: strs => strs.join(" ") });
@@ -5054,24 +5106,30 @@ class DOMObserver {
5054
5106
  this.onChange = onChange;
5055
5107
  this.onScrollChanged = onScrollChanged;
5056
5108
  this.active = false;
5057
- this.ignoreSelection = new DOMSelection;
5109
+ // The known selection. Kept in our own object, as opposed to just
5110
+ // directly accessing the selection because:
5111
+ // - Safari doesn't report the right selection in shadow DOM
5112
+ // - Reading from the selection forces a DOM layout
5113
+ // - This way, we can ignore selectionchange events if we have
5114
+ // already seen the 'new' selection
5115
+ this.selectionRange = new DOMSelectionState;
5116
+ // Set when a selection change is detected, cleared on flush
5117
+ this.selectionChanged = false;
5058
5118
  this.delayedFlush = -1;
5119
+ this.resizeTimeout = -1;
5059
5120
  this.queue = [];
5060
- this.lastFlush = 0;
5061
5121
  this.scrollTargets = [];
5062
5122
  this.intersection = null;
5123
+ this.resize = null;
5063
5124
  this.intersecting = false;
5064
5125
  this.gapIntersection = null;
5065
5126
  this.gaps = [];
5066
- // Used to work around a Safari Selection/shadow DOM bug (#414)
5067
- this._selectionRange = null;
5068
5127
  // Timeout for scheduling check of the parents that need scroll handlers
5069
5128
  this.parentCheck = -1;
5070
5129
  this.dom = view.contentDOM;
5071
5130
  this.observer = new MutationObserver(mutations => {
5072
5131
  for (let mut of mutations)
5073
5132
  this.queue.push(mut);
5074
- this._selectionRange = null;
5075
5133
  // IE11 will sometimes (on typing over a selection or
5076
5134
  // backspacing out a single character text node) call the
5077
5135
  // observer callback before actually updating the DOM.
@@ -5096,6 +5154,16 @@ class DOMObserver {
5096
5154
  this.flushSoon();
5097
5155
  };
5098
5156
  this.onSelectionChange = this.onSelectionChange.bind(this);
5157
+ if (typeof ResizeObserver == "function") {
5158
+ this.resize = new ResizeObserver(() => {
5159
+ if (this.view.docView.lastUpdate < Date.now() - 75 && this.resizeTimeout < 0)
5160
+ this.resizeTimeout = setTimeout(() => {
5161
+ this.resizeTimeout = -1;
5162
+ this.view.requestMeasure();
5163
+ }, 50);
5164
+ });
5165
+ this.resize.observe(view.scrollDOM);
5166
+ }
5099
5167
  this.start();
5100
5168
  this.onScroll = this.onScroll.bind(this);
5101
5169
  window.addEventListener("scroll", this.onScroll);
@@ -5116,10 +5184,12 @@ class DOMObserver {
5116
5184
  }, {});
5117
5185
  }
5118
5186
  this.listenForScroll();
5187
+ this.readSelectionRange();
5188
+ this.dom.ownerDocument.addEventListener("selectionchange", this.onSelectionChange);
5119
5189
  }
5120
5190
  onScroll(e) {
5121
5191
  if (this.intersecting)
5122
- this.flush();
5192
+ this.flush(false);
5123
5193
  this.onScrollChanged(e);
5124
5194
  }
5125
5195
  updateGaps(gaps) {
@@ -5131,8 +5201,8 @@ class DOMObserver {
5131
5201
  }
5132
5202
  }
5133
5203
  onSelectionChange(event) {
5134
- if (this.lastFlush < Date.now() - 50)
5135
- this._selectionRange = null;
5204
+ if (!this.readSelectionRange())
5205
+ return;
5136
5206
  let { view } = this, sel = this.selectionRange;
5137
5207
  if (view.state.facet(editable) ? view.root.activeElement != this.dom : !hasSelection(view.dom, sel))
5138
5208
  return;
@@ -5147,24 +5217,22 @@ class DOMObserver {
5147
5217
  sel.focusNode && isEquivalentPosition(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset))
5148
5218
  this.flushSoon();
5149
5219
  else
5150
- this.flush();
5151
- }
5152
- get selectionRange() {
5153
- if (!this._selectionRange) {
5154
- let { root } = this.view, sel = getSelection(root);
5155
- // The Selection object is broken in shadow roots in Safari. See
5156
- // https://github.com/codemirror/codemirror.next/issues/414
5157
- if (browser.safari && root.nodeType == 11 && deepActiveElement() == this.view.contentDOM)
5158
- sel = safariSelectionRangeHack(this.view) || sel;
5159
- this._selectionRange = sel;
5160
- }
5161
- return this._selectionRange;
5220
+ this.flush(false);
5221
+ }
5222
+ readSelectionRange() {
5223
+ let { root } = this.view, domSel = getSelection(root);
5224
+ // The Selection object is broken in shadow roots in Safari. See
5225
+ // https://github.com/codemirror/codemirror.next/issues/414
5226
+ let range = browser.safari && root.nodeType == 11 && deepActiveElement() == this.view.contentDOM &&
5227
+ safariSelectionRangeHack(this.view) || domSel;
5228
+ if (this.selectionRange.eq(range))
5229
+ return false;
5230
+ this.selectionRange.setRange(range);
5231
+ return this.selectionChanged = true;
5162
5232
  }
5163
5233
  setSelectionRange(anchor, head) {
5164
- var _a;
5165
- if (!((_a = this._selectionRange) === null || _a === void 0 ? void 0 : _a.type))
5166
- this._selectionRange = { anchorNode: anchor.node, anchorOffset: anchor.offset,
5167
- focusNode: head.node, focusOffset: head.offset };
5234
+ this.selectionRange.set(anchor.node, anchor.offset, head.node, head.offset);
5235
+ this.selectionChanged = false;
5168
5236
  }
5169
5237
  listenForScroll() {
5170
5238
  this.parentCheck = -1;
@@ -5211,7 +5279,6 @@ class DOMObserver {
5211
5279
  if (this.active)
5212
5280
  return;
5213
5281
  this.observer.observe(this.dom, observeOptions);
5214
- this.dom.ownerDocument.addEventListener("selectionchange", this.onSelectionChange);
5215
5282
  if (useCharData)
5216
5283
  this.dom.addEventListener("DOMCharacterDataModified", this.onCharData);
5217
5284
  this.active = true;
@@ -5221,18 +5288,14 @@ class DOMObserver {
5221
5288
  return;
5222
5289
  this.active = false;
5223
5290
  this.observer.disconnect();
5224
- this.dom.ownerDocument.removeEventListener("selectionchange", this.onSelectionChange);
5225
5291
  if (useCharData)
5226
5292
  this.dom.removeEventListener("DOMCharacterDataModified", this.onCharData);
5227
5293
  }
5228
- clearSelection() {
5229
- this.ignoreSelection.set(this.selectionRange);
5230
- }
5231
5294
  // Throw away any pending changes
5232
5295
  clear() {
5233
5296
  this.observer.takeRecords();
5234
5297
  this.queue.length = 0;
5235
- this.clearSelection();
5298
+ this.selectionChanged = false;
5236
5299
  }
5237
5300
  flushSoon() {
5238
5301
  if (this.delayedFlush < 0)
@@ -5269,24 +5332,24 @@ class DOMObserver {
5269
5332
  return { from, to, typeOver };
5270
5333
  }
5271
5334
  // Apply pending changes, if any
5272
- flush() {
5335
+ flush(readSelection = true) {
5336
+ if (readSelection)
5337
+ this.readSelectionRange();
5273
5338
  // Completely hold off flushing when pending keys are set—the code
5274
5339
  // managing those will make sure processRecords is called and the
5275
5340
  // view is resynchronized after
5276
5341
  if (this.delayedFlush >= 0 || this.view.inputState.pendingAndroidKey)
5277
5342
  return;
5278
- this.lastFlush = Date.now();
5279
5343
  let { from, to, typeOver } = this.processRecords();
5280
- let selection = this.selectionRange;
5281
- let newSel = !this.ignoreSelection.eq(selection) && hasSelection(this.dom, selection);
5344
+ let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
5282
5345
  if (from < 0 && !newSel)
5283
5346
  return;
5347
+ this.selectionChanged = false;
5284
5348
  let startState = this.view.state;
5285
5349
  this.onChange(from, to, typeOver);
5286
5350
  // The view wasn't updated
5287
5351
  if (this.view.state == startState)
5288
5352
  this.view.docView.reset(newSel);
5289
- this.clearSelection();
5290
5353
  }
5291
5354
  readMutation(rec) {
5292
5355
  let cView = this.view.docView.nearest(rec.target);
@@ -5309,15 +5372,16 @@ class DOMObserver {
5309
5372
  }
5310
5373
  }
5311
5374
  destroy() {
5375
+ var _a, _b, _c;
5312
5376
  this.stop();
5313
- if (this.intersection)
5314
- this.intersection.disconnect();
5315
- if (this.gapIntersection)
5316
- this.gapIntersection.disconnect();
5377
+ (_a = this.intersection) === null || _a === void 0 ? void 0 : _a.disconnect();
5378
+ (_b = this.gapIntersection) === null || _b === void 0 ? void 0 : _b.disconnect();
5379
+ (_c = this.resize) === null || _c === void 0 ? void 0 : _c.disconnect();
5317
5380
  for (let dom of this.scrollTargets)
5318
5381
  dom.removeEventListener("scroll", this.onScroll);
5319
5382
  window.removeEventListener("scroll", this.onScroll);
5320
5383
  clearTimeout(this.parentCheck);
5384
+ clearTimeout(this.resizeTimeout);
5321
5385
  }
5322
5386
  }
5323
5387
  function findChild(cView, dom, dir) {
@@ -5330,6 +5394,7 @@ function findChild(cView, dom, dir) {
5330
5394
  }
5331
5395
  return null;
5332
5396
  }
5397
+ // Used to work around a Safari Selection/shadow DOM bug (#414)
5333
5398
  function safariSelectionRangeHack(view) {
5334
5399
  let found = null;
5335
5400
  // Because Safari (at least in 2018-2021) doesn't provide regular
@@ -5749,6 +5814,7 @@ class EditorView {
5749
5814
  this.mountStyles();
5750
5815
  this.updateAttrs();
5751
5816
  this.showAnnouncements(transactions);
5817
+ this.docView.updateSelection(redrawn, transactions.some(tr => tr.isUserEvent("select.pointer")));
5752
5818
  }
5753
5819
  finally {
5754
5820
  this.updateState = 0 /* Idle */;
@@ -5826,7 +5892,7 @@ class EditorView {
5826
5892
  return;
5827
5893
  if (this.measureScheduled > -1)
5828
5894
  cancelAnimationFrame(this.measureScheduled);
5829
- this.measureScheduled = -1; // Prevent requestMeasure calls from scheduling another animation frame
5895
+ this.measureScheduled = 0; // Prevent requestMeasure calls from scheduling another animation frame
5830
5896
  if (flush)
5831
5897
  this.observer.flush();
5832
5898
  let updated = null;
@@ -5834,11 +5900,11 @@ class EditorView {
5834
5900
  for (let i = 0;; i++) {
5835
5901
  this.updateState = 1 /* Measuring */;
5836
5902
  let oldViewport = this.viewport;
5837
- let changed = this.viewState.measure(this.docView, i > 0);
5903
+ let changed = this.viewState.measure(this);
5838
5904
  if (!changed && !this.measureRequests.length && this.viewState.scrollTarget == null)
5839
5905
  break;
5840
5906
  if (i > 5) {
5841
- console.warn("Viewport failed to stabilize");
5907
+ console.warn(this.measureRequests.length ? "Measure loop restarted more than 5 times" : "Viewport failed to stabilize");
5842
5908
  break;
5843
5909
  }
5844
5910
  let measuring = [];
@@ -5854,7 +5920,7 @@ class EditorView {
5854
5920
  return BadMeasure;
5855
5921
  }
5856
5922
  });
5857
- let update = new ViewUpdate(this, this.state);
5923
+ let update = new ViewUpdate(this, this.state), redrawn = false;
5858
5924
  update.flags |= changed;
5859
5925
  if (!updated)
5860
5926
  updated = update;
@@ -5864,14 +5930,15 @@ class EditorView {
5864
5930
  if (!update.empty) {
5865
5931
  this.updatePlugins(update);
5866
5932
  this.inputState.update(update);
5933
+ this.updateAttrs();
5934
+ redrawn = this.docView.update(update);
5867
5935
  }
5868
- this.updateAttrs();
5869
- if (changed)
5870
- this.docView.update(update);
5871
5936
  for (let i = 0; i < measuring.length; i++)
5872
5937
  if (measured[i] != BadMeasure) {
5873
5938
  try {
5874
- measuring[i].write(measured[i], this);
5939
+ let m = measuring[i];
5940
+ if (m.write)
5941
+ m.write(measured[i], this);
5875
5942
  }
5876
5943
  catch (e) {
5877
5944
  logException(this.state, e);
@@ -5881,14 +5948,16 @@ class EditorView {
5881
5948
  this.docView.scrollIntoView(this.viewState.scrollTarget);
5882
5949
  this.viewState.scrollTarget = null;
5883
5950
  }
5951
+ if (redrawn)
5952
+ this.docView.updateSelection(true);
5884
5953
  if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to && this.measureRequests.length == 0)
5885
5954
  break;
5886
5955
  }
5887
5956
  }
5888
5957
  finally {
5889
5958
  this.updateState = 0 /* Idle */;
5959
+ this.measureScheduled = -1;
5890
5960
  }
5891
- this.measureScheduled = -1;
5892
5961
  if (updated && !updated.empty)
5893
5962
  for (let listener of this.state.facet(updateListener))
5894
5963
  listener(updated);
@@ -5905,8 +5974,6 @@ class EditorView {
5905
5974
  let editorAttrs = combineAttrs(this.state.facet(editorAttributes), {
5906
5975
  class: "cm-editor" + (this.hasFocus ? " cm-focused " : " ") + this.themeClasses
5907
5976
  });
5908
- updateAttrs(this.dom, this.editorAttrs, editorAttrs);
5909
- this.editorAttrs = editorAttrs;
5910
5977
  let contentAttrs = {
5911
5978
  spellcheck: "false",
5912
5979
  autocorrect: "off",
@@ -5921,7 +5988,11 @@ class EditorView {
5921
5988
  if (this.state.readOnly)
5922
5989
  contentAttrs["aria-readonly"] = "true";
5923
5990
  combineAttrs(this.state.facet(contentAttributes), contentAttrs);
5924
- updateAttrs(this.contentDOM, this.contentAttrs, contentAttrs);
5991
+ this.observer.ignore(() => {
5992
+ updateAttrs(this.contentDOM, this.contentAttrs, contentAttrs);
5993
+ updateAttrs(this.dom, this.editorAttrs, editorAttrs);
5994
+ });
5995
+ this.editorAttrs = editorAttrs;
5925
5996
  this.contentAttrs = contentAttrs;
5926
5997
  }
5927
5998
  showAnnouncements(trs) {
@@ -5991,6 +6062,13 @@ class EditorView {
5991
6062
  return null;
5992
6063
  }
5993
6064
  /**
6065
+ The top position of the document, in screen coordinates. This
6066
+ may be negative when the editor is scrolled down.
6067
+ */
6068
+ get documentTop() {
6069
+ return this.contentDOM.getBoundingClientRect().top + this.viewState.paddingTop;
6070
+ }
6071
+ /**
5994
6072
  Find the line or block widget at the given vertical position.
5995
6073
 
5996
6074
  By default, this position is interpreted as a screen position,
@@ -6000,10 +6078,21 @@ class EditorView {
6000
6078
  position, or a precomputed document top
6001
6079
  (`view.contentDOM.getBoundingClientRect().top`) to limit layout
6002
6080
  queries.
6081
+
6082
+ *Deprecated: use `blockAtHeight` instead.*
6003
6083
  */
6004
6084
  blockAtHeight(height, docTop) {
6085
+ let top = ensureTop(docTop, this);
6086
+ return this.elementAtHeight(height - top).moveY(top);
6087
+ }
6088
+ /**
6089
+ Find the text line or block widget at the given vertical
6090
+ position (which is interpreted as relative to the [top of the
6091
+ document](https://codemirror.net/6/docs/ref/#view.EditorView.documentTop)
6092
+ */
6093
+ elementAtHeight(height) {
6005
6094
  this.readMeasured();
6006
- return this.viewState.blockAtHeight(height, ensureTop(docTop, this.contentDOM));
6095
+ return this.viewState.elementAtHeight(height);
6007
6096
  }
6008
6097
  /**
6009
6098
  Find information for the visual line (see
@@ -6015,20 +6104,43 @@ class EditorView {
6015
6104
  Defaults to treating `height` as a screen position. See
6016
6105
  [`blockAtHeight`](https://codemirror.net/6/docs/ref/#view.EditorView.blockAtHeight) for the
6017
6106
  interpretation of the `docTop` parameter.
6107
+
6108
+ *Deprecated: use `lineBlockAtHeight` instead.*
6018
6109
  */
6019
6110
  visualLineAtHeight(height, docTop) {
6111
+ let top = ensureTop(docTop, this);
6112
+ return this.lineBlockAtHeight(height - top).moveY(top);
6113
+ }
6114
+ /**
6115
+ Find the line block (see
6116
+ [`lineBlockAt`](https://codemirror.net/6/docs/ref/#view.EditorView.lineBlockAt) at the given
6117
+ height.
6118
+ */
6119
+ lineBlockAtHeight(height) {
6020
6120
  this.readMeasured();
6021
- return this.viewState.lineAtHeight(height, ensureTop(docTop, this.contentDOM));
6121
+ return this.viewState.lineBlockAtHeight(height);
6022
6122
  }
6023
6123
  /**
6024
6124
  Iterate over the height information of the visual lines in the
6025
6125
  viewport. The heights of lines are reported relative to the
6026
6126
  given document top, which defaults to the screen position of the
6027
6127
  document (forcing a layout).
6128
+
6129
+ *Deprecated: use `viewportLineBlocks` instead.*
6028
6130
  */
6029
6131
  viewportLines(f, docTop) {
6030
- let { from, to } = this.viewport;
6031
- this.viewState.forEachLine(from, to, f, ensureTop(docTop, this.contentDOM));
6132
+ let top = ensureTop(docTop, this);
6133
+ for (let line of this.viewportLineBlocks)
6134
+ f(line.moveY(top));
6135
+ }
6136
+ /**
6137
+ Get the extent and vertical position of all [line
6138
+ blocks](https://codemirror.net/6/docs/ref/#view.EditorView.lineBlockAt) in the viewport. Positions
6139
+ are relative to the [top of the
6140
+ document](https://codemirror.net/6/docs/ref/#view.EditorView.documentTop);
6141
+ */
6142
+ get viewportLineBlocks() {
6143
+ return this.viewState.viewportLines;
6032
6144
  }
6033
6145
  /**
6034
6146
  Find the extent and height of the visual line (a range delimited
@@ -6039,9 +6151,22 @@ class EditorView {
6039
6151
  argument, which defaults to 0 for this method. You can pass
6040
6152
  `view.contentDOM.getBoundingClientRect().top` here to get screen
6041
6153
  coordinates.
6154
+
6155
+ *Deprecated: use `lineBlockAt` instead.*
6042
6156
  */
6043
6157
  visualLineAt(pos, docTop = 0) {
6044
- return this.viewState.lineAt(pos, docTop);
6158
+ return this.lineBlockAt(pos).moveY(docTop + this.viewState.paddingTop);
6159
+ }
6160
+ /**
6161
+ Find the line block around the given document position. A line
6162
+ block is a range delimited on both sides by either a
6163
+ non-[hidden](https://codemirror.net/6/docs/ref/#view.Decoration^range) line breaks, or the
6164
+ start/end of the document. It will usually just hold a line of
6165
+ text, but may be broken into multiple textblocks by block
6166
+ widgets.
6167
+ */
6168
+ lineBlockAt(pos) {
6169
+ return this.viewState.lineBlockAt(pos);
6045
6170
  }
6046
6171
  /**
6047
6172
  The editor's total content height.
@@ -6374,8 +6499,9 @@ search match).
6374
6499
  EditorView.announce = /*@__PURE__*/StateEffect.define();
6375
6500
  // Maximum line length for which we compute accurate bidi info
6376
6501
  const MaxBidiLine = 4096;
6377
- function ensureTop(given, dom) {
6378
- return given == null ? dom.getBoundingClientRect().top : given;
6502
+ // FIXME remove this and its callers on next breaking release
6503
+ function ensureTop(given, view) {
6504
+ return (given == null ? view.contentDOM.getBoundingClientRect().top : given) + view.viewState.paddingTop;
6379
6505
  }
6380
6506
  let resizeDebounce = -1;
6381
6507
  function ensureGlobalHandler() {
@@ -6726,7 +6852,7 @@ function wrappedLine(view, pos, inside) {
6726
6852
  type: BlockType.Text };
6727
6853
  }
6728
6854
  function blockAt(view, pos) {
6729
- let line = view.visualLineAt(pos);
6855
+ let line = view.lineBlockAt(pos);
6730
6856
  if (Array.isArray(line.type))
6731
6857
  for (let l of line.type) {
6732
6858
  if (l.to > pos || l.to == pos && (l.to == line.to || l.type == BlockType.Text))
@@ -7117,7 +7243,7 @@ const activeLineHighlighter = /*@__PURE__*/ViewPlugin.fromClass(class {
7117
7243
  for (let r of view.state.selection.ranges) {
7118
7244
  if (!r.empty)
7119
7245
  return Decoration.none;
7120
- let line = view.visualLineAt(r.head);
7246
+ let line = view.lineBlockAt(r.head);
7121
7247
  if (line.from > lastLineStart) {
7122
7248
  deco.push(lineDeco.range(line.from));
7123
7249
  lastLineStart = line.from;