@codemirror/view 6.40.0 → 6.41.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/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## 6.41.0 (2026-04-01)
2
+
3
+ ### Bug fixes
4
+
5
+ Fix an issue where `EditorView.posAtCoords` could incorrectly return a position near a higher element on the line, in mixed-font-size lines.
6
+
7
+ Expand the workaround for the Webkit bug that causes nonexistent selections to stay visible to be active on non-Safari Webkit browsers.
8
+
9
+ ### New features
10
+
11
+ The new `EditorView.cursorScrollMargin` facet can now be used to configure the extra space used when scrolling the cursor into view.
12
+
1
13
  ## 6.40.0 (2026-03-12)
2
14
 
3
15
  ### Bug fixes
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @codemirror/view [![NPM version](https://img.shields.io/npm/v/@codemirror/view.svg)](https://www.npmjs.org/package/@codemirror/view)
2
2
 
3
- [ [**WEBSITE**](https://codemirror.net/) | [**DOCS**](https://codemirror.net/docs/ref/#view) | [**ISSUES**](https://github.com/codemirror/dev/issues) | [**FORUM**](https://discuss.codemirror.net/c/next/) | [**CHANGELOG**](https://github.com/codemirror/view/blob/main/CHANGELOG.md) ]
3
+ [ [**WEBSITE**](https://codemirror.net/) | [**DOCS**](https://codemirror.net/docs/ref/#view) | [**ISSUES**](https://github.com/codemirror/dev/issues) | [**FORUM**](https://discuss.codemirror.net/) | [**CHANGELOG**](https://github.com/codemirror/view/blob/main/CHANGELOG.md) ]
4
4
 
5
5
  This package implements the DOM view component for the
6
6
  [CodeMirror](https://codemirror.net/) code editor.
package/dist/index.cjs CHANGED
@@ -548,12 +548,12 @@ function scrollRectIntoView(dom, rect, side, x, y, xMargin, yMargin, ltr) {
548
548
  }
549
549
  let moveX = 0, moveY = 0;
550
550
  if (y == "nearest") {
551
- if (rect.top < bounding.top) {
551
+ if (rect.top < bounding.top + yMargin) {
552
552
  moveY = rect.top - (bounding.top + yMargin);
553
553
  if (side > 0 && rect.bottom > bounding.bottom + moveY)
554
554
  moveY = rect.bottom - bounding.bottom + yMargin;
555
555
  }
556
- else if (rect.bottom > bounding.bottom) {
556
+ else if (rect.bottom > bounding.bottom - yMargin) {
557
557
  moveY = rect.bottom - bounding.bottom + yMargin;
558
558
  if (side < 0 && (rect.top - moveY) < bounding.top)
559
559
  moveY = rect.top - (bounding.top + yMargin);
@@ -567,12 +567,12 @@ function scrollRectIntoView(dom, rect, side, x, y, xMargin, yMargin, ltr) {
567
567
  moveY = targetTop - bounding.top;
568
568
  }
569
569
  if (x == "nearest") {
570
- if (rect.left < bounding.left) {
570
+ if (rect.left < bounding.left + xMargin) {
571
571
  moveX = rect.left - (bounding.left + xMargin);
572
572
  if (side > 0 && rect.right > bounding.right + moveX)
573
573
  moveX = rect.right - bounding.right + xMargin;
574
574
  }
575
- else if (rect.right > bounding.right) {
575
+ else if (rect.right > bounding.right - xMargin) {
576
576
  moveX = rect.right - bounding.right + xMargin;
577
577
  if (side < 0 && rect.left < bounding.left + moveX)
578
578
  moveX = rect.left - (bounding.left + xMargin);
@@ -1313,7 +1313,7 @@ const nativeSelectionHidden = state.Facet.define({
1313
1313
  });
1314
1314
  const scrollHandler = state.Facet.define();
1315
1315
  class ScrollTarget {
1316
- constructor(range, y = "nearest", x = "nearest", yMargin = 5, xMargin = 5,
1316
+ constructor(range, y, x, yMargin, xMargin,
1317
1317
  // This data structure is abused to also store precise scroll
1318
1318
  // snapshots, instead of a `scrollIntoView` request. When this
1319
1319
  // flag is `true`, `range` points at a position in the reference
@@ -3910,6 +3910,18 @@ class InlineCoordsScan {
3910
3910
  this.y = (side.top + side.bottom) / 2;
3911
3911
  return this.scan(positions, getRects);
3912
3912
  }
3913
+ // Handle the case where closest matched a higher element on the
3914
+ // same line as an element below/above the coords
3915
+ if (closestDx) {
3916
+ if (above && above.bottom > closestRect.top) {
3917
+ this.y = above.bottom - 1;
3918
+ return this.scan(positions, getRects);
3919
+ }
3920
+ if (below && below.top < closestRect.bottom) {
3921
+ this.y = below.top + 1;
3922
+ return this.scan(positions, getRects);
3923
+ }
3924
+ }
3913
3925
  let ltr = (bidi ? this.dirAt(positions[closestI], 1) : this.baseDir) == exports.Direction.LTR;
3914
3926
  return {
3915
3927
  i: closestI,
@@ -7927,7 +7939,8 @@ class EditorView {
7927
7939
  scrollTarget = scrollTarget.map(tr.changes);
7928
7940
  if (tr.scrollIntoView) {
7929
7941
  let { main } = tr.state.selection;
7930
- scrollTarget = new ScrollTarget(main.empty ? main : state.EditorSelection.cursor(main.head, main.head > main.anchor ? -1 : 1));
7942
+ let { x, y } = this.state.facet(EditorView.cursorScrollMargin);
7943
+ scrollTarget = new ScrollTarget(main.empty ? main : state.EditorSelection.cursor(main.head, main.head > main.anchor ? -1 : 1), "nearest", "nearest", y, x);
7931
7944
  }
7932
7945
  for (let e of tr.effects)
7933
7946
  if (e.is(scrollIntoView))
@@ -8584,7 +8597,8 @@ class EditorView {
8584
8597
  cause it to scroll the given position or range into view.
8585
8598
  */
8586
8599
  static scrollIntoView(pos, options = {}) {
8587
- return scrollIntoView.of(new ScrollTarget(typeof pos == "number" ? state.EditorSelection.cursor(pos) : pos, options.y, options.x, options.yMargin, options.xMargin));
8600
+ var _a, _b, _c, _d;
8601
+ return scrollIntoView.of(new ScrollTarget(typeof pos == "number" ? state.EditorSelection.cursor(pos) : pos, (_a = options.y) !== null && _a !== void 0 ? _a : "nearest", (_b = options.x) !== null && _b !== void 0 ? _b : "nearest", (_c = options.yMargin) !== null && _c !== void 0 ? _c : 5, (_d = options.xMargin) !== null && _d !== void 0 ? _d : 5));
8588
8602
  }
8589
8603
  /**
8590
8604
  Return an effect that resets the editor to its current (at the
@@ -8843,11 +8857,30 @@ supported.)
8843
8857
  */
8844
8858
  EditorView.bidiIsolatedRanges = bidiIsolatedRanges;
8845
8859
  /**
8860
+ Can be used to specify the distance that scrolling cursor into
8861
+ view keeps it away from the sides of the editor, either as a
8862
+ single pixel number or two different values for the different
8863
+ axes. Defaults to 5 pixels on both axes.
8864
+ */
8865
+ EditorView.cursorScrollMargin = state.Facet.define({
8866
+ combine: inputs => {
8867
+ let x = 5, y = 5;
8868
+ for (let i of inputs) {
8869
+ if (typeof i == "number")
8870
+ x = y = i;
8871
+ else
8872
+ ({ x, y } = i);
8873
+ }
8874
+ return { x, y };
8875
+ }
8876
+ });
8877
+ /**
8846
8878
  Facet that allows extensions to provide additional scroll
8847
8879
  margins (space around the sides of the scrolling element that
8848
8880
  should be considered invisible). This can be useful when the
8849
8881
  plugin introduces elements that cover part of that element (for
8850
- example a horizontally fixed gutter).
8882
+ example a horizontally fixed gutter). Not to be confused with
8883
+ [`cursorScrollMargin`](https://codemirror.net/6/docs/ref/#view.EditorView^cursorScrollMargin).
8851
8884
  */
8852
8885
  EditorView.scrollMargins = scrollMargins;
8853
8886
  /**
@@ -9383,7 +9416,7 @@ class LayerView {
9383
9416
  old = next;
9384
9417
  }
9385
9418
  this.drawn = markers;
9386
- if (browser.safari && browser.safari_version >= 26) // Issue #1600, 1627
9419
+ if (browser.webkit) // Issue #1600, 1627, 1686
9387
9420
  this.dom.style.display = this.dom.firstChild ? "" : "none";
9388
9421
  }
9389
9422
  }
package/dist/index.d.cts CHANGED
@@ -404,7 +404,7 @@ declare class ScrollTarget {
404
404
  readonly yMargin: number;
405
405
  readonly xMargin: number;
406
406
  readonly isSnapshot: boolean;
407
- constructor(range: SelectionRange, y?: ScrollStrategy, x?: ScrollStrategy, yMargin?: number, xMargin?: number, isSnapshot?: boolean);
407
+ constructor(range: SelectionRange, y: ScrollStrategy, x: ScrollStrategy, yMargin: number, xMargin: number, isSnapshot?: boolean);
408
408
  map(changes: ChangeDesc): ScrollTarget;
409
409
  clip(state: EditorState): ScrollTarget;
410
410
  }
@@ -1356,11 +1356,25 @@ declare class EditorView {
1356
1356
  */
1357
1357
  static bidiIsolatedRanges: Facet<DecorationSet | ((view: EditorView) => DecorationSet), readonly (DecorationSet | ((view: EditorView) => DecorationSet))[]>;
1358
1358
  /**
1359
+ Can be used to specify the distance that scrolling cursor into
1360
+ view keeps it away from the sides of the editor, either as a
1361
+ single pixel number or two different values for the different
1362
+ axes. Defaults to 5 pixels on both axes.
1363
+ */
1364
+ static cursorScrollMargin: Facet<number | {
1365
+ x: number;
1366
+ y: number;
1367
+ }, {
1368
+ x: number;
1369
+ y: number;
1370
+ }>;
1371
+ /**
1359
1372
  Facet that allows extensions to provide additional scroll
1360
1373
  margins (space around the sides of the scrolling element that
1361
1374
  should be considered invisible). This can be useful when the
1362
1375
  plugin introduces elements that cover part of that element (for
1363
- example a horizontally fixed gutter).
1376
+ example a horizontally fixed gutter). Not to be confused with
1377
+ [`cursorScrollMargin`](https://codemirror.net/6/docs/ref/#view.EditorView^cursorScrollMargin).
1364
1378
  */
1365
1379
  static scrollMargins: Facet<(view: EditorView) => Partial<Rect> | null, readonly ((view: EditorView) => Partial<Rect> | null)[]>;
1366
1380
  /**
package/dist/index.d.ts CHANGED
@@ -404,7 +404,7 @@ declare class ScrollTarget {
404
404
  readonly yMargin: number;
405
405
  readonly xMargin: number;
406
406
  readonly isSnapshot: boolean;
407
- constructor(range: SelectionRange, y?: ScrollStrategy, x?: ScrollStrategy, yMargin?: number, xMargin?: number, isSnapshot?: boolean);
407
+ constructor(range: SelectionRange, y: ScrollStrategy, x: ScrollStrategy, yMargin: number, xMargin: number, isSnapshot?: boolean);
408
408
  map(changes: ChangeDesc): ScrollTarget;
409
409
  clip(state: EditorState): ScrollTarget;
410
410
  }
@@ -1356,11 +1356,25 @@ declare class EditorView {
1356
1356
  */
1357
1357
  static bidiIsolatedRanges: Facet<DecorationSet | ((view: EditorView) => DecorationSet), readonly (DecorationSet | ((view: EditorView) => DecorationSet))[]>;
1358
1358
  /**
1359
+ Can be used to specify the distance that scrolling cursor into
1360
+ view keeps it away from the sides of the editor, either as a
1361
+ single pixel number or two different values for the different
1362
+ axes. Defaults to 5 pixels on both axes.
1363
+ */
1364
+ static cursorScrollMargin: Facet<number | {
1365
+ x: number;
1366
+ y: number;
1367
+ }, {
1368
+ x: number;
1369
+ y: number;
1370
+ }>;
1371
+ /**
1359
1372
  Facet that allows extensions to provide additional scroll
1360
1373
  margins (space around the sides of the scrolling element that
1361
1374
  should be considered invisible). This can be useful when the
1362
1375
  plugin introduces elements that cover part of that element (for
1363
- example a horizontally fixed gutter).
1376
+ example a horizontally fixed gutter). Not to be confused with
1377
+ [`cursorScrollMargin`](https://codemirror.net/6/docs/ref/#view.EditorView^cursorScrollMargin).
1364
1378
  */
1365
1379
  static scrollMargins: Facet<(view: EditorView) => Partial<Rect> | null, readonly ((view: EditorView) => Partial<Rect> | null)[]>;
1366
1380
  /**
package/dist/index.js CHANGED
@@ -545,12 +545,12 @@ function scrollRectIntoView(dom, rect, side, x, y, xMargin, yMargin, ltr) {
545
545
  }
546
546
  let moveX = 0, moveY = 0;
547
547
  if (y == "nearest") {
548
- if (rect.top < bounding.top) {
548
+ if (rect.top < bounding.top + yMargin) {
549
549
  moveY = rect.top - (bounding.top + yMargin);
550
550
  if (side > 0 && rect.bottom > bounding.bottom + moveY)
551
551
  moveY = rect.bottom - bounding.bottom + yMargin;
552
552
  }
553
- else if (rect.bottom > bounding.bottom) {
553
+ else if (rect.bottom > bounding.bottom - yMargin) {
554
554
  moveY = rect.bottom - bounding.bottom + yMargin;
555
555
  if (side < 0 && (rect.top - moveY) < bounding.top)
556
556
  moveY = rect.top - (bounding.top + yMargin);
@@ -564,12 +564,12 @@ function scrollRectIntoView(dom, rect, side, x, y, xMargin, yMargin, ltr) {
564
564
  moveY = targetTop - bounding.top;
565
565
  }
566
566
  if (x == "nearest") {
567
- if (rect.left < bounding.left) {
567
+ if (rect.left < bounding.left + xMargin) {
568
568
  moveX = rect.left - (bounding.left + xMargin);
569
569
  if (side > 0 && rect.right > bounding.right + moveX)
570
570
  moveX = rect.right - bounding.right + xMargin;
571
571
  }
572
- else if (rect.right > bounding.right) {
572
+ else if (rect.right > bounding.right - xMargin) {
573
573
  moveX = rect.right - bounding.right + xMargin;
574
574
  if (side < 0 && rect.left < bounding.left + moveX)
575
575
  moveX = rect.left - (bounding.left + xMargin);
@@ -1309,7 +1309,7 @@ const nativeSelectionHidden = /*@__PURE__*/Facet.define({
1309
1309
  });
1310
1310
  const scrollHandler = /*@__PURE__*/Facet.define();
1311
1311
  class ScrollTarget {
1312
- constructor(range, y = "nearest", x = "nearest", yMargin = 5, xMargin = 5,
1312
+ constructor(range, y, x, yMargin, xMargin,
1313
1313
  // This data structure is abused to also store precise scroll
1314
1314
  // snapshots, instead of a `scrollIntoView` request. When this
1315
1315
  // flag is `true`, `range` points at a position in the reference
@@ -3906,6 +3906,18 @@ class InlineCoordsScan {
3906
3906
  this.y = (side.top + side.bottom) / 2;
3907
3907
  return this.scan(positions, getRects);
3908
3908
  }
3909
+ // Handle the case where closest matched a higher element on the
3910
+ // same line as an element below/above the coords
3911
+ if (closestDx) {
3912
+ if (above && above.bottom > closestRect.top) {
3913
+ this.y = above.bottom - 1;
3914
+ return this.scan(positions, getRects);
3915
+ }
3916
+ if (below && below.top < closestRect.bottom) {
3917
+ this.y = below.top + 1;
3918
+ return this.scan(positions, getRects);
3919
+ }
3920
+ }
3909
3921
  let ltr = (bidi ? this.dirAt(positions[closestI], 1) : this.baseDir) == Direction.LTR;
3910
3922
  return {
3911
3923
  i: closestI,
@@ -7922,7 +7934,8 @@ class EditorView {
7922
7934
  scrollTarget = scrollTarget.map(tr.changes);
7923
7935
  if (tr.scrollIntoView) {
7924
7936
  let { main } = tr.state.selection;
7925
- scrollTarget = new ScrollTarget(main.empty ? main : EditorSelection.cursor(main.head, main.head > main.anchor ? -1 : 1));
7937
+ let { x, y } = this.state.facet(EditorView.cursorScrollMargin);
7938
+ scrollTarget = new ScrollTarget(main.empty ? main : EditorSelection.cursor(main.head, main.head > main.anchor ? -1 : 1), "nearest", "nearest", y, x);
7926
7939
  }
7927
7940
  for (let e of tr.effects)
7928
7941
  if (e.is(scrollIntoView))
@@ -8579,7 +8592,8 @@ class EditorView {
8579
8592
  cause it to scroll the given position or range into view.
8580
8593
  */
8581
8594
  static scrollIntoView(pos, options = {}) {
8582
- return scrollIntoView.of(new ScrollTarget(typeof pos == "number" ? EditorSelection.cursor(pos) : pos, options.y, options.x, options.yMargin, options.xMargin));
8595
+ var _a, _b, _c, _d;
8596
+ return scrollIntoView.of(new ScrollTarget(typeof pos == "number" ? EditorSelection.cursor(pos) : pos, (_a = options.y) !== null && _a !== void 0 ? _a : "nearest", (_b = options.x) !== null && _b !== void 0 ? _b : "nearest", (_c = options.yMargin) !== null && _c !== void 0 ? _c : 5, (_d = options.xMargin) !== null && _d !== void 0 ? _d : 5));
8583
8597
  }
8584
8598
  /**
8585
8599
  Return an effect that resets the editor to its current (at the
@@ -8838,11 +8852,30 @@ supported.)
8838
8852
  */
8839
8853
  EditorView.bidiIsolatedRanges = bidiIsolatedRanges;
8840
8854
  /**
8855
+ Can be used to specify the distance that scrolling cursor into
8856
+ view keeps it away from the sides of the editor, either as a
8857
+ single pixel number or two different values for the different
8858
+ axes. Defaults to 5 pixels on both axes.
8859
+ */
8860
+ EditorView.cursorScrollMargin = /*@__PURE__*/Facet.define({
8861
+ combine: inputs => {
8862
+ let x = 5, y = 5;
8863
+ for (let i of inputs) {
8864
+ if (typeof i == "number")
8865
+ x = y = i;
8866
+ else
8867
+ ({ x, y } = i);
8868
+ }
8869
+ return { x, y };
8870
+ }
8871
+ });
8872
+ /**
8841
8873
  Facet that allows extensions to provide additional scroll
8842
8874
  margins (space around the sides of the scrolling element that
8843
8875
  should be considered invisible). This can be useful when the
8844
8876
  plugin introduces elements that cover part of that element (for
8845
- example a horizontally fixed gutter).
8877
+ example a horizontally fixed gutter). Not to be confused with
8878
+ [`cursorScrollMargin`](https://codemirror.net/6/docs/ref/#view.EditorView^cursorScrollMargin).
8846
8879
  */
8847
8880
  EditorView.scrollMargins = scrollMargins;
8848
8881
  /**
@@ -9378,7 +9411,7 @@ class LayerView {
9378
9411
  old = next;
9379
9412
  }
9380
9413
  this.drawn = markers;
9381
- if (browser.safari && browser.safari_version >= 26) // Issue #1600, 1627
9414
+ if (browser.webkit) // Issue #1600, 1627, 1686
9382
9415
  this.dom.style.display = this.dom.firstChild ? "" : "none";
9383
9416
  }
9384
9417
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "6.40.0",
3
+ "version": "6.41.0",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",