@codemirror/view 6.11.0 → 6.11.2

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,20 @@
1
+ ## 6.11.2 (2023-05-13)
2
+
3
+ ### Bug fixes
4
+
5
+ Fix a bug where the `crosshairCursor` extension could, when non-native key events were fired, trigger disruptive and needless view updates.
6
+
7
+ Fix an Android issue where backspacing at the front of a line with widget decorations could replace those decorations with their text content.
8
+
9
+ Respect scroll margins when scrolling the target of drag-selection into view.
10
+
11
+ Validate selection offsets reported by the browser, to work around Safari giving us invalid values in some cases.
12
+
13
+ ## 6.11.1 (2023-05-09)
14
+
15
+ ### Bug fixes
16
+
17
+ Don't preserve the DOM around a composition that spans multiple lines.
1
18
  ## 6.11.0 (2023-05-03)
2
19
 
3
20
  ### New features
package/dist/index.cjs CHANGED
@@ -222,7 +222,9 @@ class DOMSelectionState {
222
222
  this.focusNode == domSel.focusNode && this.focusOffset == domSel.focusOffset;
223
223
  }
224
224
  setRange(range) {
225
- this.set(range.anchorNode, range.anchorOffset, range.focusNode, range.focusOffset);
225
+ let { anchorNode, focusNode } = range;
226
+ // Clip offsets to node size to avoid crashes when Safari reports bogus offsets (#1152)
227
+ this.set(anchorNode, Math.min(range.anchorOffset, anchorNode ? maxOffset(anchorNode) : 0), focusNode, Math.min(range.focusOffset, focusNode ? maxOffset(focusNode) : 0));
226
228
  }
