@codemirror/view 6.39.10 → 6.39.12

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.12 (2026-01-30)
2
+
3
+ ### Bug fixes
4
+
5
+ Fix a bug where the visual selection drawn by `drawSelection` could fail to update properly in some circumstances.
6
+
7
+ Fix a bug where PageUp/PageDown near the edge of the viewport might completely skip to the start/end of the document.
8
+
9
+ Fix a regression that caused mark decorations to be split on text node chunk boundaries again.
10
+
11
+ ## 6.39.11 (2026-01-14)
12
+
13
+ ### Bug fixes
14
+
15
+ Avoid handling copy events for parent editors.
16
+
1
17
  ## 6.39.10 (2026-01-13)
2
18
 
3
19
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -2266,7 +2266,7 @@ class TileBuilder {
2266
2266
  this.flushBuffer();
2267
2267
  let parent = this.ensureMarks(marks, openStart);
2268
2268
  let prev = parent.lastChild;
2269
- if (prev && prev.isText() && !(prev.flags & 8 /* TileFlag.Composition */)) {
2269
+ if (prev && prev.isText() && !(prev.flags & 8 /* TileFlag.Composition */) && prev.length + text.length < 512 /* C.Chunk */) {
2270
2270
  this.cache.reused.set(prev, 2 /* Reused.DOM */);
2271
2271
  let tile = parent.children[parent.children.length - 1] = new TextTile(prev.dom, prev.text + text);
2272
2272
  tile.parent = parent;
@@ -2760,7 +2760,7 @@ class TileUpdate {
2760
2760
  }
2761
2761
  else {
2762
2762
  b.ensureLine(pendingLineAttrs);
2763
- b.addText(chars, active, openStart);
2763
+ b.addText(chars, active, pos == from ? openStart : active.length);
2764
2764
  pos += chars.length;
2765
2765
  }
2766
2766
  pendingLineAttrs = null;
@@ -3674,11 +3674,8 @@ function moveVertically(view, start, forward, distance) {
3674
3674
  }
3675
3675
  let resolvedGoal = rect.left + goal;
3676
3676
  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 state.EditorSelection.cursor(pos.pos, pos.assoc, undefined, goal);
3681
- }
3677
+ let pos = posAtCoords(view, { x: resolvedGoal, y: startY + dist * dir }, false, dir);
3678
+ return state.EditorSelection.cursor(pos.pos, pos.assoc, undefined, goal);
3682
3679
  }
3683
3680
  function skipAtomicRanges(atoms, pos, bias) {
3684
3681
  for (;;) {
@@ -3744,7 +3741,9 @@ function posAtCoords(view, coords, precise, scanY) {
3744
3741
  if (scanY == null)
3745
3742
  break;
3746
3743
  if (block.type == exports.BlockType.Text) {
3747
- // Check whether we aren't landing the top/bottom padding of the line
3744
+ if (scanY < 0 ? block.to < view.viewport.from : block.from > view.viewport.to)
3745
+ break;
3746
+ // Check whether we aren't landing on the top/bottom padding of the line
3748
3747
  let rect = view.docView.coordsAt(scanY < 0 ? block.from : block.to, scanY);
3749
3748
  if (rect && (scanY < 0 ? rect.top <= yOffset + docTop : rect.bottom >= yOffset + docTop))
3750
3749
  break;
@@ -4088,7 +4087,7 @@ function applyDOMChange(view, domChange) {
4088
4087
  insert: state.Text.of(domChange.text.slice(diff.from, diff.toB).split(LineBreakPlaceholder)) };
4089
4088
  }
4090
4089
  }
4091
- else if (newSel && (!view.hasFocus && view.state.facet(editable) || newSel.main.eq(sel))) {
4090
+ else if (newSel && (!view.hasFocus && view.state.facet(editable) || sameSelPos(newSel, sel))) {
4092
4091
  newSel = null;
4093
4092
  }
4094
4093
  if (!change && !newSel)
@@ -4140,7 +4139,7 @@ function applyDOMChange(view, domChange) {
4140
4139
  if (change) {
4141
4140
  return applyDOMChangeInner(view, change, newSel, lastKey);
4142
4141
  }
4143
- else if (newSel && !newSel.main.eq(sel)) {
4142
+ else if (newSel && !sameSelPos(newSel, sel)) {
4144
4143
  let scrollIntoView = false, userEvent = "select";
4145
4144
  if (view.inputState.lastSelectionTime > Date.now() - 50) {
4146
4145
  if (view.inputState.lastSelectionOrigin == "select")
@@ -4311,6 +4310,9 @@ function selectionFromPoints(points, base) {
4311
4310
  let anchor = points[0].pos, head = points.length == 2 ? points[1].pos : anchor;
4312
4311
  return anchor > -1 && head > -1 ? state.EditorSelection.single(anchor + base, head + base) : null;
4313
4312
  }
4313
+ function sameSelPos(selection, range) {
4314
+ return range.head == selection.main.head && range.anchor == selection.main.anchor;
4315
+ }
4314
4316
 
4315
4317
  class InputState {
4316
4318
  setSelectionOrigin(origin) {
@@ -4993,6 +4995,14 @@ function copiedRange(state) {
4993
4995
  }
4994
4996
  let lastLinewiseCopy = null;
4995
4997
  handlers.copy = handlers.cut = (view, event) => {
4998
+ // If the DOM selection is outside this editor, don't intercept.
4999
+ // This happens when a parent editor (like ProseMirror) selects content that
5000
+ // spans multiple elements including this CodeMirror. The copy event may
5001
+ // bubble through CodeMirror (e.g. when CodeMirror is the first or the last
5002
+ // element in the selection), but we should let the parent handle it.
5003
+ let domSel = getSelection(view.root);
5004
+ if (domSel && !hasSelection(view.contentDOM, domSel))
5005
+ return false;
4996
5006
  let { text, ranges, linewise } = copiedRange(view.state);
4997
5007
  if (!text && !linewise)
4998
5008
  return false;
@@ -7226,7 +7236,7 @@ class DOMObserver {
7226
7236
  let handled = applyDOMChange(this.view, domChange);
7227
7237
  // The view wasn't updated but DOM/selection changes were seen. Reset the view.
7228
7238
  if (this.view.state == startState &&
7229
- (domChange.domChanged || domChange.newSel && !domChange.newSel.main.eq(this.view.state.selection.main)))
7239
+ (domChange.domChanged || domChange.newSel && !sameSelPos(this.view.state.selection, domChange.newSel.main)))
7230
7240
  this.view.update([]);
7231
7241
  return handled;
7232
7242
  }
@@ -7390,7 +7400,7 @@ class EditContextManager {
7390
7400
  // Edit contexts sometimes fire empty changes
7391
7401
  if (!diff) {
7392
7402
  let newSel = state.EditorSelection.single(this.toEditorPos(e.selectionStart), this.toEditorPos(e.selectionEnd));
7393
- if (!newSel.main.eq(main))
7403
+ if (!sameSelPos(newSel, main))
7394
7404
  view.dispatch({ selection: newSel, userEvent: "select" });
7395
7405
  return;
7396
7406
  }
@@ -9090,7 +9100,7 @@ function rectanglesForRange(view, className, range) {
9090
9100
  return pieces(top).concat(between).concat(pieces(bottom));
9091
9101
  }
9092
9102
  function piece(left, top, right, bottom) {
9093
- return new RectangleMarker(className, left - base.left, top - base.top, right - left, bottom - top);
9103
+ return new RectangleMarker(className, left - base.left, top - base.top, Math.max(0, right - left), bottom - top);
9094
9104
  }
9095
9105
  function pieces({ top, bottom, horizontal }) {
9096
9106
  let pieces = [];
package/dist/index.js CHANGED
@@ -2262,7 +2262,7 @@ class TileBuilder {
2262
2262
  this.flushBuffer();
2263
2263
  let parent = this.ensureMarks(marks, openStart);
2264
2264
  let prev = parent.lastChild;
2265
- if (prev && prev.isText() && !(prev.flags & 8 /* TileFlag.Composition */)) {
2265
+ if (prev && prev.isText() && !(prev.flags & 8 /* TileFlag.Composition */) && prev.length + text.length < 512 /* C.Chunk */) {
2266
2266
  this.cache.reused.set(prev, 2 /* Reused.DOM */);
2267
2267
  let tile = parent.children[parent.children.length - 1] = new TextTile(prev.dom, prev.text + text);
2268
2268
  tile.parent = parent;
@@ -2756,7 +2756,7 @@ class TileUpdate {
2756
2756
  }
2757
2757
  else {
2758
2758
  b.ensureLine(pendingLineAttrs);
2759
- b.addText(chars, active, openStart);
2759
+ b.addText(chars, active, pos == from ? openStart : active.length);
2760
2760
  pos += chars.length;
2761
2761
  }
2762
2762
  pendingLineAttrs = null;
@@ -3670,11 +3670,8 @@ function moveVertically(view, start, forward, distance) {
3670
3670
  }
3671
3671
  let resolvedGoal = rect.left + goal;
3672
3672
  let dist = distance !== null && distance !== void 0 ? distance : (view.viewState.heightOracle.textHeight >> 1);
3673
- for (let extra = 0;; extra += 10) {
3674
- let curY = startY + (dist + extra) * dir;
3675
- let pos = posAtCoords(view, { x: resolvedGoal, y: curY }, false, dir);
3676
- return EditorSelection.cursor(pos.pos, pos.assoc, undefined, goal);
3677
- }
3673
+ let pos = posAtCoords(view, { x: resolvedGoal, y: startY + dist * dir }, false, dir);
3674
+ return EditorSelection.cursor(pos.pos, pos.assoc, undefined, goal);
3678
3675
  }
3679
3676
  function skipAtomicRanges(atoms, pos, bias) {
3680
3677
  for (;;) {
@@ -3740,7 +3737,9 @@ function posAtCoords(view, coords, precise, scanY) {
3740
3737
  if (scanY == null)
3741
3738
  break;
3742
3739
  if (block.type == BlockType.Text) {
3743
- // Check whether we aren't landing the top/bottom padding of the line
3740
+ if (scanY < 0 ? block.to < view.viewport.from : block.from > view.viewport.to)
3741
+ break;
3742
+ // Check whether we aren't landing on the top/bottom padding of the line
3744
3743
  let rect = view.docView.coordsAt(scanY < 0 ? block.from : block.to, scanY);
3745
3744
  if (rect && (scanY < 0 ? rect.top <= yOffset + docTop : rect.bottom >= yOffset + docTop))
3746
3745
  break;
@@ -4084,7 +4083,7 @@ function applyDOMChange(view, domChange) {
4084
4083
  insert: Text.of(domChange.text.slice(diff.from, diff.toB).split(LineBreakPlaceholder)) };
4085
4084
  }
4086
4085
  }
4087
- else if (newSel && (!view.hasFocus && view.state.facet(editable) || newSel.main.eq(sel))) {
4086
+ else if (newSel && (!view.hasFocus && view.state.facet(editable) || sameSelPos(newSel, sel))) {
4088
4087
  newSel = null;
4089
4088
  }
4090
4089
  if (!change && !newSel)
@@ -4136,7 +4135,7 @@ function applyDOMChange(view, domChange) {
4136
4135
  if (change) {
4137
4136
  return applyDOMChangeInner(view, change, newSel, lastKey);
4138
4137
  }
4139
- else if (newSel && !newSel.main.eq(sel)) {
4138
+ else if (newSel && !sameSelPos(newSel, sel)) {
4140
4139
  let scrollIntoView = false, userEvent = "select";
4141
4140
  if (view.inputState.lastSelectionTime > Date.now() - 50) {
4142
4141
  if (view.inputState.lastSelectionOrigin == "select")
@@ -4307,6 +4306,9 @@ function selectionFromPoints(points, base) {
4307
4306
  let anchor = points[0].pos, head = points.length == 2 ? points[1].pos : anchor;
4308
4307
  return anchor > -1 && head > -1 ? EditorSelection.single(anchor + base, head + base) : null;
4309
4308
  }
4309
+ function sameSelPos(selection, range) {
4310
+ return range.head == selection.main.head && range.anchor == selection.main.anchor;
4311
+ }
4310
4312
 
4311
4313
  class InputState {
4312
4314
  setSelectionOrigin(origin) {
@@ -4989,6 +4991,14 @@ function copiedRange(state) {
4989
4991
  }
4990
4992
  let lastLinewiseCopy = null;
4991
4993
  handlers.copy = handlers.cut = (view, event) => {
4994
+ // If the DOM selection is outside this editor, don't intercept.
4995
+ // This happens when a parent editor (like ProseMirror) selects content that
4996
+ // spans multiple elements including this CodeMirror. The copy event may
4997
+ // bubble through CodeMirror (e.g. when CodeMirror is the first or the last
4998
+ // element in the selection), but we should let the parent handle it.
4999
+ let domSel = getSelection(view.root);
5000
+ if (domSel && !hasSelection(view.contentDOM, domSel))
5001
+ return false;
4992
5002
  let { text, ranges, linewise } = copiedRange(view.state);
4993
5003
  if (!text && !linewise)
4994
5004
  return false;
@@ -7221,7 +7231,7 @@ class DOMObserver {
7221
7231
  let handled = applyDOMChange(this.view, domChange);
7222
7232
  // The view wasn't updated but DOM/selection changes were seen. Reset the view.
7223
7233
  if (this.view.state == startState &&
7224
- (domChange.domChanged || domChange.newSel && !domChange.newSel.main.eq(this.view.state.selection.main)))
7234
+ (domChange.domChanged || domChange.newSel && !sameSelPos(this.view.state.selection, domChange.newSel.main)))
7225
7235
  this.view.update([]);
7226
7236
  return handled;
7227
7237
  }
@@ -7385,7 +7395,7 @@ class EditContextManager {
7385
7395
  // Edit contexts sometimes fire empty changes
7386
7396
  if (!diff) {
7387
7397
  let newSel = EditorSelection.single(this.toEditorPos(e.selectionStart), this.toEditorPos(e.selectionEnd));
7388
- if (!newSel.main.eq(main))
7398
+ if (!sameSelPos(newSel, main))
7389
7399
  view.dispatch({ selection: newSel, userEvent: "select" });
7390
7400
  return;
7391
7401
  }
@@ -9085,7 +9095,7 @@ function rectanglesForRange(view, className, range) {
9085
9095
  return pieces(top).concat(between).concat(pieces(bottom));
9086
9096
  }
9087
9097
  function piece(left, top, right, bottom) {
9088
- return new RectangleMarker(className, left - base.left, top - base.top, right - left, bottom - top);
9098
+ return new RectangleMarker(className, left - base.left, top - base.top, Math.max(0, right - left), bottom - top);
9089
9099
  }
9090
9100
  function pieces({ top, bottom, horizontal }) {
9091
9101
  let pieces = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "6.39.10",
3
+ "version": "6.39.12",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",