@codemirror/view 6.39.8 → 6.39.10

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,25 @@
1
+ ## 6.39.10 (2026-01-13)
2
+
3
+ ### Bug fixes
4
+
5
+ Fix a regression in the way widget are reused when content next to them changes.
6
+
7
+ Make sure font metrics get recomputed on `fonts.ready` even if the line height doesn't change.
8
+
9
+ Fix an issue where compositions next to a widget that create a new text node could get needlessly interrupted during an editor update.
10
+
11
+ ## 6.39.9 (2026-01-06)
12
+
13
+ ### Bug fixes
14
+
15
+ Fix a bug where `EditorSelection.cursor()` with a non-zero `assoc` value would not be visually respected at soft-wrap boundaries on initial view creation.
16
+
17
+ Fix error caused by hover tooltips running a scheduled timeout after their editor has been destroyed.
18
+
19
+ Fix a bug that caused `EditorView.outerDecorations` to not affect the content height map.
20
+
21
+ Fix an issue where composition near a widget could get unnecessarily interrupted.
22
+
1
23
  ## 6.39.8 (2025-12-30)
2
24
 
3
25
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -2541,7 +2541,7 @@ class TileCache {
2541
2541
  widgets.splice(i, 1);
2542
2542
  if (i < this.index[0])
2543
2543
  this.index[0]--;
2544
- if (tile.length == length && (tile.flags & (496 /* TileFlag.Widget */ | 1 /* TileFlag.BreakAfter */)) == flags) {
2544
+ if (tile.widget == widget && tile.length == length && (tile.flags & (496 /* TileFlag.Widget */ | 1 /* TileFlag.BreakAfter */)) == flags) {
2545
2545
  this.reused.set(tile, 1 /* Reused.Full */);
2546
2546
  return tile;
2547
2547
  }
@@ -2562,6 +2562,10 @@ class TileCache {
2562
2562
  this.reused.set(tile, type);
2563
2563
  return tile.dom;
2564
2564
  }
2565
+ clear() {
2566
+ for (let i = 0; i < this.buckets.length; i++)
2567
+ this.buckets[i].length = this.index[i] = 0;
2568
+ }
2565
2569
  }
2566
2570
  // This class organizes a pass over the document, guided by the array
2567
2571
  // of replaced ranges. For ranges that haven't changed, it iterates
@@ -2604,17 +2608,20 @@ class TileUpdate {
2604
2608
  }
2605
2609
  if (!next)
2606
2610
  break;
2607
- this.forward(next.fromA, next.toA);
2608
2611
  // Compositions need to be handled specially, forcing the
2609
2612
  // focused text node and its parent nodes to remain stable at
2610
2613
  // that point in the document.
2611
2614
  if (composition && next.fromA <= composition.range.fromA && next.toA >= composition.range.toA) {
2615
+ this.forward(next.fromA, composition.range.fromA, composition.range.fromA < composition.range.toA ? 1 : -1);
2612
2616
  this.emit(posB, composition.range.fromB);
2617
+ this.cache.clear(); // Must not reuse DOM across composition
2613
2618
  this.builder.addComposition(composition, compositionContext);
2614
2619
  this.text.skip(composition.range.toB - composition.range.fromB);
2620
+ this.forward(composition.range.fromA, next.toA);
2615
2621
  this.emit(composition.range.toB, next.toB);
2616
2622
  }
2617
2623
  else {
2624
+ this.forward(next.fromA, next.toA);
2618
2625
  this.emit(posB, next.toB);
2619
2626
  }
2620
2627
  posB = next.toB;
@@ -2764,14 +2771,14 @@ class TileUpdate {
2764
2771
  this.openWidget = openEnd > markCount;
2765
2772
  this.openMarks = openEnd;
2766
2773
  }
2767
- forward(from, to) {
2774
+ forward(from, to, side = 1) {
2768
2775
  if (to - from <= 10) {
2769
- this.old.advance(to - from, 1, this.reuseWalker);
2776
+ this.old.advance(to - from, side, this.reuseWalker);
2770
2777
  }
2771
2778
  else {
2772
2779
  this.old.advance(5, -1, this.reuseWalker);
2773
2780
  this.old.advance(to - from - 10, -1);
2774
- this.old.advance(5, 1, this.reuseWalker);
2781
+ this.old.advance(5, side, this.reuseWalker);
2775
2782
  }
2776
2783
  }
2777
2784
  getCompositionContext(text) {
@@ -5193,7 +5200,8 @@ class HeightOracle {
5193
5200
  }
5194
5201
  refresh(whiteSpace, lineHeight, charWidth, textHeight, lineLength, knownHeights) {
5195
5202
  let lineWrapping = wrappingWhiteSpace.indexOf(whiteSpace) > -1;
5196
- let changed = Math.round(lineHeight) != Math.round(this.lineHeight) || this.lineWrapping != lineWrapping;
5203
+ let changed = Math.abs(lineHeight - this.lineHeight) > 0.3 || this.lineWrapping != lineWrapping ||
5204
+ Math.abs(charWidth - this.charWidth) > 0.1;
5197
5205
  this.lineWrapping = lineWrapping;
5198
5206
  this.lineHeight = lineHeight;
5199
5207
  this.charWidth = charWidth;
@@ -6027,7 +6035,7 @@ class ViewState {
6027
6035
  this.mustEnforceCursorAssoc = false;
6028
6036
  let guessWrapping = state$1.facet(contentAttributes).some(v => typeof v != "function" && v.class == "cm-lineWrapping");
6029
6037
  this.heightOracle = new HeightOracle(guessWrapping);
6030
- this.stateDeco = state$1.facet(decorations).filter(d => typeof d != "function");
6038
+ this.stateDeco = staticDeco(state$1);
6031
6039
  this.heightMap = HeightMap.empty().applyChanges(this.stateDeco, state.Text.empty, this.heightOracle.setDoc(state$1.doc), [new ChangedRange(0, 0, 0, state$1.doc.length)]);
6032
6040
  for (let i = 0; i < 2; i++) {
6033
6041
  this.viewport = this.getViewport(0, null);
@@ -6066,7 +6074,7 @@ class ViewState {
6066
6074
  update(update, scrollTarget = null) {
6067
6075
  this.state = update.state;
6068
6076
  let prevDeco = this.stateDeco;
6069
- this.stateDeco = this.state.facet(decorations).filter(d => typeof d != "function");
6077
+ this.stateDeco = staticDeco(this.state);
6070
6078
  let contentChanges = update.changedRanges;
6071
6079
  let heightChanges = ChangedRange.extendWithRanges(contentChanges, heightRelevantDecoChanges(prevDeco, this.stateDeco, update ? update.changes : state.ChangeSet.empty(this.state.doc.length)));
6072
6080
  let prevHeight = this.heightMap.height;
@@ -6097,7 +6105,7 @@ class ViewState {
6097
6105
  update.flags |= this.computeVisibleRanges(update.changes);
6098
6106
  if (scrollTarget)
6099
6107
  this.scrollTarget = scrollTarget;
6100
- if (!this.mustEnforceCursorAssoc && update.selectionSet && update.view.lineWrapping &&
6108
+ if (!this.mustEnforceCursorAssoc && (update.selectionSet || update.focusChanged) && update.view.lineWrapping &&
6101
6109
  update.state.selection.main.empty && update.state.selection.main.assoc &&
6102
6110
  !update.state.facet(nativeSelectionHidden))
6103
6111
  this.mustEnforceCursorAssoc = true;
@@ -6107,7 +6115,7 @@ class ViewState {
6107
6115
  let oracle = this.heightOracle;
6108
6116
  let whiteSpace = style.whiteSpace;
6109
6117
  this.defaultTextDirection = style.direction == "rtl" ? exports.Direction.RTL : exports.Direction.LTR;
6110
- let refresh = this.heightOracle.mustRefreshForWrapping(whiteSpace);
6118
+ let refresh = this.heightOracle.mustRefreshForWrapping(whiteSpace) || this.mustMeasureContent;
6111
6119
  let domRect = dom.getBoundingClientRect();
6112
6120
  let measureContent = refresh || this.mustMeasureContent || this.contentDOMHeight != domRect.height;
6113
6121
  this.contentDOMHeight = domRect.height;
@@ -6495,6 +6503,13 @@ const IdScaler = {
6495
6503
  scale: 1,
6496
6504
  eq(other) { return other == this; }
6497
6505
  };
6506
+ function staticDeco(state$1) {
6507
+ let deco = state$1.facet(decorations).filter(d => typeof d != "function");
6508
+ let outer = state$1.facet(outerDecorations).filter(d => typeof d != "function");
6509
+ if (outer.length)
6510
+ deco.push(state.RangeSet.join(outer));
6511
+ return deco;
6512
+ }
6498
6513
  // When the height is too big (> VP.MaxDOMHeight), scale down the
6499
6514
  // regions outside the viewports so that the total height is
6500
6515
  // VP.MaxDOMHeight.
@@ -7676,7 +7691,10 @@ class EditorView {
7676
7691
  this.updateState = 0 /* UpdateState.Idle */;
7677
7692
  this.requestMeasure();
7678
7693
  if ((_a = document.fonts) === null || _a === void 0 ? void 0 : _a.ready)
7679
- document.fonts.ready.then(() => this.requestMeasure());
7694
+ document.fonts.ready.then(() => {
7695
+ this.viewState.mustMeasureContent = true;
7696
+ this.requestMeasure();
7697
+ });
7680
7698
  }
7681
7699
  dispatch(...input) {
7682
7700
  let trs = input.length == 1 && input[0] instanceof state.Transaction ? input
@@ -10524,6 +10542,7 @@ class HoverPlugin {
10524
10542
  }
10525
10543
  destroy() {
10526
10544
  clearTimeout(this.hoverTimeout);
10545
+ clearTimeout(this.restartTimeout);
10527
10546
  this.view.dom.removeEventListener("mouseleave", this.mouseleave);
10528
10547
  this.view.dom.removeEventListener("mousemove", this.mousemove);
10529
10548
  }
package/dist/index.js CHANGED
@@ -2537,7 +2537,7 @@ class TileCache {
2537
2537
  widgets.splice(i, 1);
2538
2538
  if (i < this.index[0])
2539
2539
  this.index[0]--;
2540
- if (tile.length == length && (tile.flags & (496 /* TileFlag.Widget */ | 1 /* TileFlag.BreakAfter */)) == flags) {
2540
+ if (tile.widget == widget && tile.length == length && (tile.flags & (496 /* TileFlag.Widget */ | 1 /* TileFlag.BreakAfter */)) == flags) {
2541
2541
  this.reused.set(tile, 1 /* Reused.Full */);
2542
2542
  return tile;
2543
2543
  }
@@ -2558,6 +2558,10 @@ class TileCache {
2558
2558
  this.reused.set(tile, type);
2559
2559
  return tile.dom;
2560
2560
  }
2561
+ clear() {
2562
+ for (let i = 0; i < this.buckets.length; i++)
2563
+ this.buckets[i].length = this.index[i] = 0;
2564
+ }
2561
2565
  }
2562
2566
  // This class organizes a pass over the document, guided by the array
2563
2567
  // of replaced ranges. For ranges that haven't changed, it iterates
@@ -2600,17 +2604,20 @@ class TileUpdate {
2600
2604
  }
2601
2605
  if (!next)
2602
2606
  break;
2603
- this.forward(next.fromA, next.toA);
2604
2607
  // Compositions need to be handled specially, forcing the
2605
2608
  // focused text node and its parent nodes to remain stable at
2606
2609
  // that point in the document.
2607
2610
  if (composition && next.fromA <= composition.range.fromA && next.toA >= composition.range.toA) {
2611
+ this.forward(next.fromA, composition.range.fromA, composition.range.fromA < composition.range.toA ? 1 : -1);
2608
2612
  this.emit(posB, composition.range.fromB);
2613
+ this.cache.clear(); // Must not reuse DOM across composition
2609
2614
  this.builder.addComposition(composition, compositionContext);
2610
2615
  this.text.skip(composition.range.toB - composition.range.fromB);
2616
+ this.forward(composition.range.fromA, next.toA);
2611
2617
  this.emit(composition.range.toB, next.toB);
2612
2618
  }
2613
2619
  else {
2620
+ this.forward(next.fromA, next.toA);
2614
2621
  this.emit(posB, next.toB);
2615
2622
  }
2616
2623
  posB = next.toB;
@@ -2760,14 +2767,14 @@ class TileUpdate {
2760
2767
  this.openWidget = openEnd > markCount;
2761
2768
  this.openMarks = openEnd;
2762
2769
  }
2763
- forward(from, to) {
2770
+ forward(from, to, side = 1) {
2764
2771
  if (to - from <= 10) {
2765
- this.old.advance(to - from, 1, this.reuseWalker);
2772
+ this.old.advance(to - from, side, this.reuseWalker);
2766
2773
  }
2767
2774
  else {
2768
2775
  this.old.advance(5, -1, this.reuseWalker);
2769
2776
  this.old.advance(to - from - 10, -1);
2770
- this.old.advance(5, 1, this.reuseWalker);
2777
+ this.old.advance(5, side, this.reuseWalker);
2771
2778
  }
2772
2779
  }
2773
2780
  getCompositionContext(text) {
@@ -5189,7 +5196,8 @@ class HeightOracle {
5189
5196
  }
5190
5197
  refresh(whiteSpace, lineHeight, charWidth, textHeight, lineLength, knownHeights) {
5191
5198
  let lineWrapping = wrappingWhiteSpace.indexOf(whiteSpace) > -1;
5192
- let changed = Math.round(lineHeight) != Math.round(this.lineHeight) || this.lineWrapping != lineWrapping;
5199
+ let changed = Math.abs(lineHeight - this.lineHeight) > 0.3 || this.lineWrapping != lineWrapping ||
5200
+ Math.abs(charWidth - this.charWidth) > 0.1;
5193
5201
  this.lineWrapping = lineWrapping;
5194
5202
  this.lineHeight = lineHeight;
5195
5203
  this.charWidth = charWidth;
@@ -6022,7 +6030,7 @@ class ViewState {
6022
6030
  this.mustEnforceCursorAssoc = false;
6023
6031
  let guessWrapping = state.facet(contentAttributes).some(v => typeof v != "function" && v.class == "cm-lineWrapping");
6024
6032
  this.heightOracle = new HeightOracle(guessWrapping);
6025
- this.stateDeco = state.facet(decorations).filter(d => typeof d != "function");
6033
+ this.stateDeco = staticDeco(state);
6026
6034
  this.heightMap = HeightMap.empty().applyChanges(this.stateDeco, Text.empty, this.heightOracle.setDoc(state.doc), [new ChangedRange(0, 0, 0, state.doc.length)]);
6027
6035
  for (let i = 0; i < 2; i++) {
6028
6036
  this.viewport = this.getViewport(0, null);
@@ -6061,7 +6069,7 @@ class ViewState {
6061
6069
  update(update, scrollTarget = null) {
6062
6070
  this.state = update.state;
6063
6071
  let prevDeco = this.stateDeco;
6064
- this.stateDeco = this.state.facet(decorations).filter(d => typeof d != "function");
6072
+ this.stateDeco = staticDeco(this.state);
6065
6073
  let contentChanges = update.changedRanges;
6066
6074
  let heightChanges = ChangedRange.extendWithRanges(contentChanges, heightRelevantDecoChanges(prevDeco, this.stateDeco, update ? update.changes : ChangeSet.empty(this.state.doc.length)));
6067
6075
  let prevHeight = this.heightMap.height;
@@ -6092,7 +6100,7 @@ class ViewState {
6092
6100
  update.flags |= this.computeVisibleRanges(update.changes);
6093
6101
  if (scrollTarget)
6094
6102
  this.scrollTarget = scrollTarget;
6095
- if (!this.mustEnforceCursorAssoc && update.selectionSet && update.view.lineWrapping &&
6103
+ if (!this.mustEnforceCursorAssoc && (update.selectionSet || update.focusChanged) && update.view.lineWrapping &&
6096
6104
  update.state.selection.main.empty && update.state.selection.main.assoc &&
6097
6105
  !update.state.facet(nativeSelectionHidden))
6098
6106
  this.mustEnforceCursorAssoc = true;
@@ -6102,7 +6110,7 @@ class ViewState {
6102
6110
  let oracle = this.heightOracle;
6103
6111
  let whiteSpace = style.whiteSpace;
6104
6112
  this.defaultTextDirection = style.direction == "rtl" ? Direction.RTL : Direction.LTR;
6105
- let refresh = this.heightOracle.mustRefreshForWrapping(whiteSpace);
6113
+ let refresh = this.heightOracle.mustRefreshForWrapping(whiteSpace) || this.mustMeasureContent;
6106
6114
  let domRect = dom.getBoundingClientRect();
6107
6115
  let measureContent = refresh || this.mustMeasureContent || this.contentDOMHeight != domRect.height;
6108
6116
  this.contentDOMHeight = domRect.height;
@@ -6490,6 +6498,13 @@ const IdScaler = {
6490
6498
  scale: 1,
6491
6499
  eq(other) { return other == this; }
6492
6500
  };
6501
+ function staticDeco(state) {
6502
+ let deco = state.facet(decorations).filter(d => typeof d != "function");
6503
+ let outer = state.facet(outerDecorations).filter(d => typeof d != "function");
6504
+ if (outer.length)
6505
+ deco.push(RangeSet.join(outer));
6506
+ return deco;
6507
+ }
6493
6508
  // When the height is too big (> VP.MaxDOMHeight), scale down the
6494
6509
  // regions outside the viewports so that the total height is
6495
6510
  // VP.MaxDOMHeight.
@@ -7671,7 +7686,10 @@ class EditorView {
7671
7686
  this.updateState = 0 /* UpdateState.Idle */;
7672
7687
  this.requestMeasure();
7673
7688
  if ((_a = document.fonts) === null || _a === void 0 ? void 0 : _a.ready)
7674
- document.fonts.ready.then(() => this.requestMeasure());
7689
+ document.fonts.ready.then(() => {
7690
+ this.viewState.mustMeasureContent = true;
7691
+ this.requestMeasure();
7692
+ });
7675
7693
  }
7676
7694
  dispatch(...input) {
7677
7695
  let trs = input.length == 1 && input[0] instanceof Transaction ? input
@@ -10519,6 +10537,7 @@ class HoverPlugin {
10519
10537
  }
10520
10538
  destroy() {
10521
10539
  clearTimeout(this.hoverTimeout);
10540
+ clearTimeout(this.restartTimeout);
10522
10541
  this.view.dom.removeEventListener("mouseleave", this.mouseleave);
10523
10542
  this.view.dom.removeEventListener("mousemove", this.mousemove);
10524
10543
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "6.39.8",
3
+ "version": "6.39.10",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",
@@ -36,6 +36,6 @@
36
36
  },
37
37
  "repository": {
38
38
  "type": "git",
39
- "url": "https://github.com/codemirror/view.git"
39
+ "url": "git+https://github.com/codemirror/view.git"
40
40
  }
41
41
  }