@codemirror/view 6.43.6 → 6.43.8

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,23 @@
1
+ ## 6.43.8 (2026-08-04)
2
+
3
+ ### Bug fixes
4
+
5
+ Fix an issue that could cause identical `posAtCoords` queries to return different results.
6
+
7
+ Fix an issue where scrolling a position into view vertically on Android or iOS could cause horizontal scrolling motion.
8
+
9
+ ## 6.43.7 (2026-07-27)
10
+
11
+ ### Bug fixes
12
+
13
+ Make the panel container top/bottom style a regular rule, rather than an inline style.
14
+
15
+ Fix an issue where widgets changing length could break content updates.
16
+
17
+ Fix incorrectly drawn selection when a line wrap point lies between widgets.
18
+
19
+ Work around regression in Firefox 153 breaking selection hiding.
20
+
1
21
  ## 6.43.6 (2026-07-06)
2
22
 
3
23
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -685,6 +685,22 @@ class DOMSelectionState {
685
685
  this.focusOffset = focusOffset;
686
686
  }
687
687
  }
688
+ function getScrollStack(target) {
689
+ let stack = [];
690
+ for (let cur = target; cur; cur = cur.nodeType == 11 ? cur.host : cur.parentNode) {
691
+ if (cur.nodeType == 1)
692
+ stack.push({ node: cur, left: cur.scrollLeft, top: cur.scrollTop });
693
+ }
694
+ return stack;
695
+ }
696
+ function restoreScrollStack(stack, vert = true) {
697
+ for (let { node, left, top } of stack) {
698
+ if (vert && node.scrollTop != top)
699
+ node.scrollTop = top;
700
+ if (node.scrollLeft != left)
701
+ node.scrollLeft = left;
702
+ }
703
+ }
688
704
  let preventScrollSupported = null;
689
705
  // Safari 26 breaks preventScroll support
690
706
  if (browser.safari && browser.safari_version >= 26)
