@codemirror/view 6.9.2 → 6.9.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,15 @@
1
+ ## 6.9.3 (2023-03-21)
2
+
3
+ ### Bug fixes
4
+
5
+ Work around a Firefox issue that caused `coordsAtPos` to return rectangles with the full line height on empty lines.
6
+
7
+ Opening a context menu by clicking below the content element but inside the editor now properly shows the browser's menu for editable elements.
8
+
9
+ Fix an issue that broke composition (especially of Chinese IME) after widget decorations.
10
+
11
+ Fix an issue that would cause the cursor to jump around during compositions inside nested mark decorations.
12
+
1
13
  ## 6.9.2 (2023-03-08)
2
14
 
3
15
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -957,8 +957,9 @@ function scanCompositionTree(pos, side, view, text, enterView, fromText) {
957
957
  }
958
958
  function posFromDOMInCompositionTree(node, offset, view, text) {
959
959
  if (view instanceof MarkView) {
960
+ let pos = 0;
960
961
  for (let child of view.children) {
961
- let pos = 0, hasComp = contains(child.dom, text);
962
+ let hasComp = contains(child.dom, text);
962
963
  if (contains(child.dom, node))
963
964
  return pos + (hasComp ? posFromDOMInCompositionTree(node, offset, child, text) : child.localPosFromDOM(node, offset));
964
965
  pos += hasComp ? text.nodeValue.length : child.length;
@@ -992,7 +993,7 @@ class WidgetBufferView extends ContentView {
992
993
  }
993
994
  }
994
995
  getSide() { return this.side; }
995
- domAtPos(pos) { return DOMPos.before(this.dom); }
996
+ domAtPos(pos) { return this.side > 0 ? DOMPos.before(this.dom) : DOMPos.after(this.dom); }
996
997
  localPosFromDOM() { return 0; }
997
998
  domBoundsAround() { return null; }
998
999
  coordsAt(pos) {
@@ -1516,7 +1517,7 @@ class LineView extends ContentView {
1516
1517
  measureTextSize() {
1517
1518
  if (this.children.length == 0 || this.length > 20)
1518
1519
  return null;
1519
- let totalWidth = 0;
1520
+ let totalWidth = 0, textHeight;
1520
1521
  for (let child of this.children) {
1521
1522
  if (!(child instanceof TextView) || /[^ -~]/.test(child.text))
1522
1523
  return null;
@@ -1524,14 +1525,26 @@ class LineView extends ContentView {
1524
1525
  if (rects.length != 1)
1525
1526
  return null;
1526
1527
  totalWidth += rects[0].width;
1528
+ textHeight = rects[0].height;
1527
1529
  }
1528
1530
  return !totalWidth ? null : {
1529
1531
  lineHeight: this.dom.getBoundingClientRect().height,
1530
- charWidth: totalWidth / this.length
1532
+ charWidth: totalWidth / this.length,
1533
+ textHeight
1531
1534
  };
1532
1535
  }
1533
1536
  coordsAt(pos, side) {
1534
- return coordsInChildren(this, pos, side);
1537
+ let rect = coordsInChildren(this, pos, side);
1538
+ // Correct rectangle height for empty lines when the returned
1539
+ // height is larger than the text height.
1540
+ if (!this.children.length && rect && this.parent) {
1541
+ let { heightOracle } = this.parent.view.viewState, height = rect.bottom - rect.top;
1542
+ if (Math.abs(height - heightOracle.lineHeight) < 2 && heightOracle.textHeight < height) {
1543
+ let dist = (height - heightOracle.textHeight) / 2;
1544
+ return { top: rect.top + dist, bottom: rect.bottom - dist, left: rect.left, right: rect.left };
1545
+ }
1546
+ }
1547
+ return rect;
1535
1548
  }
1536
1549
  become(_other) { return false; }
1537
1550
  get type() { return exports.BlockType.Text; }
@@ -2812,7 +2825,7 @@ class DocView extends ContentView {
2812
2825
  }
2813
2826
  }
2814
2827
  // If no workable line exists, force a layout of a measurable element
2815
- let dummy = document.createElement("div"), lineHeight, charWidth;
2828
+ let dummy = document.createElement("div"), lineHeight, charWidth, textHeight;
2816
2829
  dummy.className = "cm-line";
2817
2830
  dummy.style.width = "99999px";
2818
2831
  dummy.textContent = "abc def ghi jkl mno pqr stu";
@@ -2821,9 +2834,10 @@ class DocView extends ContentView {
2821
2834
  let rect = clientRectsFor(dummy.firstChild)[0];
2822
2835
  lineHeight = dummy.getBoundingClientRect().height;
2823
2836
  charWidth = rect ? rect.width / 27 : 7;
2837
+ textHeight = rect ? rect.height : lineHeight;
2824
2838
  dummy.remove();
2825
2839
  });
2826
- return { lineHeight, charWidth };
2840
+ return { lineHeight, charWidth, textHeight };
2827
2841
  }
2828
2842
  childCursor(pos = this.length) {
2829
2843
  // Move back to start of last element when possible, so that
@@ -2988,22 +3002,32 @@ class CompositionWidget extends WidgetType {
2988
3002
  ignoreEvent() { return false; }
2989
3003
  get customView() { return CompositionView; }
2990
3004
  }
2991
- function nearbyTextNode(node, offset, side) {
2992
- for (;;) {
2993
- if (node.nodeType == 3)
2994
- return node;
2995
- if (node.nodeType == 1 && offset > 0 && side <= 0) {
2996
- node = node.childNodes[offset - 1];
2997
- offset = maxOffset(node);
2998
- }
2999
- else if (node.nodeType == 1 && offset < node.childNodes.length && side >= 0) {
3000
- node = node.childNodes[offset];
3001
- offset = 0;
3005
+ function nearbyTextNode(startNode, startOffset, side) {
3006
+ if (side <= 0)
3007
+ for (let node = startNode, offset = startOffset;;) {
3008
+ if (node.nodeType == 3)
3009
+ return node;
3010
+ if (node.nodeType == 1 && offset > 0) {
3011
+ node = node.childNodes[offset - 1];
3012
+ offset = maxOffset(node);
3013
+ }
3014
+ else {
3015
+ break;
3016
+ }
3002
3017
  }
3003
- else {
3004
- return null;
3018
+ if (side >= 0)
3019
+ for (let node = startNode, offset = startOffset;;) {
3020
+ if (node.nodeType == 3)
3021
+ return node;
3022
+ if (node.nodeType == 1 && offset < node.childNodes.length && side >= 0) {
3023
+ node = node.childNodes[offset];
3024
+ offset = 0;
3025
+ }
3026
+ else {
3027
+ break;
3028
+ }
3005
3029
  }
3006
- }
3030
+ return null;
3007
3031
  }
3008
3032
  function nextToUneditable(node, offset) {
3009
3033
  if (node.nodeType != 1)
@@ -3439,8 +3463,16 @@ class InputState {
3439
3463
  this.registeredEvents.push(type);
3440
3464
  }
3441
3465
  view.scrollDOM.addEventListener("mousedown", (event) => {
3442
- if (event.target == view.scrollDOM && event.clientY > view.contentDOM.getBoundingClientRect().bottom)
3466
+ if (event.target == view.scrollDOM && event.clientY > view.contentDOM.getBoundingClientRect().bottom) {
3443
3467
  handleEvent(handlers.mousedown, event);
3468
+ if (!event.defaultPrevented && event.button == 2) {
3469
+ // Make sure the content covers the entire scroller height, in order
3470
+ // to catch a native context menu click below it
3471
+ let start = view.contentDOM.style.minHeight;
3472
+ view.contentDOM.style.minHeight = "100%";
3473
+ setTimeout(() => view.contentDOM.style.minHeight = start, 200);
3474
+ }
3475
+ }
3444
3476
  });
3445
3477
  if (browser.chrome && browser.chrome_version == 102) { // FIXME remove at some point
3446
3478
  // On Chrome 102, viewport updates somehow stop wheel-based
@@ -4134,8 +4166,9 @@ class HeightOracle {
4134
4166
  this.lineWrapping = lineWrapping;
4135
4167
  this.doc = state.Text.empty;
4136
4168
  this.heightSamples = {};
4137
- this.lineHeight = 14;
4169
+ this.lineHeight = 14; // The height of an entire line (line-height)
4138
4170
  this.charWidth = 7;
4171
+ this.textHeight = 14; // The height of the actual font (font-size)
4139
4172
  this.lineLength = 30;
4140
4173
  // Used to track, during updateHeight, if any actual heights changed
4141
4174
  this.heightChanged = false;
@@ -4170,12 +4203,13 @@ class HeightOracle {
4170
4203
  }
4171
4204
  return newHeight;
4172
4205
  }
4173
- refresh(whiteSpace, lineHeight, charWidth, lineLength, knownHeights) {
4206
+ refresh(whiteSpace, lineHeight, charWidth, textHeight, lineLength, knownHeights) {
4174
4207
  let lineWrapping = wrappingWhiteSpace.indexOf(whiteSpace) > -1;
4175
4208
  let changed = Math.round(lineHeight) != Math.round(this.lineHeight) || this.lineWrapping != lineWrapping;
4176
4209
  this.lineWrapping = lineWrapping;
4177
4210
  this.lineHeight = lineHeight;
4178
4211
  this.charWidth = charWidth;
4212
+ this.textHeight = textHeight;
4179
4213
  this.lineLength = lineLength;
4180
4214
  if (changed) {
4181
4215
  this.heightSamples = {};
@@ -5023,8 +5057,8 @@ class ViewState {
5023
5057
  if (oracle.mustRefreshForHeights(lineHeights))
5024
5058
  refresh = true;
5025
5059
  if (refresh || oracle.lineWrapping && Math.abs(contentWidth - this.contentDOMWidth) > oracle.charWidth) {
5026
- let { lineHeight, charWidth } = view.docView.measureTextSize();
5027
- refresh = lineHeight > 0 && oracle.refresh(whiteSpace, lineHeight, charWidth, contentWidth / charWidth, lineHeights);
5060
+ let { lineHeight, charWidth, textHeight } = view.docView.measureTextSize();
5061
+ refresh = lineHeight > 0 && oracle.refresh(whiteSpace, lineHeight, charWidth, textHeight, contentWidth / charWidth, lineHeights);
5028
5062
  if (refresh) {
5029
5063
  view.docView.minWidth = 0;
5030
5064
  result |= 8 /* UpdateFlag.Geometry */;
package/dist/index.js CHANGED
@@ -953,8 +953,9 @@ function scanCompositionTree(pos, side, view, text, enterView, fromText) {
953
953
  }
954
954
  function posFromDOMInCompositionTree(node, offset, view, text) {
955
955
  if (view instanceof MarkView) {
956
+ let pos = 0;
956
957
  for (let child of view.children) {
957
- let pos = 0, hasComp = contains(child.dom, text);
958
+ let hasComp = contains(child.dom, text);
958
959
  if (contains(child.dom, node))
959
960
  return pos + (hasComp ? posFromDOMInCompositionTree(node, offset, child, text) : child.localPosFromDOM(node, offset));
960
961
  pos += hasComp ? text.nodeValue.length : child.length;
@@ -988,7 +989,7 @@ class WidgetBufferView extends ContentView {
988
989
  }
989
990
  }
990
991
  getSide() { return this.side; }
991
- domAtPos(pos) { return DOMPos.before(this.dom); }
992
+ domAtPos(pos) { return this.side > 0 ? DOMPos.before(this.dom) : DOMPos.after(this.dom); }
992
993
  localPosFromDOM() { return 0; }
993
994
  domBoundsAround() { return null; }
994
995
  coordsAt(pos) {
@@ -1511,7 +1512,7 @@ class LineView extends ContentView {
1511
1512
  measureTextSize() {
1512
1513
  if (this.children.length == 0 || this.length > 20)
1513
1514
  return null;
1514
- let totalWidth = 0;
1515
+ let totalWidth = 0, textHeight;
1515
1516
  for (let child of this.children) {
1516
1517
  if (!(child instanceof TextView) || /[^ -~]/.test(child.text))
1517
1518
  return null;
@@ -1519,14 +1520,26 @@ class LineView extends ContentView {
1519
1520
  if (rects.length != 1)
1520
1521
  return null;
1521
1522
  totalWidth += rects[0].width;
1523
+ textHeight = rects[0].height;
1522
1524
  }
1523
1525
  return !totalWidth ? null : {
1524
1526
  lineHeight: this.dom.getBoundingClientRect().height,
1525
- charWidth: totalWidth / this.length
1527
+ charWidth: totalWidth / this.length,
1528
+ textHeight
1526
1529
  };
1527
1530
  }
1528
1531
  coordsAt(pos, side) {
1529
- return coordsInChildren(this, pos, side);
1532
+ let rect = coordsInChildren(this, pos, side);
1533
+ // Correct rectangle height for empty lines when the returned
1534
+ // height is larger than the text height.
1535
+ if (!this.children.length && rect && this.parent) {
1536
+ let { heightOracle } = this.parent.view.viewState, height = rect.bottom - rect.top;
1537
+ if (Math.abs(height - heightOracle.lineHeight) < 2 && heightOracle.textHeight < height) {
1538
+ let dist = (height - heightOracle.textHeight) / 2;
1539
+ return { top: rect.top + dist, bottom: rect.bottom - dist, left: rect.left, right: rect.left };
1540
+ }
1541
+ }
1542
+ return rect;
1530
1543
  }
1531
1544
  become(_other) { return false; }
1532
1545
  get type() { return BlockType.Text; }
@@ -2806,7 +2819,7 @@ class DocView extends ContentView {
2806
2819
  }
2807
2820
  }
2808
2821
  // If no workable line exists, force a layout of a measurable element
2809
- let dummy = document.createElement("div"), lineHeight, charWidth;
2822
+ let dummy = document.createElement("div"), lineHeight, charWidth, textHeight;
2810
2823
  dummy.className = "cm-line";
2811
2824
  dummy.style.width = "99999px";
2812
2825
  dummy.textContent = "abc def ghi jkl mno pqr stu";
@@ -2815,9 +2828,10 @@ class DocView extends ContentView {
2815
2828
  let rect = clientRectsFor(dummy.firstChild)[0];
2816
2829
  lineHeight = dummy.getBoundingClientRect().height;
2817
2830
  charWidth = rect ? rect.width / 27 : 7;
2831
+ textHeight = rect ? rect.height : lineHeight;
2818
2832
  dummy.remove();
2819
2833
  });
2820
- return { lineHeight, charWidth };
2834
+ return { lineHeight, charWidth, textHeight };
2821
2835
  }
2822
2836
  childCursor(pos = this.length) {
2823
2837
  // Move back to start of last element when possible, so that
@@ -2982,22 +2996,32 @@ class CompositionWidget extends WidgetType {
2982
2996
  ignoreEvent() { return false; }
2983
2997
  get customView() { return CompositionView; }
2984
2998
  }
2985
- function nearbyTextNode(node, offset, side) {
2986
- for (;;) {
2987
- if (node.nodeType == 3)
2988
- return node;
2989
- if (node.nodeType == 1 && offset > 0 && side <= 0) {
2990
- node = node.childNodes[offset - 1];
2991
- offset = maxOffset(node);
2992
- }
2993
- else if (node.nodeType == 1 && offset < node.childNodes.length && side >= 0) {
2994
- node = node.childNodes[offset];
2995
- offset = 0;
2999
+ function nearbyTextNode(startNode, startOffset, side) {
3000
+ if (side <= 0)
3001
+ for (let node = startNode, offset = startOffset;;) {
3002
+ if (node.nodeType == 3)
3003
+ return node;
3004
+ if (node.nodeType == 1 && offset > 0) {
3005
+ node = node.childNodes[offset - 1];
3006
+ offset = maxOffset(node);
3007
+ }
3008
+ else {
3009
+ break;
3010
+ }
2996
3011
  }
2997
- else {
2998
- return null;
3012
+ if (side >= 0)
3013
+ for (let node = startNode, offset = startOffset;;) {
3014
+ if (node.nodeType == 3)
3015
+ return node;
3016
+ if (node.nodeType == 1 && offset < node.childNodes.length && side >= 0) {
3017
+ node = node.childNodes[offset];
3018
+ offset = 0;
3019
+ }
3020
+ else {
3021
+ break;
3022
+ }
2999
3023
  }
3000
- }
3024
+ return null;
3001
3025
  }
3002
3026
  function nextToUneditable(node, offset) {
3003
3027
  if (node.nodeType != 1)
@@ -3433,8 +3457,16 @@ class InputState {
3433
3457
  this.registeredEvents.push(type);
3434
3458
  }
3435
3459
  view.scrollDOM.addEventListener("mousedown", (event) => {
3436
- if (event.target == view.scrollDOM && event.clientY > view.contentDOM.getBoundingClientRect().bottom)
3460
+ if (event.target == view.scrollDOM && event.clientY > view.contentDOM.getBoundingClientRect().bottom) {
3437
3461
  handleEvent(handlers.mousedown, event);
3462
+ if (!event.defaultPrevented && event.button == 2) {
3463
+ // Make sure the content covers the entire scroller height, in order
3464
+ // to catch a native context menu click below it
3465
+ let start = view.contentDOM.style.minHeight;
3466
+ view.contentDOM.style.minHeight = "100%";
3467
+ setTimeout(() => view.contentDOM.style.minHeight = start, 200);
3468
+ }
3469
+ }
3438
3470
  });
3439
3471
  if (browser.chrome && browser.chrome_version == 102) { // FIXME remove at some point
3440
3472
  // On Chrome 102, viewport updates somehow stop wheel-based
@@ -4128,8 +4160,9 @@ class HeightOracle {
4128
4160
  this.lineWrapping = lineWrapping;
4129
4161
  this.doc = Text.empty;
4130
4162
  this.heightSamples = {};
4131
- this.lineHeight = 14;
4163
+ this.lineHeight = 14; // The height of an entire line (line-height)
4132
4164
  this.charWidth = 7;
4165
+ this.textHeight = 14; // The height of the actual font (font-size)
4133
4166
  this.lineLength = 30;
4134
4167
  // Used to track, during updateHeight, if any actual heights changed
4135
4168
  this.heightChanged = false;
@@ -4164,12 +4197,13 @@ class HeightOracle {
4164
4197
  }
4165
4198
  return newHeight;
4166
4199
  }
4167
- refresh(whiteSpace, lineHeight, charWidth, lineLength, knownHeights) {
4200
+ refresh(whiteSpace, lineHeight, charWidth, textHeight, lineLength, knownHeights) {
4168
4201
  let lineWrapping = wrappingWhiteSpace.indexOf(whiteSpace) > -1;
4169
4202
  let changed = Math.round(lineHeight) != Math.round(this.lineHeight) || this.lineWrapping != lineWrapping;
4170
4203
  this.lineWrapping = lineWrapping;
4171
4204
  this.lineHeight = lineHeight;
4172
4205
  this.charWidth = charWidth;
4206
+ this.textHeight = textHeight;
4173
4207
  this.lineLength = lineLength;
4174
4208
  if (changed) {
4175
4209
  this.heightSamples = {};
@@ -5016,8 +5050,8 @@ class ViewState {
5016
5050
  if (oracle.mustRefreshForHeights(lineHeights))
5017
5051
  refresh = true;
5018
5052
  if (refresh || oracle.lineWrapping && Math.abs(contentWidth - this.contentDOMWidth) > oracle.charWidth) {
5019
- let { lineHeight, charWidth } = view.docView.measureTextSize();
5020
- refresh = lineHeight > 0 && oracle.refresh(whiteSpace, lineHeight, charWidth, contentWidth / charWidth, lineHeights);
5053
+ let { lineHeight, charWidth, textHeight } = view.docView.measureTextSize();
5054
+ refresh = lineHeight > 0 && oracle.refresh(whiteSpace, lineHeight, charWidth, textHeight, contentWidth / charWidth, lineHeights);
5021
5055
  if (refresh) {
5022
5056
  view.docView.minWidth = 0;
5023
5057
  result |= 8 /* UpdateFlag.Geometry */;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "6.9.2",
3
+ "version": "6.9.3",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",