227
229
  set(anchorNode, anchorOffset, focusNode, focusOffset) {
228
230
  this.anchorNode = anchorNode;
@@ -295,6 +297,8 @@ function atElementStart(doc, selection) {
295
297
  let node = selection.focusNode, offset = selection.focusOffset;
296
298
  if (!node || selection.anchorNode != node || selection.anchorOffset != offset)
297
299
  return false;
300
+ // Safari can report bogus offsets (#1152)
301
+ offset = Math.min(offset, maxOffset(node));
298
302
  for (;;) {
299
303
  if (offset) {
300
304
  if (node.nodeType != 1)
@@ -2007,6 +2011,23 @@ const contentAttributes = state.Facet.define();
2007
2011
  const decorations = state.Facet.define();
2008
2012
  const atomicRanges = state.Facet.define();
2009
2013
  const scrollMargins = state.Facet.define();
2014
+ function getScrollMargins(view) {
2015
+ let left = 0, right = 0, top = 0, bottom = 0;
2016
+ for (let source of view.state.facet(scrollMargins)) {
2017
+ let m = source(view);
2018
+ if (m) {
2019
+ if (m.left != null)
2020
+ left = Math.max(left, m.left);
2021
+ if (m.right != null)
2022
+ right = Math.max(right, m.right);
2023
+ if (m.top != null)
2024
+ top = Math.max(top, m.top);
2025
+ if (m.bottom != null)
2026
+ bottom = Math.max(bottom, m.bottom);
2027
+ }
2028
+ }
2029
+ return { left, right, top, bottom };
2030
+ }
2010
2031
  const styleModule = state.Facet.define();
2011
2032
  class ChangedRange {
2012
2033
  constructor(fromA, toA, fromB, toB) {
@@ -2938,22 +2959,10 @@ class DocView extends ContentView {
2938
2959
  if (!range.empty && (other = this.coordsAt(range.anchor, range.anchor > range.head ? -1 : 1)))
2939
2960
  rect = { left: Math.min(rect.left, other.left), top: Math.min(rect.top, other.top),
2940
2961
  right: Math.max(rect.right, other.right), bottom: Math.max(rect.bottom, other.bottom) };
2941
- let mLeft = 0, mRight = 0, mTop = 0, mBottom = 0;
2942
- for (let margins of this.view.state.facet(scrollMargins).map(f => f(this.view)))
2943
- if (margins) {
2944
- let { left, right, top, bottom } = margins;
2945
- if (left != null)
2946
- mLeft = Math.max(mLeft, left);
2947
- if (right != null)
2948
- mRight = Math.max(mRight, right);
2949
- if (top != null)
2950
- mTop = Math.max(mTop, top);
2951
- if (bottom != null)
2952
- mBottom = Math.max(mBottom, bottom);
2953
- }
2962
+ let margins = getScrollMargins(this.view);
2954
2963
  let targetRect = {
2955
- left: rect.left - mLeft, top: rect.top - mTop,
2956
- right: rect.right + mRight, bottom: rect.bottom + mBottom
2964
+ left: rect.left - margins.left, top: rect.top - margins.top,
2965
+ right: rect.right + margins.right, bottom: rect.bottom + margins.bottom
2957
2966
  };
2958
2967
  scrollRectIntoView(this.view.scrollDOM, targetRect, range.head < range.anchor ? -1 : 1, target.x, target.y, target.xMargin, target.yMargin, this.view.textDirection == exports.Direction.LTR);
2959
2968
  }
@@ -3019,15 +3028,17 @@ function computeCompositionDeco(view, changes) {
3019
3028
  let newFrom = changes.mapPos(from, 1), newTo = Math.max(newFrom, changes.mapPos(to, -1));
3020
3029
  let { state } = view, text = node.nodeType == 3 ? node.nodeValue :
3021
3030
  new DOMReader([], state).readRange(node.firstChild, null).text;
3031
+ if (text.indexOf(LineBreakPlaceholder) > -1)
3032
+ return Decoration.none; // Don't try to preserve multi-line compositions
3022
3033
  if (newTo - newFrom < text.length) {
3023
- if (state.doc.sliceString(newFrom, Math.min(state.doc.length, newFrom + text.length), LineBreakPlaceholder) == text)
3034
+ if (state.doc.sliceString(newFrom, Math.min(state.doc.length, newFrom + text.length)) == text)
3024
3035
  newTo = newFrom + text.length;
3025
- else if (state.doc.sliceString(Math.max(0, newTo - text.length), newTo, LineBreakPlaceholder) == text)
3036
+ else if (state.doc.sliceString(Math.max(0, newTo - text.length), newTo) == text)
3026
3037
  newFrom = newTo - text.length;
3027
3038
  else
3028
3039
  return Decoration.none;
3029
3040
  }
3030
- else if (state.doc.sliceString(newFrom, newTo, LineBreakPlaceholder) != text) {
3041
+ else if (state.doc.sliceString(newFrom, newTo) != text) {
3031
3042
  return Decoration.none;
3032
3043
  }
3033
3044
  let topView = ContentView.get(node);
@@ -3731,13 +3742,14 @@ class MouseSelection {
3731
3742
  let sx = 0, sy = 0;
3732
3743
  let rect = ((_a = this.scrollParent) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect())
3733
3744
  || { left: 0, top: 0, right: this.view.win.innerWidth, bottom: this.view.win.innerHeight };
3734
- if (event.clientX <= rect.left + dragScrollMargin)
3745
+ let margins = getScrollMargins(this.view);
3746
+ if (event.clientX - margins.left <= rect.left + dragScrollMargin)
3735
3747
  sx = -dragScrollSpeed(rect.left - event.clientX);
3736
- else if (event.clientX >= rect.right - dragScrollMargin)
3748
+ else if (event.clientX + margins.right >= rect.right - dragScrollMargin)
3737
3749
  sx = dragScrollSpeed(event.clientX - rect.right);
3738
- if (event.clientY <= rect.top + dragScrollMargin)
3750
+ if (event.clientY - margins.top <= rect.top + dragScrollMargin)
3739
3751
  sy = -dragScrollSpeed(rect.top - event.clientY);
3740
- else if (event.clientY >= rect.bottom - dragScrollMargin)
3752
+ else if (event.clientY + margins.bottom >= rect.bottom - dragScrollMargin)
3741
3753
  sy = dragScrollSpeed(event.clientY - rect.bottom);
3742
3754
  this.setScrollSpeed(sx, sy);
3743
3755
  }
@@ -5779,13 +5791,13 @@ class DOMChange {
5779
5791
  function applyDOMChange(view, domChange) {
5780
5792
  let change;
5781
5793
  let { newSel } = domChange, sel = view.state.selection.main;
5794
+ let lastKey = view.inputState.lastKeyTime > Date.now() - 100 ? view.inputState.lastKeyCode : -1;
5782
5795
  if (domChange.bounds) {
5783
5796
  let { from, to } = domChange.bounds;
5784
5797
  let preferredPos = sel.from, preferredSide = null;
5785
5798
  // Prefer anchoring to end when Backspace is pressed (or, on
5786
5799
  // Android, when something was deleted)
5787
- if (view.inputState.lastKeyCode === 8 && view.inputState.lastKeyTime > Date.now() - 100 ||
5788
- browser.android && domChange.text.length < to - from) {
5800
+ if (lastKey === 8 || browser.android && domChange.text.length < to - from) {
5789
5801
  preferredPos = sel.to;
5790
5802
  preferredSide = "end";
5791
5803
  }
@@ -5793,7 +5805,7 @@ function applyDOMChange(view, domChange) {
5793
5805
  if (diff) {
5794
5806
  // Chrome inserts two newlines when pressing shift-enter at the
5795
5807
  // end of a line. DomChange drops one of those.
5796
- if (browser.chrome && view.inputState.lastKeyCode == 13 &&
5808
+ if (browser.chrome && lastKey == 13 &&
5797
5809
  diff.toB == diff.from + 2 && domChange.text.slice(diff.from, diff.toB) == LineBreakPlaceholder + LineBreakPlaceholder)
5798
5810
  diff.toB--;
5799
5811
  change = { from: from + diff.from, to: from + diff.toA,
@@ -5851,7 +5863,8 @@ function applyDOMChange(view, domChange) {
5851
5863
  ((change.from == sel.from && change.to == sel.to &&
5852
5864
  change.insert.length == 1 && change.insert.lines == 2 &&
5853
5865
  dispatchKey(view.contentDOM, "Enter", 13)) ||
5854
- (change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 &&
5866
+ ((change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 ||
5867
+ lastKey == 8 && change.insert.length < change.to - change.from) &&
5855
5868
  dispatchKey(view.contentDOM, "Backspace", 8)) ||
5856
5869
  (change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
5857
5870
  dispatchKey(view.contentDOM, "Delete", 46))))
@@ -6249,7 +6262,10 @@ class DOMObserver {
6249
6262
  let key = this.delayedAndroidKey;
6250
6263
  if (key) {
6251
6264
  this.clearDelayedAndroidKey();
6252
- if (!this.flush() && key.force)
6265
+ this.view.inputState.lastKeyCode = key.keyCode;
6266
+ this.view.inputState.lastKeyTime = Date.now();
6267
+ let flushed = this.flush();
6268
+ if (!flushed && key.force)
6253
6269
  dispatchKey(this.dom, key.key, key.keyCode);
6254
6270
  }
6255
6271
  };
@@ -8478,10 +8494,10 @@ function rectangularSelection(options) {
8478
8494
  return EditorView.mouseSelectionStyle.of((view, event) => filter(event) ? rectangleSelectionStyle(view, event) : null);
8479
8495
  }
8480
8496
  const keys = {
8481
- Alt: [18, e => e.altKey],
8482
- Control: [17, e => e.ctrlKey],
8483
- Shift: [16, e => e.shiftKey],
8484
- Meta: [91, e => e.metaKey]
8497
+ Alt: [18, e => !!e.altKey],
8498
+ Control: [17, e => !!e.ctrlKey],
8499
+ Shift: [16, e => !!e.shiftKey],
8500
+ Meta: [91, e => !!e.metaKey]
8485
8501
  };
8486
8502
  const showCrosshair = { style: "cursor: crosshair" };
8487
8503
  /**
package/dist/index.js CHANGED
@@ -218,7 +218,9 @@ class DOMSelectionState {
218
218
  this.focusNode == domSel.focusNode && this.focusOffset == domSel.focusOffset;
219
219
  }
220
220
  setRange(range) {
221
- this.set(range.anchorNode, range.anchorOffset, range.focusNode, range.focusOffset);
221
+ let { anchorNode, focusNode } = range;
222
+ // Clip offsets to node size to avoid crashes when Safari reports bogus offsets (#1152)
223
+ this.set(anchorNode, Math.min(range.anchorOffset, anchorNode ? maxOffset(anchorNode) : 0), focusNode, Math.min(range.focusOffset, focusNode ? maxOffset(focusNode) : 0));
222
224
  }
223
225
  set(anchorNode, anchorOffset, focusNode, focusOffset) {
224
226
  this.anchorNode = anchorNode;
@@ -291,6 +293,8 @@ function atElementStart(doc, selection) {
291
293
  let node = selection.focusNode, offset = selection.focusOffset;
292
294
  if (!node || selection.anchorNode != node || selection.anchorOffset != offset)
293
295
  return false;
296
+ // Safari can report bogus offsets (#1152)
297
+ offset = Math.min(offset, maxOffset(node));
294
298
  for (;;) {
295
299
  if (offset) {
296
300
  if (node.nodeType != 1)
@@ -2002,6 +2006,23 @@ const contentAttributes = /*@__PURE__*/Facet.define();
2002
2006
  const decorations = /*@__PURE__*/Facet.define();
2003
2007
  const atomicRanges = /*@__PURE__*/Facet.define();
2004
2008
  const scrollMargins = /*@__PURE__*/Facet.define();
2009
+ function getScrollMargins(view) {
2010
+ let left = 0, right = 0, top = 0, bottom = 0;
2011
+ for (let source of view.state.facet(scrollMargins)) {
2012
+ let m = source(view);
2013
+ if (m) {
2014
+ if (m.left != null)
2015
+ left = Math.max(left, m.left);
2016
+ if (m.right != null)
2017
+ right = Math.max(right, m.right);
2018
+ if (m.top != null)
2019
+ top = Math.max(top, m.top);
2020
+ if (m.bottom != null)
2021
+ bottom = Math.max(bottom, m.bottom);
2022
+ }
2023
+ }
2024
+ return { left, right, top, bottom };
2025
+ }
2005
2026
  const styleModule = /*@__PURE__*/Facet.define();
2006
2027
  class ChangedRange {
2007
2028
  constructor(fromA, toA, fromB, toB) {
@@ -2932,22 +2953,10 @@ class DocView extends ContentView {
2932
2953
  if (!range.empty && (other = this.coordsAt(range.anchor, range.anchor > range.head ? -1 : 1)))
2933
2954
  rect = { left: Math.min(rect.left, other.left), top: Math.min(rect.top, other.top),
2934
2955
  right: Math.max(rect.right, other.right), bottom: Math.max(rect.bottom, other.bottom) };
2935
- let mLeft = 0, mRight = 0, mTop = 0, mBottom = 0;
2936
- for (let margins of this.view.state.facet(scrollMargins).map(f => f(this.view)))
2937
- if (margins) {
2938
- let { left, right, top, bottom } = margins;
2939
- if (left != null)
2940
- mLeft = Math.max(mLeft, left);
2941
- if (right != null)
2942
- mRight = Math.max(mRight, right);
2943
- if (top != null)
2944
- mTop = Math.max(mTop, top);
2945
- if (bottom != null)
2946
- mBottom = Math.max(mBottom, bottom);
2947
- }
2956
+ let margins = getScrollMargins(this.view);
2948
2957
  let targetRect = {
2949
- left: rect.left - mLeft, top: rect.top - mTop,
2950
- right: rect.right + mRight, bottom: rect.bottom + mBottom
2958
+ left: rect.left - margins.left, top: rect.top - margins.top,
2959
+ right: rect.right + margins.right, bottom: rect.bottom + margins.bottom
2951
2960
  };
2952
2961
  scrollRectIntoView(this.view.scrollDOM, targetRect, range.head < range.anchor ? -1 : 1, target.x, target.y, target.xMargin, target.yMargin, this.view.textDirection == Direction.LTR);
2953
2962
  }
@@ -3013,15 +3022,17 @@ function computeCompositionDeco(view, changes) {
3013
3022
  let newFrom = changes.mapPos(from, 1), newTo = Math.max(newFrom, changes.mapPos(to, -1));
3014
3023
  let { state } = view, text = node.nodeType == 3 ? node.nodeValue :
3015
3024
  new DOMReader([], state).readRange(node.firstChild, null).text;
3025
+ if (text.indexOf(LineBreakPlaceholder) > -1)
3026
+ return Decoration.none; // Don't try to preserve multi-line compositions
3016
3027
  if (newTo - newFrom < text.length) {
3017
- if (state.doc.sliceString(newFrom, Math.min(state.doc.length, newFrom + text.length), LineBreakPlaceholder) == text)
3028
+ if (state.doc.sliceString(newFrom, Math.min(state.doc.length, newFrom + text.length)) == text)
3018
3029
  newTo = newFrom + text.length;
3019
- else if (state.doc.sliceString(Math.max(0, newTo - text.length), newTo, LineBreakPlaceholder) == text)
3030
+ else if (state.doc.sliceString(Math.max(0, newTo - text.length), newTo) == text)
3020
3031
  newFrom = newTo - text.length;
3021
3032
  else
3022
3033
  return Decoration.none;
3023
3034
  }
3024
- else if (state.doc.sliceString(newFrom, newTo, LineBreakPlaceholder) != text) {
3035
+ else if (state.doc.sliceString(newFrom, newTo) != text) {
3025
3036
  return Decoration.none;
3026
3037
  }
3027
3038
  let topView = ContentView.get(node);
@@ -3725,13 +3736,14 @@ class MouseSelection {
3725
3736
  let sx = 0, sy = 0;
3726
3737
  let rect = ((_a = this.scrollParent) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect())
3727
3738
  || { left: 0, top: 0, right: this.view.win.innerWidth, bottom: this.view.win.innerHeight };
3728
- if (event.clientX <= rect.left + dragScrollMargin)
3739
+ let margins = getScrollMargins(this.view);
3740
+ if (event.clientX - margins.left <= rect.left + dragScrollMargin)
3729
3741
  sx = -dragScrollSpeed(rect.left - event.clientX);
3730
- else if (event.clientX >= rect.right - dragScrollMargin)
3742
+ else if (event.clientX + margins.right >= rect.right - dragScrollMargin)
3731
3743
  sx = dragScrollSpeed(event.clientX - rect.right);
3732
- if (event.clientY <= rect.top + dragScrollMargin)
3744
+ if (event.clientY - margins.top <= rect.top + dragScrollMargin)
3733
3745
  sy = -dragScrollSpeed(rect.top - event.clientY);
3734
- else if (event.clientY >= rect.bottom - dragScrollMargin)
3746
+ else if (event.clientY + margins.bottom >= rect.bottom - dragScrollMargin)
3735
3747
  sy = dragScrollSpeed(event.clientY - rect.bottom);
3736
3748
  this.setScrollSpeed(sx, sy);
3737
3749
  }
@@ -5772,13 +5784,13 @@ class DOMChange {
5772
5784
  function applyDOMChange(view, domChange) {
5773
5785
  let change;
5774
5786
  let { newSel } = domChange, sel = view.state.selection.main;
5787
+ let lastKey = view.inputState.lastKeyTime > Date.now() - 100 ? view.inputState.lastKeyCode : -1;
5775
5788
  if (domChange.bounds) {
5776
5789
  let { from, to } = domChange.bounds;
5777
5790
  let preferredPos = sel.from, preferredSide = null;
5778
5791
  // Prefer anchoring to end when Backspace is pressed (or, on
5779
5792
  // Android, when something was deleted)
5780
- if (view.inputState.lastKeyCode === 8 && view.inputState.lastKeyTime > Date.now() - 100 ||
5781
- browser.android && domChange.text.length < to - from) {
5793
+ if (lastKey === 8 || browser.android && domChange.text.length < to - from) {
5782
5794
  preferredPos = sel.to;
5783
5795
  preferredSide = "end";
5784
5796
  }
@@ -5786,7 +5798,7 @@ function applyDOMChange(view, domChange) {
5786
5798
  if (diff) {
5787
5799
  // Chrome inserts two newlines when pressing shift-enter at the
5788
5800
  // end of a line. DomChange drops one of those.
5789
- if (browser.chrome && view.inputState.lastKeyCode == 13 &&
5801
+ if (browser.chrome && lastKey == 13 &&
5790
5802
  diff.toB == diff.from + 2 && domChange.text.slice(diff.from, diff.toB) == LineBreakPlaceholder + LineBreakPlaceholder)
5791
5803
  diff.toB--;
5792
5804
  change = { from: from + diff.from, to: from + diff.toA,
@@ -5844,7 +5856,8 @@ function applyDOMChange(view, domChange) {
5844
5856
  ((change.from == sel.from && change.to == sel.to &&
5845
5857
  change.insert.length == 1 && change.insert.lines == 2 &&
5846
5858
  dispatchKey(view.contentDOM, "Enter", 13)) ||
5847
- (change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 &&
5859
+ ((change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 ||
5860
+ lastKey == 8 && change.insert.length < change.to - change.from) &&
5848
5861
  dispatchKey(view.contentDOM, "Backspace", 8)) ||
5849
5862
  (change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
5850
5863
  dispatchKey(view.contentDOM, "Delete", 46))))
@@ -6242,7 +6255,10 @@ class DOMObserver {
6242
6255
  let key = this.delayedAndroidKey;
6243
6256
  if (key) {
6244
6257
  this.clearDelayedAndroidKey();
6245
- if (!this.flush() && key.force)
6258
+ this.view.inputState.lastKeyCode = key.keyCode;
6259
+ this.view.inputState.lastKeyTime = Date.now();
6260
+ let flushed = this.flush();
6261
+ if (!flushed && key.force)
6246
6262
  dispatchKey(this.dom, key.key, key.keyCode);
6247
6263
  }
6248
6264
  };
@@ -8471,10 +8487,10 @@ function rectangularSelection(options) {
8471
8487
  return EditorView.mouseSelectionStyle.of((view, event) => filter(event) ? rectangleSelectionStyle(view, event) : null);
8472
8488
  }
8473
8489
  const keys = {
8474
- Alt: [18, e => e.altKey],
8475
- Control: [17, e => e.ctrlKey],
8476
- Shift: [16, e => e.shiftKey],
8477
- Meta: [91, e => e.metaKey]
8490
+ Alt: [18, e => !!e.altKey],
8491
+ Control: [17, e => !!e.ctrlKey],
8492
+ Shift: [16, e => !!e.shiftKey],
8493
+ Meta: [91, e => !!e.metaKey]
8478
8494
  };
8479
8495
  const showCrosshair = { style: "cursor: crosshair" };
8480
8496
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "6.11.0",
3
+ "version": "6.11.2",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",