@codemirror/view 6.39.9 → 6.39.11

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,19 @@
1
+ ## 6.39.11 (2026-01-14)
2
+
3
+ ### Bug fixes
4
+
5
+ Avoid handling copy events for parent editors.
6
+
7
+ ## 6.39.10 (2026-01-13)
8
+
9
+ ### Bug fixes
10
+
11
+ Fix a regression in the way widget are reused when content next to them changes.
12
+
13
+ Make sure font metrics get recomputed on `fonts.ready` even if the line height doesn't change.
14
+
15
+ Fix an issue where compositions next to a widget that create a new text node could get needlessly interrupted during an editor update.
16
+
1
17
  ## 6.39.9 (2026-01-06)
2
18
 
3
19
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -2502,8 +2502,6 @@ class TileCache {
2502
2502
  }
2503
2503
  // Put a tile in the cache.
2504
2504
  add(tile) {
2505
- if (tile.demo)
2506
- console.log("Add widget to cache");
2507
2505
  let i = tile.constructor.bucket, bucket = this.buckets[i];
2508
2506
  if (bucket.length < 6 /* C.Bucket */)
2509
2507
  bucket.push(tile);
@@ -2528,8 +2526,6 @@ class TileCache {
2528
2526
  }
2529
2527
  findWidget(widget, length, flags) {
2530
2528
  let widgets = this.buckets[0];
2531
- if (widget.demo)
2532
- console.log("looking for widget", widget, "in cache", widgets.slice());
2533
2529
  if (widgets.length)
2534
2530
  for (let i = 0, pass = 0;; i++) {
2535
2531
  if (i == widgets.length) {
@@ -2545,7 +2541,7 @@ class TileCache {
2545
2541
  widgets.splice(i, 1);
2546
2542
  if (i < this.index[0])
2547
2543
  this.index[0]--;
2548
- 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) {
2549
2545
  this.reused.set(tile, 1 /* Reused.Full */);
2550
2546
  return tile;
2551
2547
  }
@@ -2616,7 +2612,7 @@ class TileUpdate {
2616
2612
  // focused text node and its parent nodes to remain stable at
2617
2613
  // that point in the document.
2618
2614
  if (composition && next.fromA <= composition.range.fromA && next.toA >= composition.range.toA) {
2619
- this.forward(next.fromA, composition.range.fromA);
2615
+ this.forward(next.fromA, composition.range.fromA, composition.range.fromA < composition.range.toA ? 1 : -1);
2620
2616
  this.emit(posB, composition.range.fromB);
2621
2617
  this.cache.clear(); // Must not reuse DOM across composition
2622
2618
  this.builder.addComposition(composition, compositionContext);
@@ -2775,14 +2771,14 @@ class TileUpdate {
2775
2771
  this.openWidget = openEnd > markCount;
2776
2772
  this.openMarks = openEnd;
2777
2773
  }
2778
- forward(from, to) {
2774
+ forward(from, to, side = 1) {
2779
2775
  if (to - from <= 10) {
2780
- this.old.advance(to - from, 1, this.reuseWalker);
2776
+ this.old.advance(to - from, side, this.reuseWalker);
2781
2777
  }
2782
2778
  else {
2783
2779
  this.old.advance(5, -1, this.reuseWalker);
2784
2780
  this.old.advance(to - from - 10, -1);
2785
- this.old.advance(5, 1, this.reuseWalker);
2781
+ this.old.advance(5, side, this.reuseWalker);
2786
2782
  }
2787
2783
  }
2788
2784
  getCompositionContext(text) {
@@ -3678,11 +3674,8 @@ function moveVertically(view, start, forward, distance) {
3678
3674
  }
3679
3675
  let resolvedGoal = rect.left + goal;
3680
3676
  let dist = distance !== null && distance !== void 0 ? distance : (view.viewState.heightOracle.textHeight >> 1);
3681
- for (let extra = 0;; extra += 10) {
3682
- let curY = startY + (dist + extra) * dir;
3683
- let pos = posAtCoords(view, { x: resolvedGoal, y: curY }, false, dir);
3684
- return state.EditorSelection.cursor(pos.pos, pos.assoc, undefined, goal);
3685
- }
3677
+ let pos = posAtCoords(view, { x: resolvedGoal, y: startY + dist * dir }, false, dir);
3678
+ return state.EditorSelection.cursor(pos.pos, pos.assoc, undefined, goal);
3686
3679
  }
3687
3680
  function skipAtomicRanges(atoms, pos, bias) {
3688
3681
  for (;;) {
@@ -4092,7 +4085,7 @@ function applyDOMChange(view, domChange) {
4092
4085
  insert: state.Text.of(domChange.text.slice(diff.from, diff.toB).split(LineBreakPlaceholder)) };
4093
4086
  }
4094
4087
  }
4095
- else if (newSel && (!view.hasFocus && view.state.facet(editable) || newSel.main.eq(sel))) {
4088
+ else if (newSel && (!view.hasFocus && view.state.facet(editable) || sameSelPos(newSel, sel))) {
4096
4089
  newSel = null;
4097
4090
  }
4098
4091
  if (!change && !newSel)
@@ -4144,7 +4137,7 @@ function applyDOMChange(view, domChange) {
4144
4137
  if (change) {
4145
4138
  return applyDOMChangeInner(view, change, newSel, lastKey);
4146
4139
  }
4147
- else if (newSel && !newSel.main.eq(sel)) {
4140
+ else if (newSel && !sameSelPos(newSel, sel)) {
4148
4141
  let scrollIntoView = false, userEvent = "select";
4149
4142
  if (view.inputState.lastSelectionTime > Date.now() - 50) {
4150
4143
  if (view.inputState.lastSelectionOrigin == "select")
@@ -4315,6 +4308,9 @@ function selectionFromPoints(points, base) {
4315
4308
  let anchor = points[0].pos, head = points.length == 2 ? points[1].pos : anchor;
4316
4309
  return anchor > -1 && head > -1 ? state.EditorSelection.single(anchor + base, head + base) : null;
4317
4310
  }
4311
+ function sameSelPos(selection, range) {
4312
+ return range.head == selection.main.head && range.anchor == selection.main.anchor;
4313
+ }
4318
4314
 
4319
4315
  class InputState {
4320
4316
  setSelectionOrigin(origin) {
@@ -4997,6 +4993,14 @@ function copiedRange(state) {
4997
4993
  }
4998
4994
  let lastLinewiseCopy = null;
4999
4995
  handlers.copy = handlers.cut = (view, event) => {
4996
+ // If the DOM selection is outside this editor, don't intercept.
4997
+ // This happens when a parent editor (like ProseMirror) selects content that
4998
+ // spans multiple elements including this CodeMirror. The copy event may
4999
+ // bubble through CodeMirror (e.g. when CodeMirror is the first or the last
5000
+ // element in the selection), but we should let the parent handle it.
5001
+ let domSel = getSelection(view.root);
5002
+ if (domSel && !hasSelection(view.contentDOM, domSel))
5003
+ return false;
5000
5004
  let { text, ranges, linewise } = copiedRange(view.state);
5001
5005
  if (!text && !linewise)
5002
5006
  return false;
@@ -5204,7 +5208,8 @@ class HeightOracle {
5204
5208
  }
5205
5209
  refresh(whiteSpace, lineHeight, charWidth, textHeight, lineLength, knownHeights) {
5206
5210
  let lineWrapping = wrappingWhiteSpace.indexOf(whiteSpace) > -1;
5207
- let changed = Math.round(lineHeight) != Math.round(this.lineHeight) || this.lineWrapping != lineWrapping;
5211
+ let changed = Math.abs(lineHeight - this.lineHeight) > 0.3 || this.lineWrapping != lineWrapping ||
5212
+ Math.abs(charWidth - this.charWidth) > 0.1;
5208
5213
  this.lineWrapping = lineWrapping;
5209
5214
  this.lineHeight = lineHeight;
5210
5215
  this.charWidth = charWidth;
@@ -6118,7 +6123,7 @@ class ViewState {
6118
6123
  let oracle = this.heightOracle;
6119
6124
  let whiteSpace = style.whiteSpace;
6120
6125
  this.defaultTextDirection = style.direction == "rtl" ? exports.Direction.RTL : exports.Direction.LTR;
6121
- let refresh = this.heightOracle.mustRefreshForWrapping(whiteSpace);
6126
+ let refresh = this.heightOracle.mustRefreshForWrapping(whiteSpace) || this.mustMeasureContent;
6122
6127
  let domRect = dom.getBoundingClientRect();
6123
6128
  let measureContent = refresh || this.mustMeasureContent || this.contentDOMHeight != domRect.height;
6124
6129
  this.contentDOMHeight = domRect.height;
@@ -7229,7 +7234,7 @@ class DOMObserver {
7229
7234
  let handled = applyDOMChange(this.view, domChange);
7230
7235
  // The view wasn't updated but DOM/selection changes were seen. Reset the view.
7231
7236
  if (this.view.state == startState &&
7232
- (domChange.domChanged || domChange.newSel && !domChange.newSel.main.eq(this.view.state.selection.main)))
7237
+ (domChange.domChanged || domChange.newSel && !sameSelPos(this.view.state.selection, domChange.newSel.main)))
7233
7238
  this.view.update([]);
7234
7239
  return handled;
7235
7240
  }
@@ -7393,7 +7398,7 @@ class EditContextManager {
7393
7398
  // Edit contexts sometimes fire empty changes
7394
7399
  if (!diff) {
7395
7400
  let newSel = state.EditorSelection.single(this.toEditorPos(e.selectionStart), this.toEditorPos(e.selectionEnd));
7396
- if (!newSel.main.eq(main))
7401
+ if (!sameSelPos(newSel, main))
7397
7402
  view.dispatch({ selection: newSel, userEvent: "select" });
7398
7403
  return;
7399
7404
  }
@@ -7694,7 +7699,10 @@ class EditorView {
7694
7699
  this.updateState = 0 /* UpdateState.Idle */;
7695
7700
  this.requestMeasure();
7696
7701
  if ((_a = document.fonts) === null || _a === void 0 ? void 0 : _a.ready)
7697
- document.fonts.ready.then(() => this.requestMeasure());
7702
+ document.fonts.ready.then(() => {
7703
+ this.viewState.mustMeasureContent = true;
7704
+ this.requestMeasure();
7705
+ });
7698
7706
  }
7699
7707
  dispatch(...input) {
7700
7708
  let trs = input.length == 1 && input[0] instanceof state.Transaction ? input
package/dist/index.js CHANGED
@@ -2498,8 +2498,6 @@ class TileCache {
2498
2498
  }
2499
2499
  // Put a tile in the cache.
2500
2500
  add(tile) {
2501
- if (tile.demo)
2502
- console.log("Add widget to cache");
2503
2501
  let i = tile.constructor.bucket, bucket = this.buckets[i];
2504
2502
  if (bucket.length < 6 /* C.Bucket */)
2505
2503
  bucket.push(tile);
@@ -2524,8 +2522,6 @@ class TileCache {
2524
2522
  }
2525
2523
  findWidget(widget, length, flags) {
2526
2524
  let widgets = this.buckets[0];
2527
- if (widget.demo)
2528
- console.log("looking for widget", widget, "in cache", widgets.slice());
2529
2525
  if (widgets.length)
2530
2526
  for (let i = 0, pass = 0;; i++) {
2531
2527
  if (i == widgets.length) {
@@ -2541,7 +2537,7 @@ class TileCache {
2541
2537
  widgets.splice(i, 1);
2542
2538
  if (i < this.index[0])
2543
2539
  this.index[0]--;
2544
- 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) {
2545
2541
  this.reused.set(tile, 1 /* Reused.Full */);
2546
2542
  return tile;
2547
2543
  }
@@ -2612,7 +2608,7 @@ class TileUpdate {
2612
2608
  // focused text node and its parent nodes to remain stable at
2613
2609
  // that point in the document.
2614
2610
  if (composition && next.fromA <= composition.range.fromA && next.toA >= composition.range.toA) {
2615
- this.forward(next.fromA, composition.range.fromA);
2611
+ this.forward(next.fromA, composition.range.fromA, composition.range.fromA < composition.range.toA ? 1 : -1);
2616
2612
  this.emit(posB, composition.range.fromB);
2617
2613
  this.cache.clear(); // Must not reuse DOM across composition
2618
2614
  this.builder.addComposition(composition, compositionContext);
@@ -2771,14 +2767,14 @@ class TileUpdate {
2771
2767
  this.openWidget = openEnd > markCount;
2772
2768
  this.openMarks = openEnd;
2773
2769
  }
2774
- forward(from, to) {
2770
+ forward(from, to, side = 1) {
2775
2771
  if (to - from <= 10) {
2776
- this.old.advance(to - from, 1, this.reuseWalker);
2772
+ this.old.advance(to - from, side, this.reuseWalker);
2777
2773
  }
2778
2774
  else {
2779
2775
  this.old.advance(5, -1, this.reuseWalker);
2780
2776
  this.old.advance(to - from - 10, -1);
2781
- this.old.advance(5, 1, this.reuseWalker);
2777
+ this.old.advance(5, side, this.reuseWalker);
2782
2778
  }
2783
2779
  }
2784
2780
  getCompositionContext(text) {
@@ -3674,11 +3670,8 @@ function moveVertically(view, start, forward, distance) {
3674
3670
  }
3675
3671
  let resolvedGoal = rect.left + goal;
3676
3672
  let dist = distance !== null && distance !== void 0 ? distance : (view.viewState.heightOracle.textHeight >> 1);
3677
- for (let extra = 0;; extra += 10) {
3678
- let curY = startY + (dist + extra) * dir;
3679
- let pos = posAtCoords(view, { x: resolvedGoal, y: curY }, false, dir);
3680
- return EditorSelection.cursor(pos.pos, pos.assoc, undefined, goal);
3681
- }
3673
+ let pos = posAtCoords(view, { x: resolvedGoal, y: startY + dist * dir }, false, dir);
3674
+ return EditorSelection.cursor(pos.pos, pos.assoc, undefined, goal);
3682
3675
  }
3683
3676
  function skipAtomicRanges(atoms, pos, bias) {
3684
3677
  for (;;) {
@@ -4088,7 +4081,7 @@ function applyDOMChange(view, domChange) {
4088
4081
  insert: Text.of(domChange.text.slice(diff.from, diff.toB).split(LineBreakPlaceholder)) };
4089
4082
  }
4090
4083
  }
4091
- else if (newSel && (!view.hasFocus && view.state.facet(editable) || newSel.main.eq(sel))) {
4084
+ else if (newSel && (!view.hasFocus && view.state.facet(editable) || sameSelPos(newSel, sel))) {
4092
4085
  newSel = null;
4093
4086
  }
4094
4087
  if (!change && !newSel)
@@ -4140,7 +4133,7 @@ function applyDOMChange(view, domChange) {
4140
4133
  if (change) {
4141
4134
  return applyDOMChangeInner(view, change, newSel, lastKey);
4142
4135
  }
4143
- else if (newSel && !newSel.main.eq(sel)) {
4136
+ else if (newSel && !sameSelPos(newSel, sel)) {
4144
4137
  let scrollIntoView = false, userEvent = "select";
4145
4138
  if (view.inputState.lastSelectionTime > Date.now() - 50) {
4146
4139
  if (view.inputState.lastSelectionOrigin == "select")
@@ -4311,6 +4304,9 @@ function selectionFromPoints(points, base) {
4311
4304
  let anchor = points[0].pos, head = points.length == 2 ? points[1].pos : anchor;
4312
4305
  return anchor > -1 && head > -1 ? EditorSelection.single(anchor + base, head + base) : null;
4313
4306
  }
4307
+ function sameSelPos(selection, range) {
4308
+ return range.head == selection.main.head && range.anchor == selection.main.anchor;
4309
+ }
4314
4310
 
4315
4311
  class InputState {
4316
4312
  setSelectionOrigin(origin) {
@@ -4993,6 +4989,14 @@ function copiedRange(state) {
4993
4989
  }
4994
4990
  let lastLinewiseCopy = null;
4995
4991
  handlers.copy = handlers.cut = (view, event) => {
4992
+ // If the DOM selection is outside this editor, don't intercept.
4993
+ // This happens when a parent editor (like ProseMirror) selects content that
4994
+ // spans multiple elements including this CodeMirror. The copy event may
4995
+ // bubble through CodeMirror (e.g. when CodeMirror is the first or the last
4996
+ // element in the selection), but we should let the parent handle it.
4997
+ let domSel = getSelection(view.root);
4998
+ if (domSel && !hasSelection(view.contentDOM, domSel))
4999
+ return false;
4996
5000
  let { text, ranges, linewise } = copiedRange(view.state);
4997
5001
  if (!text && !linewise)
4998
5002
  return false;
@@ -5200,7 +5204,8 @@ class HeightOracle {
5200
5204
  }
5201
5205
  refresh(whiteSpace, lineHeight, charWidth, textHeight, lineLength, knownHeights) {
5202
5206
  let lineWrapping = wrappingWhiteSpace.indexOf(whiteSpace) > -1;
5203
- let changed = Math.round(lineHeight) != Math.round(this.lineHeight) || this.lineWrapping != lineWrapping;
5207
+ let changed = Math.abs(lineHeight - this.lineHeight) > 0.3 || this.lineWrapping != lineWrapping ||
5208
+ Math.abs(charWidth - this.charWidth) > 0.1;
5204
5209
  this.lineWrapping = lineWrapping;
5205
5210
  this.lineHeight = lineHeight;
5206
5211
  this.charWidth = charWidth;
@@ -6113,7 +6118,7 @@ class ViewState {
6113
6118
  let oracle = this.heightOracle;
6114
6119
  let whiteSpace = style.whiteSpace;
6115
6120
  this.defaultTextDirection = style.direction == "rtl" ? Direction.RTL : Direction.LTR;
6116
- let refresh = this.heightOracle.mustRefreshForWrapping(whiteSpace);
6121
+ let refresh = this.heightOracle.mustRefreshForWrapping(whiteSpace) || this.mustMeasureContent;
6117
6122
  let domRect = dom.getBoundingClientRect();
6118
6123
  let measureContent = refresh || this.mustMeasureContent || this.contentDOMHeight != domRect.height;
6119
6124
  this.contentDOMHeight = domRect.height;
@@ -7224,7 +7229,7 @@ class DOMObserver {
7224
7229
  let handled = applyDOMChange(this.view, domChange);
7225
7230
  // The view wasn't updated but DOM/selection changes were seen. Reset the view.
7226
7231
  if (this.view.state == startState &&
7227
- (domChange.domChanged || domChange.newSel && !domChange.newSel.main.eq(this.view.state.selection.main)))
7232
+ (domChange.domChanged || domChange.newSel && !sameSelPos(this.view.state.selection, domChange.newSel.main)))
7228
7233
  this.view.update([]);
7229
7234
  return handled;
7230
7235
  }
@@ -7388,7 +7393,7 @@ class EditContextManager {
7388
7393
  // Edit contexts sometimes fire empty changes
7389
7394
  if (!diff) {
7390
7395
  let newSel = EditorSelection.single(this.toEditorPos(e.selectionStart), this.toEditorPos(e.selectionEnd));
7391
- if (!newSel.main.eq(main))
7396
+ if (!sameSelPos(newSel, main))
7392
7397
  view.dispatch({ selection: newSel, userEvent: "select" });
7393
7398
  return;
7394
7399
  }
@@ -7689,7 +7694,10 @@ class EditorView {
7689
7694
  this.updateState = 0 /* UpdateState.Idle */;
7690
7695
  this.requestMeasure();
7691
7696
  if ((_a = document.fonts) === null || _a === void 0 ? void 0 : _a.ready)
7692
- document.fonts.ready.then(() => this.requestMeasure());
7697
+ document.fonts.ready.then(() => {
7698
+ this.viewState.mustMeasureContent = true;
7699
+ this.requestMeasure();
7700
+ });
7693
7701
  }
7694
7702
  dispatch(...input) {
7695
7703
  let trs = input.length == 1 && input[0] instanceof Transaction ? input
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "6.39.9",
3
+ "version": "6.39.11",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",