@@ -696,12 +712,7 @@ function focusPreventScroll(dom) {
696
712
  return dom.setActive(); // in IE
697
713
  if (preventScrollSupported)
698
714
  return dom.focus(preventScrollSupported);
699
- let stack = [];
700
- for (let cur = dom; cur; cur = cur.parentNode) {
701
- stack.push(cur, cur.scrollTop, cur.scrollLeft);
702
- if (cur == cur.ownerDocument)
703
- break;
704
- }
715
+ let stack = getScrollStack(dom);
705
716
  dom.focus(preventScrollSupported == null ? {
706
717
  get preventScroll() {
707
718
  preventScrollSupported = { preventScroll: true };
@@ -710,13 +721,7 @@ function focusPreventScroll(dom) {
710
721
  } : undefined);
711
722
  if (!preventScrollSupported) {
712
723
  preventScrollSupported = false;
713
- for (let i = 0; i < stack.length;) {
714
- let elt = stack[i++], top = stack[i++], left = stack[i++];
715
- if (elt.scrollTop != top)
716
- elt.scrollTop = top;
717
- if (elt.scrollLeft != left)
718
- elt.scrollLeft = left;
719
- }
724
+ restoreScrollStack(stack);
720
725
  }
721
726
  }
722
727
  let scratchRange;
@@ -1970,6 +1975,9 @@ class LineTile extends CompositeTile {
1970
1975
  }
1971
1976
  get domAttrs() { return this.attrs; }
1972
1977
  // Find the tile associated with a given position in this line.
1978
+ // Side -2/2 is handled specially, in that it allows the position
1979
+ // returned to be before (-2) or after (2) widgets that would always
1980
+ // be after/before a cursor position.
1973
1981
  resolveInline(pos, side, forCoords) {
1974
1982
  let before = null, beforeOff = -1, after = null, afterOff = -1;
1975
1983
  function scan(tile, pos) {
@@ -1980,11 +1988,11 @@ class LineTile extends CompositeTile {
1980
1988
  scan(child, pos - off);
1981
1989
  }
1982
1990
  else if ((!after || after.isHidden && (side > 0 && !(after.flags & 32 /* TileFlag.After */) || forCoords && onSameLine(after, child))) &&
1983
- (end > pos || (child.flags & 32 /* TileFlag.After */))) {
1991
+ (end > pos || (child.flags & 32 /* TileFlag.After */) && side <= 1)) {
1984
1992
  after = child;
1985
1993
  afterOff = pos - off;
1986
1994
  }
1987
- else if (off < pos || (child.flags & 16 /* TileFlag.Before */) && !child.isHidden) {
1995
+ else if (off < pos || (child.flags & 16 /* TileFlag.Before */) && !child.isHidden && side >= -1) {
1988
1996
  before = child;
1989
1997
  beforeOff = pos - off;
1990
1998
  }
@@ -2190,7 +2198,15 @@ class TilePointer {
2190
2198
  let { tile, index, beforeBreak, parents } = this;
2191
2199
  while (dist || side > 0) {
2192
2200
  if (!tile.isComposite()) {
2193
- if (index == tile.length) {
2201
+ let len = tile.length;
2202
+ if (index < len && dist) {
2203
+ let take = Math.min(dist, len - index);
2204
+ if (walker)
2205
+ walker.skip(tile, index, index + take);
2206
+ dist -= take;
2207
+ index += take;
2208
+ }
2209
+ if (index == len) {
2194
2210
  beforeBreak = !!tile.breakAfter;
2195
2211
  ({ tile, index } = parents.pop());
2196
2212
  index++;
@@ -2198,13 +2214,6 @@ class TilePointer {
2198
2214
  else if (!dist) {
2199
2215
  break;
2200
2216
  }
2201
- else {
2202
- let take = Math.min(dist, tile.length - index);
2203
- if (walker)
2204
- walker.skip(tile, index, index + take);
2205
- dist -= take;
2206
- index += take;
2207
- }
2208
2217
  }
2209
2218
  else if (beforeBreak) {
2210
2219
  if (!dist)
@@ -3458,11 +3467,14 @@ class DocView {
3458
3467
  // can affect it. So this tries to kludge around the problem by
3459
3468
  // calling scrollIntoView on the scroll target's line.
3460
3469
  if (window.visualViewport && window.innerHeight - window.visualViewport.height > 1 &&
3461
- (rect.top > window.pageYOffset + window.visualViewport.offsetTop + window.visualViewport.height ||
3462
- rect.bottom < window.pageYOffset + window.visualViewport.offsetTop)) {
3470
+ (rect.top > window.visualViewport.offsetTop + window.visualViewport.height ||
3471
+ rect.bottom < window.visualViewport.offsetTop)) {
3463
3472
  let line = this.view.docView.lineAt(range.head, 1);
3464
- if (line)
3473
+ if (line) {
3474
+ let stack = getScrollStack(line.dom);
3465
3475
  line.dom.scrollIntoView({ block: "nearest" });
3476
+ restoreScrollStack(stack, false);
3477
+ }
3466
3478
  }
3467
3479
  }
3468
3480
  lineHasWidget(pos) {
@@ -3884,23 +3896,22 @@ class InlineCoordsScan {
3884
3896
  search: while (lo < hi) {
3885
3897
  let dist = hi - lo, mid = (lo + hi) >> 1;
3886
3898
  adjust: if (seen.has(mid)) {
3887
- let scan = lo + Math.floor(Math.random() * dist);
3888
- for (let i = 0; i < dist; i++) {
3899
+ for (let i = 1; i < dist; i++) {
3900
+ let scan = mid + i;
3901
+ if (scan >= hi)
3902
+ scan -= dist;
3889
3903
  if (!seen.has(scan)) {
3890
3904
  mid = scan;
3891
3905
  break adjust;
3892
3906
  }
3893
- scan++;
3894
- if (scan == hi)
3895
- scan = lo; // Wrap around
3896
3907
  }
3897
3908
  break search; // No index found, we're done
3898
3909
  }
3899
3910
  seen.add(mid);
3900
- let rects = getRects(mid);
3911
+ let rects = getRects(mid), side = 0;
3901
3912
  if (rects)
3902
3913
  for (let i = 0; i < rects.length; i++) {
3903
- let rect = rects[i], side = 0;
3914
+ let rect = rects[i];
3904
3915
  // Ignore empty rectangles when there are other rectangles
3905
3916
  if (rect.width == 0 && rects.length > 1)
3906
3917
  continue;
@@ -3925,12 +3936,12 @@ class InlineCoordsScan {
3925
3936
  if (off)
3926
3937
  side = (off < 0) == (this.baseDir == exports.Direction.LTR) ? -1 : 1;
3927
3938
  }
3928
- // Narrow binary search when it is safe to do so
3929
- if (side == -1 && (!bidi || this.baseDirAt(positions[mid], 1)))
3930
- hi = mid;
3931
- else if (side == 1 && (!bidi || this.baseDirAt(positions[mid + 1], -1)))
3932
- lo = mid + 1;
3933
3939
  }
3940
+ // Narrow binary search when it is safe to do so
3941
+ if (side == -1 && (!bidi || this.baseDirAt(positions[mid], 1)))
3942
+ hi = mid;
3943
+ else if (side == 1 && (!bidi || this.baseDirAt(positions[mid + 1], -1)))
3944
+ lo = mid + 1;
3934
3945
  }
3935
3946
  // If no element with y overlap is found, find the nearest element
3936
3947
  // on the y axis, move this.y into it, and retry the scan.
@@ -6983,6 +6994,8 @@ const baseTheme$1 = buildTheme("." + baseThemeID, {
6983
6994
  backgroundColor: "#f5f5f5",
6984
6995
  color: "black"
6985
6996
  },
6997
+ ".cm-panels-top": { top: "0" },
6998
+ ".cm-panels-bottom": { bottom: "0" },
6986
6999
  "&light .cm-panels-top": {
6987
7000
  borderBottom: "1px solid #ddd"
6988
7001
  },
@@ -9591,9 +9604,11 @@ const selectionLayer = layer({
9591
9604
  },
9592
9605
  class: "cm-selectionLayer"
9593
9606
  });
9607
+ // https://discuss.codemirror.net/t/firefox-153-ignores-transparent-selection-styling/9838
9608
+ const selectionBg = browser.gecko && browser.gecko_version == 153 ? "#ffffff01" : "transparent";
9594
9609
  const hideNativeSelection = state.Prec.highest(EditorView.theme({
9595
9610
  ".cm-line": {
9596
- "& ::selection, &::selection": { backgroundColor: "transparent !important" },
9611
+ "& ::selection, &::selection": { backgroundColor: `${selectionBg} !important` },
9597
9612
  caretColor: "transparent !important"
9598
9613
  },
9599
9614
  ".cm-content": {
@@ -11104,7 +11119,6 @@ class PanelGroup {
11104
11119
  if (!this.dom) {
11105
11120
  this.dom = document.createElement("div");
11106
11121
  this.dom.className = this.top ? "cm-panels cm-panels-top" : "cm-panels cm-panels-bottom";
11107
- this.dom.style[this.top ? "top" : "bottom"] = "0";
11108
11122
  let parent = this.container || this.view.dom;
11109
11123
  parent.insertBefore(this.dom, this.top ? parent.firstChild : null);
11110
11124
  }
package/dist/index.js CHANGED
@@ -682,6 +682,22 @@ class DOMSelectionState {
682
682
  this.focusOffset = focusOffset;
683
683
  }
684
684
  }
685
+ function getScrollStack(target) {
686
+ let stack = [];
687
+ for (let cur = target; cur; cur = cur.nodeType == 11 ? cur.host : cur.parentNode) {
688
+ if (cur.nodeType == 1)
689
+ stack.push({ node: cur, left: cur.scrollLeft, top: cur.scrollTop });
690
+ }
691
+ return stack;
692
+ }
693
+ function restoreScrollStack(stack, vert = true) {
694
+ for (let { node, left, top } of stack) {
695
+ if (vert && node.scrollTop != top)
696
+ node.scrollTop = top;
697
+ if (node.scrollLeft != left)
698
+ node.scrollLeft = left;
699
+ }
700
+ }
685
701
  let preventScrollSupported = null;
686
702
  // Safari 26 breaks preventScroll support
687
703
  if (browser.safari && browser.safari_version >= 26)
@@ -693,12 +709,7 @@ function focusPreventScroll(dom) {
693
709
  return dom.setActive(); // in IE
694
710
  if (preventScrollSupported)
695
711
  return dom.focus(preventScrollSupported);
696
- let stack = [];
697
- for (let cur = dom; cur; cur = cur.parentNode) {
698
- stack.push(cur, cur.scrollTop, cur.scrollLeft);
699
- if (cur == cur.ownerDocument)
700
- break;
701
- }
712
+ let stack = getScrollStack(dom);
702
713
  dom.focus(preventScrollSupported == null ? {
703
714
  get preventScroll() {
704
715
  preventScrollSupported = { preventScroll: true };
@@ -707,13 +718,7 @@ function focusPreventScroll(dom) {
707
718
  } : undefined);
708
719
  if (!preventScrollSupported) {
709
720
  preventScrollSupported = false;
710
- for (let i = 0; i < stack.length;) {
711
- let elt = stack[i++], top = stack[i++], left = stack[i++];
712
- if (elt.scrollTop != top)
713
- elt.scrollTop = top;
714
- if (elt.scrollLeft != left)
715
- elt.scrollLeft = left;
716
- }
721
+ restoreScrollStack(stack);
717
722
  }
718
723
  }
719
724
  let scratchRange;
@@ -1966,6 +1971,9 @@ class LineTile extends CompositeTile {
1966
1971
  }
1967
1972
  get domAttrs() { return this.attrs; }
1968
1973
  // Find the tile associated with a given position in this line.
1974
+ // Side -2/2 is handled specially, in that it allows the position
1975
+ // returned to be before (-2) or after (2) widgets that would always
1976
+ // be after/before a cursor position.
1969
1977
  resolveInline(pos, side, forCoords) {
1970
1978
  let before = null, beforeOff = -1, after = null, afterOff = -1;
1971
1979
  function scan(tile, pos) {
@@ -1976,11 +1984,11 @@ class LineTile extends CompositeTile {
1976
1984
  scan(child, pos - off);
1977
1985
  }
1978
1986
  else if ((!after || after.isHidden && (side > 0 && !(after.flags & 32 /* TileFlag.After */) || forCoords && onSameLine(after, child))) &&
1979
- (end > pos || (child.flags & 32 /* TileFlag.After */))) {
1987
+ (end > pos || (child.flags & 32 /* TileFlag.After */) && side <= 1)) {
1980
1988
  after = child;
1981
1989
  afterOff = pos - off;
1982
1990
  }
1983
- else if (off < pos || (child.flags & 16 /* TileFlag.Before */) && !child.isHidden) {
1991
+ else if (off < pos || (child.flags & 16 /* TileFlag.Before */) && !child.isHidden && side >= -1) {
1984
1992
  before = child;
1985
1993
  beforeOff = pos - off;
1986
1994
  }
@@ -2186,7 +2194,15 @@ class TilePointer {
2186
2194
  let { tile, index, beforeBreak, parents } = this;
2187
2195
  while (dist || side > 0) {
2188
2196
  if (!tile.isComposite()) {
2189
- if (index == tile.length) {
2197
+ let len = tile.length;
2198
+ if (index < len && dist) {
2199
+ let take = Math.min(dist, len - index);
2200
+ if (walker)
2201
+ walker.skip(tile, index, index + take);
2202
+ dist -= take;
2203
+ index += take;
2204
+ }
2205
+ if (index == len) {
2190
2206
  beforeBreak = !!tile.breakAfter;
2191
2207
  ({ tile, index } = parents.pop());
2192
2208
  index++;
@@ -2194,13 +2210,6 @@ class TilePointer {
2194
2210
  else if (!dist) {
2195
2211
  break;
2196
2212
  }
2197
- else {
2198
- let take = Math.min(dist, tile.length - index);
2199
- if (walker)
2200
- walker.skip(tile, index, index + take);
2201
- dist -= take;
2202
- index += take;
2203
- }
2204
2213
  }
2205
2214
  else if (beforeBreak) {
2206
2215
  if (!dist)
@@ -3454,11 +3463,14 @@ class DocView {
3454
3463
  // can affect it. So this tries to kludge around the problem by
3455
3464
  // calling scrollIntoView on the scroll target's line.
3456
3465
  if (window.visualViewport && window.innerHeight - window.visualViewport.height > 1 &&
3457
- (rect.top > window.pageYOffset + window.visualViewport.offsetTop + window.visualViewport.height ||
3458
- rect.bottom < window.pageYOffset + window.visualViewport.offsetTop)) {
3466
+ (rect.top > window.visualViewport.offsetTop + window.visualViewport.height ||
3467
+ rect.bottom < window.visualViewport.offsetTop)) {
3459
3468
  let line = this.view.docView.lineAt(range.head, 1);
3460
- if (line)
3469
+ if (line) {
3470
+ let stack = getScrollStack(line.dom);
3461
3471
  line.dom.scrollIntoView({ block: "nearest" });
3472
+ restoreScrollStack(stack, false);
3473
+ }
3462
3474
  }
3463
3475
  }
3464
3476
  lineHasWidget(pos) {
@@ -3880,23 +3892,22 @@ class InlineCoordsScan {
3880
3892
  search: while (lo < hi) {
3881
3893
  let dist = hi - lo, mid = (lo + hi) >> 1;
3882
3894
  adjust: if (seen.has(mid)) {
3883
- let scan = lo + Math.floor(Math.random() * dist);
3884
- for (let i = 0; i < dist; i++) {
3895
+ for (let i = 1; i < dist; i++) {
3896
+ let scan = mid + i;
3897
+ if (scan >= hi)
3898
+ scan -= dist;
3885
3899
  if (!seen.has(scan)) {
3886
3900
  mid = scan;
3887
3901
  break adjust;
3888
3902
  }
3889
- scan++;
3890
- if (scan == hi)
3891
- scan = lo; // Wrap around
3892
3903
  }
3893
3904
  break search; // No index found, we're done
3894
3905
  }
3895
3906
  seen.add(mid);
3896
- let rects = getRects(mid);
3907
+ let rects = getRects(mid), side = 0;
3897
3908
  if (rects)
3898
3909
  for (let i = 0; i < rects.length; i++) {
3899
- let rect = rects[i], side = 0;
3910
+ let rect = rects[i];
3900
3911
  // Ignore empty rectangles when there are other rectangles
3901
3912
  if (rect.width == 0 && rects.length > 1)
3902
3913
  continue;
@@ -3921,12 +3932,12 @@ class InlineCoordsScan {
3921
3932
  if (off)
3922
3933
  side = (off < 0) == (this.baseDir == Direction.LTR) ? -1 : 1;
3923
3934
  }
3924
- // Narrow binary search when it is safe to do so
3925
- if (side == -1 && (!bidi || this.baseDirAt(positions[mid], 1)))
3926
- hi = mid;
3927
- else if (side == 1 && (!bidi || this.baseDirAt(positions[mid + 1], -1)))
3928
- lo = mid + 1;
3929
3935
  }
3936
+ // Narrow binary search when it is safe to do so
3937
+ if (side == -1 && (!bidi || this.baseDirAt(positions[mid], 1)))
3938
+ hi = mid;
3939
+ else if (side == 1 && (!bidi || this.baseDirAt(positions[mid + 1], -1)))
3940
+ lo = mid + 1;
3930
3941
  }
3931
3942
  // If no element with y overlap is found, find the nearest element
3932
3943
  // on the y axis, move this.y into it, and retry the scan.
@@ -6978,6 +6989,8 @@ const baseTheme$1 = /*@__PURE__*/buildTheme("." + baseThemeID, {
6978
6989
  backgroundColor: "#f5f5f5",
6979
6990
  color: "black"
6980
6991
  },
6992
+ ".cm-panels-top": { top: "0" },
6993
+ ".cm-panels-bottom": { bottom: "0" },
6981
6994
  "&light .cm-panels-top": {
6982
6995
  borderBottom: "1px solid #ddd"
6983
6996
  },
@@ -9586,9 +9599,11 @@ const selectionLayer = /*@__PURE__*/layer({
9586
9599
  },
9587
9600
  class: "cm-selectionLayer"
9588
9601
  });
9602
+ // https://discuss.codemirror.net/t/firefox-153-ignores-transparent-selection-styling/9838
9603
+ const selectionBg = browser.gecko && browser.gecko_version == 153 ? "#ffffff01" : "transparent";
9589
9604
  const hideNativeSelection = /*@__PURE__*/Prec.highest(/*@__PURE__*/EditorView.theme({
9590
9605
  ".cm-line": {
9591
- "& ::selection, &::selection": { backgroundColor: "transparent !important" },
9606
+ "& ::selection, &::selection": { backgroundColor: `${selectionBg} !important` },
9592
9607
  caretColor: "transparent !important"
9593
9608
  },
9594
9609
  ".cm-content": {
@@ -11099,7 +11114,6 @@ class PanelGroup {
11099
11114
  if (!this.dom) {
11100
11115
  this.dom = document.createElement("div");
11101
11116
  this.dom.className = this.top ? "cm-panels cm-panels-top" : "cm-panels cm-panels-bottom";
11102
- this.dom.style[this.top ? "top" : "bottom"] = "0";
11103
11117
  let parent = this.container || this.view.dom;
11104
11118
  parent.insertBefore(this.dom, this.top ? parent.firstChild : null);
11105
11119
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "6.43.6",
3
+ "version": "6.43.8",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",