@codemirror/view 6.43.1 → 6.43.3

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,21 @@
1
+ ## 6.43.3 (2026-06-25)
2
+
3
+ ### Bug fixes
4
+
5
+ Fix a bug in the content DOM update code that could corrupt the editor state.
6
+
7
+ ## 6.43.2 (2026-06-23)
8
+
9
+ ### Bug fixes
10
+
11
+ Fix using select-all from the native context menu on Chrome Android.
12
+
13
+ On iOS when autocapitalize is enabled, ignore the shift modifier on virtual keyboard Enter or Backspace presses.
14
+
15
+ Work around an issue where Chrome Android scrolled the editor up when tapping an empty line to focus it.
16
+
17
+ Create undirectional selection ranges for double and triple clicks.
18
+
1
19
  ## 6.43.1 (2026-06-09)
2
20
 
3
21
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -2785,8 +2785,9 @@ class TileUpdate {
2785
2785
  }
2786
2786
  }
2787
2787
  });
2788
- b.addLineStartIfNotCovered(pendingLineAttrs);
2789
2788
  this.openWidget = openEnd > markCount;
2789
+ if (!this.openWidget)
2790
+ b.addLineStartIfNotCovered(pendingLineAttrs);
2790
2791
  this.openMarks = openEnd;
2791
2792
  }
2792
2793
  forward(from, to, side = 1) {
@@ -3611,7 +3612,7 @@ function groupAt(state$1, pos, bias = 1) {
3611
3612
  break;
3612
3613
  to = next;
3613
3614
  }
3614
- return state.EditorSelection.range(from + line.from, to + line.from);
3615
+ return state.EditorSelection.undirectionalRange(from + line.from, to + line.from);
3615
3616
  }
