@codemirror/view 6.17.0 → 6.17.1

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,17 @@
1
+ ## 6.17.1 (2023-08-31)
2
+
3
+ ### Bug fixes
4
+
5
+ Don't close the hover tooltip when the pointer moves over empty space caused by line breaks within the hovered range.
6
+
7
+ Fix a bug where on Chrome Android, if a virtual keyboard was slow to apply a change, the editor could end up dropping it.
8
+
9
+ Work around an issue where line-wise copy/cut didn't work in Firefox because the browser wasn't firing those events when nothing was selected.
10
+
11
+ Fix a crash triggered by the way some Android IME systems update the DOM.
12
+
13
+ Fix a bug that caused replacing a word by an emoji on Chrome Android to be treated as a backspace press.
14
+
1
15
  ## 6.17.0 (2023-08-28)
2
16
 
3
17
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -3184,7 +3184,7 @@ class BlockGapWidget extends WidgetType {
3184
3184
  }
3185
3185
  get estimatedHeight() { return this.height; }
3186
3186
  }
3187
- function findCompositionNode(view) {
3187
+ function findCompositionNode(view, dLen) {
3188
3188
  let sel = view.observer.selectionRange;
3189
3189
  let textNode = sel.focusNode && nearbyTextNode(sel.focusNode, sel.focusOffset, 0);
3190
3190
  if (!textNode)
@@ -3196,10 +3196,12 @@ function findCompositionNode(view) {
3196
3196
  to = from + cView.length;
3197
3197
  }
3198
3198
  else {
3199
+ let oldLen = Math.max(0, textNode.nodeValue.length - dLen);
3199
3200
  up: for (let offset = 0, node = textNode;;) {
3200
3201
  for (let sibling = node.previousSibling, cView; sibling; sibling = sibling.previousSibling) {
3201
3202
  if (cView = ContentView.get(sibling)) {
3202
- from = to = cView.posAtEnd + offset;
3203
+ to = cView.posAtEnd + offset;
3204
+ from = Math.max(0, to - oldLen);
3203
3205
  break up;
3204
3206
  }
3205
3207
  let reader = new DOMReader([], view.state);
@@ -3213,15 +3215,16 @@ function findCompositionNode(view) {
3213
3215
  return null;
3214
3216
  let parentView = ContentView.get(node);
3215
3217
  if (parentView) {
3216
- from = to = parentView.posAtStart + offset;
3218
+ from = parentView.posAtStart + offset;
3219
+ to = from + oldLen;
3217
3220
  break;
3218
3221
  }
3219
3222
  }
3220
3223
  }
3221
- return { from, to, node: textNode };
3224
+ return { from, to: to, node: textNode };
3222
3225
  }
3223
3226
  function findCompositionRange(view, changes) {
3224
- let found = findCompositionNode(view);
3227
+ let found = findCompositionNode(view, changes.newLength - changes.length);
3225
3228
  if (!found)
3226
3229
  return null;
3227
3230
  let { from: fromA, to: toA, node: textNode } = found;
@@ -3785,6 +3788,8 @@ class InputState {
3785
3788
  // issue where the composition vanishes when you press enter.
3786
3789
  if (browser.safari)
3787
3790
  view.contentDOM.addEventListener("input", () => null);
3791
+ if (browser.gecko)
3792
+ firefoxCopyCutHack(view.contentDOM.ownerDocument);
3788
3793
  }
3789
3794
  ensureHandlers(view, plugins) {
3790
3795
  var _a;
@@ -4490,6 +4495,18 @@ handlers.beforeinput = (view, event) => {
4490
4495
  }
4491
4496
  }
4492
4497
  };
4498
+ const appliedFirefoxHack = new Set;
4499
+ // In Firefox, when cut/copy handlers are added to the document, that
4500
+ // somehow avoids a bug where those events aren't fired when the
4501
+ // selection is empty. See https://github.com/codemirror/dev/issues/1082
4502
+ // and https://bugzilla.mozilla.org/show_bug.cgi?id=995961
4503
+ function firefoxCopyCutHack(doc) {
4504
+ if (!appliedFirefoxHack.has(doc)) {
4505
+ appliedFirefoxHack.add(doc);
4506
+ doc.addEventListener("copy", () => { });
4507
+ doc.addEventListener("cut", () => { });
4508
+ }
4509
+ }
4493
4510
 
4494
4511
  const wrappingWhiteSpace = ["pre-wrap", "normal", "pre-line", "break-spaces"];
4495
4512
  class HeightOracle {
@@ -6146,7 +6163,7 @@ function applyDOMChange(view, domChange) {
6146
6163
  change.insert.length == 1 && change.insert.lines == 2 &&
6147
6164
  dispatchKey(view.contentDOM, "Enter", 13)) ||
6148
6165
  ((change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 ||
6149
- lastKey == 8 && change.insert.length < change.to - change.from) &&
6166
+ lastKey == 8 && change.insert.length < change.to - change.from && change.to > sel.head) &&
6150
6167
  dispatchKey(view.contentDOM, "Backspace", 8)) ||
6151
6168
  (change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
6152
6169
  dispatchKey(view.contentDOM, "Delete", 46))))
@@ -6190,7 +6207,8 @@ function applyDefaultInsert(view, change, newSel) {
6190
6207
  if (startState.selection.ranges.length > 1 && view.inputState.composing >= 0 &&
6191
6208
  change.to <= sel.to && change.to >= sel.to - 10) {
6192
6209
  let replaced = view.state.sliceDoc(change.from, change.to);
6193
- let composition = findCompositionNode(view) || view.state.doc.lineAt(sel.head);
6210
+ let composition = findCompositionNode(view, change.insert.length - (change.to - change.from)) ||
6211
+ view.state.doc.lineAt(sel.head);
6194
6212
  let offset = sel.to - change.to, size = sel.to - sel.from;
6195
6213
  tr = startState.changeByRange(range => {
6196
6214
  if (range.from == sel.from && range.to == sel.to)
@@ -7040,6 +7058,11 @@ class EditorView {
7040
7058
  return;
7041
7059
  if (this.measureScheduled > -1)
7042
7060
  this.win.cancelAnimationFrame(this.measureScheduled);
7061
+ if (this.observer.delayedAndroidKey) {
7062
+ this.measureScheduled = -1;
7063
+ this.requestMeasure();
7064
+ return;
7065
+ }
7043
7066
  this.measureScheduled = 0; // Prevent requestMeasure calls from scheduling another animation frame
7044
7067
  if (flush)
7045
7068
  this.observer.forceFlush();
@@ -9370,7 +9393,7 @@ class HoverPlugin {
9370
9393
  if (tooltip && !isInTooltip(this.lastMove.target) || this.pending) {
9371
9394
  let { pos } = tooltip || this.pending, end = (_a = tooltip === null || tooltip === void 0 ? void 0 : tooltip.end) !== null && _a !== void 0 ? _a : pos;
9372
9395
  if ((pos == end ? this.view.posAtCoords(this.lastMove) != pos
9373
- : !isOverRange(this.view, pos, end, event.clientX, event.clientY, 6 /* Hover.MaxDist */))) {
9396
+ : !isOverRange(this.view, pos, end, event.clientX, event.clientY))) {
9374
9397
  this.view.dispatch({ effects: this.setHover.of(null) });
9375
9398
  this.pending = null;
9376
9399
  }
@@ -9395,19 +9418,12 @@ function isInTooltip(elt) {
9395
9418
  return false;
9396
9419
  }
9397
9420
  function isOverRange(view, from, to, x, y, margin) {
9398
- let range = document.createRange();
9399
- let fromDOM = view.domAtPos(from), toDOM = view.domAtPos(to);
9400
- range.setEnd(toDOM.node, toDOM.offset);
9401
- range.setStart(fromDOM.node, fromDOM.offset);
9402
- let rects = range.getClientRects();
9403
- range.detach();
9404
- for (let i = 0; i < rects.length; i++) {
9405
- let rect = rects[i];
9406
- let dist = Math.max(rect.top - y, y - rect.bottom, rect.left - x, x - rect.right);
9407
- if (dist <= margin)
9408
- return true;
9409
- }
9410
- return false;
9421
+ let rect = view.scrollDOM.getBoundingClientRect();
9422
+ let docBottom = view.documentTop + view.documentPadding.top + view.contentHeight;
9423
+ if (rect.left > x || rect.right < x || rect.top > y || Math.min(rect.bottom, docBottom) < y)
9424
+ return false;
9425
+ let pos = view.posAtCoords({ x, y }, false);
9426
+ return pos >= from && pos <= to;
9411
9427
  }
9412
9428
  /**
9413
9429
  Set up a hover tooltip, which shows up when the pointer hovers
package/dist/index.js CHANGED
@@ -3180,7 +3180,7 @@ class BlockGapWidget extends WidgetType {
3180
3180
  }
3181
3181
  get estimatedHeight() { return this.height; }
3182
3182
  }
3183
- function findCompositionNode(view) {
3183
+ function findCompositionNode(view, dLen) {
3184
3184
  let sel = view.observer.selectionRange;
3185
3185
  let textNode = sel.focusNode && nearbyTextNode(sel.focusNode, sel.focusOffset, 0);
3186
3186
  if (!textNode)
@@ -3192,10 +3192,12 @@ function findCompositionNode(view) {
3192
3192
  to = from + cView.length;
3193
3193
  }
3194
3194
  else {
3195
+ let oldLen = Math.max(0, textNode.nodeValue.length - dLen);
3195
3196
  up: for (let offset = 0, node = textNode;;) {
3196
3197
  for (let sibling = node.previousSibling, cView; sibling; sibling = sibling.previousSibling) {
3197
3198
  if (cView = ContentView.get(sibling)) {
3198
- from = to = cView.posAtEnd + offset;
3199
+ to = cView.posAtEnd + offset;
3200
+ from = Math.max(0, to - oldLen);
3199
3201
  break up;
3200
3202
  }
3201
3203
  let reader = new DOMReader([], view.state);
@@ -3209,15 +3211,16 @@ function findCompositionNode(view) {
3209
3211
  return null;
3210
3212
  let parentView = ContentView.get(node);
3211
3213
  if (parentView) {
3212
- from = to = parentView.posAtStart + offset;
3214
+ from = parentView.posAtStart + offset;
3215
+ to = from + oldLen;
3213
3216
  break;
3214
3217
  }
3215
3218
  }
3216
3219
  }
3217
- return { from, to, node: textNode };
3220
+ return { from, to: to, node: textNode };
3218
3221
  }
3219
3222
  function findCompositionRange(view, changes) {
3220
- let found = findCompositionNode(view);
3223
+ let found = findCompositionNode(view, changes.newLength - changes.length);
3221
3224
  if (!found)
3222
3225
  return null;
3223
3226
  let { from: fromA, to: toA, node: textNode } = found;
@@ -3781,6 +3784,8 @@ class InputState {
3781
3784
  // issue where the composition vanishes when you press enter.
3782
3785
  if (browser.safari)
3783
3786
  view.contentDOM.addEventListener("input", () => null);
3787
+ if (browser.gecko)
3788
+ firefoxCopyCutHack(view.contentDOM.ownerDocument);
3784
3789
  }
3785
3790
  ensureHandlers(view, plugins) {
3786
3791
  var _a;
@@ -4486,6 +4491,18 @@ handlers.beforeinput = (view, event) => {
4486
4491
  }
4487
4492
  }
4488
4493
  };
4494
+ const appliedFirefoxHack = /*@__PURE__*/new Set;
4495
+ // In Firefox, when cut/copy handlers are added to the document, that
4496
+ // somehow avoids a bug where those events aren't fired when the
4497
+ // selection is empty. See https://github.com/codemirror/dev/issues/1082
4498
+ // and https://bugzilla.mozilla.org/show_bug.cgi?id=995961
4499
+ function firefoxCopyCutHack(doc) {
4500
+ if (!appliedFirefoxHack.has(doc)) {
4501
+ appliedFirefoxHack.add(doc);
4502
+ doc.addEventListener("copy", () => { });
4503
+ doc.addEventListener("cut", () => { });
4504
+ }
4505
+ }
4489
4506
 
4490
4507
  const wrappingWhiteSpace = ["pre-wrap", "normal", "pre-line", "break-spaces"];
4491
4508
  class HeightOracle {
@@ -6141,7 +6158,7 @@ function applyDOMChange(view, domChange) {
6141
6158
  change.insert.length == 1 && change.insert.lines == 2 &&
6142
6159
  dispatchKey(view.contentDOM, "Enter", 13)) ||
6143
6160
  ((change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 ||
6144
- lastKey == 8 && change.insert.length < change.to - change.from) &&
6161
+ lastKey == 8 && change.insert.length < change.to - change.from && change.to > sel.head) &&
6145
6162
  dispatchKey(view.contentDOM, "Backspace", 8)) ||
6146
6163
  (change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
6147
6164
  dispatchKey(view.contentDOM, "Delete", 46))))
@@ -6185,7 +6202,8 @@ function applyDefaultInsert(view, change, newSel) {
6185
6202
  if (startState.selection.ranges.length > 1 && view.inputState.composing >= 0 &&
6186
6203
  change.to <= sel.to && change.to >= sel.to - 10) {
6187
6204
  let replaced = view.state.sliceDoc(change.from, change.to);
6188
- let composition = findCompositionNode(view) || view.state.doc.lineAt(sel.head);
6205
+ let composition = findCompositionNode(view, change.insert.length - (change.to - change.from)) ||
6206
+ view.state.doc.lineAt(sel.head);
6189
6207
  let offset = sel.to - change.to, size = sel.to - sel.from;
6190
6208
  tr = startState.changeByRange(range => {
6191
6209
  if (range.from == sel.from && range.to == sel.to)
@@ -7035,6 +7053,11 @@ class EditorView {
7035
7053
  return;
7036
7054
  if (this.measureScheduled > -1)
7037
7055
  this.win.cancelAnimationFrame(this.measureScheduled);
7056
+ if (this.observer.delayedAndroidKey) {
7057
+ this.measureScheduled = -1;
7058
+ this.requestMeasure();
7059
+ return;
7060
+ }
7038
7061
  this.measureScheduled = 0; // Prevent requestMeasure calls from scheduling another animation frame
7039
7062
  if (flush)
7040
7063
  this.observer.forceFlush();
@@ -9365,7 +9388,7 @@ class HoverPlugin {
9365
9388
  if (tooltip && !isInTooltip(this.lastMove.target) || this.pending) {
9366
9389
  let { pos } = tooltip || this.pending, end = (_a = tooltip === null || tooltip === void 0 ? void 0 : tooltip.end) !== null && _a !== void 0 ? _a : pos;
9367
9390
  if ((pos == end ? this.view.posAtCoords(this.lastMove) != pos
9368
- : !isOverRange(this.view, pos, end, event.clientX, event.clientY, 6 /* Hover.MaxDist */))) {
9391
+ : !isOverRange(this.view, pos, end, event.clientX, event.clientY))) {
9369
9392
  this.view.dispatch({ effects: this.setHover.of(null) });
9370
9393
  this.pending = null;
9371
9394
  }
@@ -9390,19 +9413,12 @@ function isInTooltip(elt) {
9390
9413
  return false;
9391
9414
  }
9392
9415
  function isOverRange(view, from, to, x, y, margin) {
9393
- let range = document.createRange();
9394
- let fromDOM = view.domAtPos(from), toDOM = view.domAtPos(to);
9395
- range.setEnd(toDOM.node, toDOM.offset);
9396
- range.setStart(fromDOM.node, fromDOM.offset);
9397
- let rects = range.getClientRects();
9398
- range.detach();
9399
- for (let i = 0; i < rects.length; i++) {
9400
- let rect = rects[i];
9401
- let dist = Math.max(rect.top - y, y - rect.bottom, rect.left - x, x - rect.right);
9402
- if (dist <= margin)
9403
- return true;
9404
- }
9405
- return false;
9416
+ let rect = view.scrollDOM.getBoundingClientRect();
9417
+ let docBottom = view.documentTop + view.documentPadding.top + view.contentHeight;
9418
+ if (rect.left > x || rect.right < x || rect.top > y || Math.min(rect.bottom, docBottom) < y)
9419
+ return false;
9420
+ let pos = view.posAtCoords({ x, y }, false);
9421
+ return pos >= from && pos <= to;
9406
9422
  }
9407
9423
  /**
9408
9424
  Set up a hover tooltip, which shows up when the pointer hovers
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "6.17.0",
3
+ "version": "6.17.1",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",