3616
3617
  function posAtCoordsImprecise(view, contentRect, block, x, y) {
3617
3618
  let into = Math.round((x - contentRect.left) * view.defaultCharacterWidth);
@@ -3748,8 +3749,12 @@ function skipAtomsForSelection(atoms, sel) {
3748
3749
  else {
3749
3750
  let from = skipAtomicRanges(atoms, range.from, -1);
3750
3751
  let to = skipAtomicRanges(atoms, range.to, 1);
3751
- if (from != range.from || to != range.to)
3752
- updated = state.EditorSelection.range(range.from == range.anchor ? from : to, range.from == range.head ? from : to);
3752
+ if (from != range.from || to != range.to) {
3753
+ if (range.undirectional)
3754
+ updated = state.EditorSelection.undirectionalRange(range.from, range.to);
3755
+ else
3756
+ updated = state.EditorSelection.range(range.from == range.anchor ? from : to, range.from == range.head ? from : to);
3757
+ }
3753
3758
  }
3754
3759
  if (updated) {
3755
3760
  if (!ranges)
@@ -4155,7 +4160,8 @@ class DOMChange {
4155
4160
  // Chrome will put the selection *inside* them, confusing
4156
4161
  // posFromDOM
4157
4162
  let vp = view.viewport;
4158
- if ((browser.ios || browser.chrome) && curSel.main.empty && head != anchor &&
4163
+ if ((browser.ios || browser.chrome) && head != anchor &&
4164
+ Math.min(head, anchor) <= curSel.main.from && Math.max(head, anchor) >= curSel.main.to &&
4159
4165
  (vp.from > 0 || vp.to < view.state.doc.length)) {
4160
4166
  let from = Math.min(head, anchor), to = Math.max(head, anchor);
4161
4167
  let offFrom = vp.from - from, offTo = vp.to - to;
@@ -4476,13 +4482,11 @@ class InputState {
4476
4482
  // (after which we retroactively handle them and reset the DOM) to
4477
4483
  // avoid messing up the virtual keyboard state.
4478
4484
  this.pendingIOSKey = undefined;
4479
- /**
4480
- When enabled (>-1), tab presses are not given to key handlers,
4481
- leaving the browser's default behavior. If >0, the mode expires
4482
- at that timestamp, and any other keypress clears it.
4483
- Esc enables temporary tab focus mode for two seconds when not
4484
- otherwise handled.
4485
- */
4485
+ // When enabled (>-1), tab presses are not given to key handlers,
4486
+ // leaving the browser's default behavior. If >0, the mode expires
4487
+ // at that timestamp, and any other keypress clears it.
4488
+ // Esc enables temporary tab focus mode for two seconds when not
4489
+ // otherwise handled.
4486
4490
  this.tabFocusMode = -1;
4487
4491
  this.lastSelectionOrigin = null;
4488
4492
  this.lastSelectionTime = 0;
@@ -4590,11 +4594,16 @@ class InputState {
4590
4594
  // state. So we let it go through, and then, in
4591
4595
  // applyDOMChange, notify key handlers of it and reset to
4592
4596
  // the state they produce.
4593
- let pending;
4594
- if (browser.ios && !event.synthetic && !event.altKey && !event.metaKey && !event.shiftKey &&
4595
- ((pending = PendingKeys.find(key => key.keyCode == event.keyCode)) && !event.ctrlKey ||
4597
+ if (browser.ios && !event.synthetic && !event.altKey && !event.metaKey &&
4598
+ (PendingKeys.some(key => key.keyCode == event.keyCode) && !event.ctrlKey ||
4596
4599
  EmacsyPendingKeys.indexOf(event.key) > -1 && event.ctrlKey)) {
4597
- this.pendingIOSKey = pending || event;
4600
+ let mods = { ctrlKey: event.ctrlKey, altKey: event.altKey, metaKey: event.metaKey, shiftKey: event.shiftKey };
4601
+ // On iOS with autocapitalize, drop the shift modifier for these
4602
+ // keys, since it will be set at the start of every sentence.
4603
+ if (mods.shiftKey && browser.ios && !/^(off|none)$/.test(this.view.contentDOM.autocapitalize) &&
4604
+ iosVirtualKeyboardOpen(this.view.win))
4605
+ mods.shiftKey = false;
4606
+ this.pendingIOSKey = { key: event.key, keyCode: event.keyCode, mods };
4598
4607
  setTimeout(() => this.flushIOSKey(), 250);
4599
4608
  return true;
4600
4609
  }
@@ -4610,7 +4619,7 @@ class InputState {
4610
4619
  if (key.key == "Enter" && change && change.from < change.to && /^\S+$/.test(change.insert.toString()))
4611
4620
  return false;
4612
4621
  this.pendingIOSKey = undefined;
4613
- return dispatchKey(this.view.contentDOM, key.key, key.keyCode, key instanceof KeyboardEvent ? key : undefined);
4622
+ return dispatchKey(this.view.contentDOM, key.key, key.keyCode, key.mods);
4614
4623
  }
4615
4624
  ignoreDuringComposition(event) {
4616
4625
  if (!/^key/.test(event.type) || event.synthetic)
@@ -4648,6 +4657,11 @@ class InputState {
4648
4657
  this.mouseSelection.destroy();
4649
4658
  }
4650
4659
  }
4660
+ function iosVirtualKeyboardOpen(win) {
4661
+ if (!win.visualViewport)
4662
+ return false;
4663
+ return win.visualViewport.height * win.visualViewport.scale / win.document.documentElement.clientHeight < 0.85;
4664
+ }
4651
4665
  function bindHandler(plugin, handler) {
4652
4666
  return (view, event) => {
4653
4667
  try {
@@ -4967,7 +4981,7 @@ function rangeForClick(view, pos, bias, type) {
4967
4981
  let from = visual ? visual.posAtStart : line.from, to = visual ? visual.posAtEnd : line.to;
4968
4982
  if (to < view.state.doc.length && to == line.to)
4969
4983
  to++;
4970
- return state.EditorSelection.range(from, to);
4984
+ return state.EditorSelection.undirectionalRange(from, to);
4971
4985
  }
4972
4986
  }
4973
4987
  const BadMouseDetail = browser.ie && browser.ie_version <= 11;
@@ -5025,7 +5039,7 @@ handlers.dragstart = (view, event) => {
5025
5039
  if (tile && tile.isWidget()) {
5026
5040
  let from = tile.posAtStart, to = from + tile.length;
5027
5041
  if (from >= range.to || to <= range.from)
5028
- range = state.EditorSelection.range(from, to);
5042
+ range = state.EditorSelection.undirectionalRange(from, to);
5029
5043
  }
5030
5044
  }
5031
5045
  let { inputState } = view;
@@ -7651,7 +7665,6 @@ class EditContextManager {
7651
7665
  for (let event in this.handlers)
7652
7666
  context.addEventListener(event, this.handlers[event]);
7653
7667
  this.measureReq = { read: view => {
7654
- this.editContext.updateControlBounds(view.contentDOM.getBoundingClientRect());
7655
7668
  let sel = getSelection(view.root);
7656
7669
  if (sel && sel.rangeCount)
7657
7670
  this.editContext.updateSelectionBounds(sel.getRangeAt(0).getBoundingClientRect());
package/dist/index.js CHANGED
@@ -2781,8 +2781,9 @@ class TileUpdate {
2781
2781
  }
2782
2782
  }
2783
2783
  });
2784
- b.addLineStartIfNotCovered(pendingLineAttrs);
2785
2784
  this.openWidget = openEnd > markCount;
2785
+ if (!this.openWidget)
2786
+ b.addLineStartIfNotCovered(pendingLineAttrs);
2786
2787
  this.openMarks = openEnd;
2787
2788
  }
2788
2789
  forward(from, to, side = 1) {
@@ -3607,7 +3608,7 @@ function groupAt(state, pos, bias = 1) {
3607
3608
  break;
3608
3609
  to = next;
3609
3610
  }
3610
- return EditorSelection.range(from + line.from, to + line.from);
3611
+ return EditorSelection.undirectionalRange(from + line.from, to + line.from);
3611
3612
  }
3612
3613
  function posAtCoordsImprecise(view, contentRect, block, x, y) {
3613
3614
  let into = Math.round((x - contentRect.left) * view.defaultCharacterWidth);
@@ -3744,8 +3745,12 @@ function skipAtomsForSelection(atoms, sel) {
3744
3745
  else {
3745
3746
  let from = skipAtomicRanges(atoms, range.from, -1);
3746
3747
  let to = skipAtomicRanges(atoms, range.to, 1);
3747
- if (from != range.from || to != range.to)
3748
- updated = EditorSelection.range(range.from == range.anchor ? from : to, range.from == range.head ? from : to);
3748
+ if (from != range.from || to != range.to) {
3749
+ if (range.undirectional)
3750
+ updated = EditorSelection.undirectionalRange(range.from, range.to);
3751
+ else
3752
+ updated = EditorSelection.range(range.from == range.anchor ? from : to, range.from == range.head ? from : to);
3753
+ }
3749
3754
  }
3750
3755
  if (updated) {
3751
3756
  if (!ranges)
@@ -4151,7 +4156,8 @@ class DOMChange {
4151
4156
  // Chrome will put the selection *inside* them, confusing
4152
4157
  // posFromDOM
4153
4158
  let vp = view.viewport;
4154
- if ((browser.ios || browser.chrome) && curSel.main.empty && head != anchor &&
4159
+ if ((browser.ios || browser.chrome) && head != anchor &&
4160
+ Math.min(head, anchor) <= curSel.main.from && Math.max(head, anchor) >= curSel.main.to &&
4155
4161
  (vp.from > 0 || vp.to < view.state.doc.length)) {
4156
4162
  let from = Math.min(head, anchor), to = Math.max(head, anchor);
4157
4163
  let offFrom = vp.from - from, offTo = vp.to - to;
@@ -4472,13 +4478,11 @@ class InputState {
4472
4478
  // (after which we retroactively handle them and reset the DOM) to
4473
4479
  // avoid messing up the virtual keyboard state.
4474
4480
  this.pendingIOSKey = undefined;
4475
- /**
4476
- When enabled (>-1), tab presses are not given to key handlers,
4477
- leaving the browser's default behavior. If >0, the mode expires
4478
- at that timestamp, and any other keypress clears it.
4479
- Esc enables temporary tab focus mode for two seconds when not
4480
- otherwise handled.
4481
- */
4481
+ // When enabled (>-1), tab presses are not given to key handlers,
4482
+ // leaving the browser's default behavior. If >0, the mode expires
4483
+ // at that timestamp, and any other keypress clears it.
4484
+ // Esc enables temporary tab focus mode for two seconds when not
4485
+ // otherwise handled.
4482
4486
  this.tabFocusMode = -1;
4483
4487
  this.lastSelectionOrigin = null;
4484
4488
  this.lastSelectionTime = 0;
@@ -4586,11 +4590,16 @@ class InputState {
4586
4590
  // state. So we let it go through, and then, in
4587
4591
  // applyDOMChange, notify key handlers of it and reset to
4588
4592
  // the state they produce.
4589
- let pending;
4590
- if (browser.ios && !event.synthetic && !event.altKey && !event.metaKey && !event.shiftKey &&
4591
- ((pending = PendingKeys.find(key => key.keyCode == event.keyCode)) && !event.ctrlKey ||
4593
+ if (browser.ios && !event.synthetic && !event.altKey && !event.metaKey &&
4594
+ (PendingKeys.some(key => key.keyCode == event.keyCode) && !event.ctrlKey ||
4592
4595
  EmacsyPendingKeys.indexOf(event.key) > -1 && event.ctrlKey)) {
4593
- this.pendingIOSKey = pending || event;
4596
+ let mods = { ctrlKey: event.ctrlKey, altKey: event.altKey, metaKey: event.metaKey, shiftKey: event.shiftKey };
4597
+ // On iOS with autocapitalize, drop the shift modifier for these
4598
+ // keys, since it will be set at the start of every sentence.
4599
+ if (mods.shiftKey && browser.ios && !/^(off|none)$/.test(this.view.contentDOM.autocapitalize) &&
4600
+ iosVirtualKeyboardOpen(this.view.win))
4601
+ mods.shiftKey = false;
4602
+ this.pendingIOSKey = { key: event.key, keyCode: event.keyCode, mods };
4594
4603
  setTimeout(() => this.flushIOSKey(), 250);
4595
4604
  return true;
4596
4605
  }
@@ -4606,7 +4615,7 @@ class InputState {
4606
4615
  if (key.key == "Enter" && change && change.from < change.to && /^\S+$/.test(change.insert.toString()))
4607
4616
  return false;
4608
4617
  this.pendingIOSKey = undefined;
4609
- return dispatchKey(this.view.contentDOM, key.key, key.keyCode, key instanceof KeyboardEvent ? key : undefined);
4618
+ return dispatchKey(this.view.contentDOM, key.key, key.keyCode, key.mods);
4610
4619
  }
4611
4620
  ignoreDuringComposition(event) {
4612
4621
  if (!/^key/.test(event.type) || event.synthetic)
@@ -4644,6 +4653,11 @@ class InputState {
4644
4653
  this.mouseSelection.destroy();
4645
4654
  }
4646
4655
  }
4656
+ function iosVirtualKeyboardOpen(win) {
4657
+ if (!win.visualViewport)
4658
+ return false;
4659
+ return win.visualViewport.height * win.visualViewport.scale / win.document.documentElement.clientHeight < 0.85;
4660
+ }
4647
4661
  function bindHandler(plugin, handler) {
4648
4662
  return (view, event) => {
4649
4663
  try {
@@ -4963,7 +4977,7 @@ function rangeForClick(view, pos, bias, type) {
4963
4977
  let from = visual ? visual.posAtStart : line.from, to = visual ? visual.posAtEnd : line.to;
4964
4978
  if (to < view.state.doc.length && to == line.to)
4965
4979
  to++;
4966
- return EditorSelection.range(from, to);
4980
+ return EditorSelection.undirectionalRange(from, to);
4967
4981
  }
4968
4982
  }
4969
4983
  const BadMouseDetail = browser.ie && browser.ie_version <= 11;
@@ -5021,7 +5035,7 @@ handlers.dragstart = (view, event) => {
5021
5035
  if (tile && tile.isWidget()) {
5022
5036
  let from = tile.posAtStart, to = from + tile.length;
5023
5037
  if (from >= range.to || to <= range.from)
5024
- range = EditorSelection.range(from, to);
5038
+ range = EditorSelection.undirectionalRange(from, to);
5025
5039
  }
5026
5040
  }
5027
5041
  let { inputState } = view;
@@ -7646,7 +7660,6 @@ class EditContextManager {
7646
7660
  for (let event in this.handlers)
7647
7661
  context.addEventListener(event, this.handlers[event]);
7648
7662
  this.measureReq = { read: view => {
7649
- this.editContext.updateControlBounds(view.contentDOM.getBoundingClientRect());
7650
7663
  let sel = getSelection(view.root);
7651
7664
  if (sel && sel.rangeCount)
7652
7665
  this.editContext.updateSelectionBounds(sel.getRangeAt(0).getBoundingClientRect());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "6.43.1",
3
+ "version": "6.43.3",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",
@@ -26,7 +26,7 @@
26
26
  "sideEffects": false,
27
27
  "license": "MIT",
28
28
  "dependencies": {
29
- "@codemirror/state": "^6.6.0",
29
+ "@codemirror/state": "^6.7.0",
30
30
  "crelt": "^1.0.6",
31
31
  "style-mod": "^4.1.0",
32
32
  "w3c-keyname": "^2.2.4"