@codemirror/view 6.9.6 → 6.10.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,25 @@
1
+ ## 6.10.1 (2023-05-01)
2
+
3
+ ### Bug fixes
4
+
5
+ Limit cursor height in front of custom placeholder DOM elements.
6
+
7
+ ## 6.10.0 (2023-04-25)
8
+
9
+ ### Bug fixes
10
+
11
+ Fix a crash in `drawSelection` when a measured position falls on a position that doesn't have corresponding screen coordinates.
12
+
13
+ Work around unhelpful interaction observer behavior that could cause the editor to not notice it was visible.
14
+
15
+ Give the cursor next to a line-wrapped placeholder a single-line height.
16
+
17
+ Make sure drop events below the editable element in a fixed-height editor get handled properly.
18
+
19
+ ### New features
20
+
21
+ Widget decorations can now define custom `coordsAtPos` methods to control the way the editor computes screen positions at or in the widget.
22
+
1
23
  ## 6.9.6 (2023-04-21)
2
24
 
3
25
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -90,7 +90,6 @@ function scanFor(node, off, targetNode, targetOff, dir) {
90
90
  function maxOffset(node) {
91
91
  return node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length;
92
92
  }
93
- const Rect0 = { left: 0, right: 0, top: 0, bottom: 0 };
94
93
  function flattenRect(rect, left) {
95
94
  let x = left ? rect.left : rect.right;
96
95
  return { left: x, right: x, top: rect.top, bottom: rect.bottom };
@@ -353,10 +352,6 @@ class ContentView {
353
352
  posAfter(view) {
354
353
  return this.posBefore(view) + view.length;
355
354
  }
356
- // Will return a rectangle directly before (when side < 0), after
357
- // (side > 0) or directly on (when the browser supports it) the
358
- // given position.
359
- coordsAt(_pos, _side) { return null; }
360
355
  sync(view, track) {
361
356
  if (this.dirty & 2 /* Dirty.Node */) {
362
357
  let parent = this.dom;
@@ -815,7 +810,7 @@ function textCoords(text, pos, side) {
815
810
  }
816
811
  let rects = textRange(text, from, to).getClientRects();
817
812
  if (!rects.length)
818
- return Rect0;
813
+ return null;
819
814
  let rect = rects[(flatten ? flatten < 0 : side >= 0) ? 0 : rects.length - 1];
820
815
  if (browser.safari && !flatten && rect.width == 0)
821
816
  rect = Array.prototype.find.call(rects, r => r.width) || rect;
@@ -886,9 +881,12 @@ class WidgetView extends ContentView {
886
881
  }
887
882
  domBoundsAround() { return null; }
888
883
  coordsAt(pos, side) {
884
+ let custom = this.widget.coordsAt(this.dom, pos, side);
885
+ if (custom)
886
+ return custom;
889
887
  let rects = this.dom.getClientRects(), rect = null;
890
888
  if (!rects.length)
891
- return Rect0;
889
+ return null;
892
890
  for (let i = pos > 0 ? rects.length - 1 : 0;; i += (pos > 0 ? -1 : 1)) {
893
891
  rect = rects[i];
894
892
  if (pos > 0 ? i == 0 : i == rects.length - 1 || rect.top < rect.bottom)
@@ -1055,12 +1053,7 @@ class WidgetBufferView extends ContentView {
1055
1053
  localPosFromDOM() { return 0; }
1056
1054
  domBoundsAround() { return null; }
1057
1055
  coordsAt(pos) {
1058
- let imgRect = this.dom.getBoundingClientRect();
1059
- // Since the <img> height doesn't correspond to text height, try
1060
- // to borrow the height from some sibling node.
1061
- let siblingRect = inlineSiblingRect(this, this.side > 0 ? -1 : 1);
1062
- return siblingRect && siblingRect.top < imgRect.bottom && siblingRect.bottom > imgRect.top
1063
- ? { left: imgRect.left, right: imgRect.right, top: siblingRect.top, bottom: siblingRect.bottom } : imgRect;
1056
+ return this.dom.getBoundingClientRect();
1064
1057
  }
1065
1058
  get overrideDOMText() {
1066
1059
  return state.Text.empty;
@@ -1068,31 +1061,6 @@ class WidgetBufferView extends ContentView {
1068
1061
  get isHidden() { return true; }
1069
1062
  }
1070
1063
  TextView.prototype.children = WidgetView.prototype.children = WidgetBufferView.prototype.children = noChildren;
1071
- function inlineSiblingRect(view, side) {
1072
- let parent = view.parent, index = parent ? parent.children.indexOf(view) : -1;
1073
- while (parent && index >= 0) {
1074
- if (side < 0 ? index > 0 : index < parent.children.length) {
1075
- let next = parent.children[index + side];
1076
- if (next instanceof TextView) {
1077
- let nextRect = next.coordsAt(side < 0 ? next.length : 0, side);
1078
- if (nextRect)
1079
- return nextRect;
1080
- }
1081
- index += side;
1082
- }
1083
- else if (parent instanceof MarkView && parent.parent) {
1084
- index = parent.parent.children.indexOf(parent) + (side < 0 ? 0 : 1);
1085
- parent = parent.parent;
1086
- }
1087
- else {
1088
- let last = parent.dom.lastChild;
1089
- if (last && last.nodeName == "BR")
1090
- return last.getClientRects()[0];
1091
- break;
1092
- }
1093
- }
1094
- return undefined;
1095
- }
1096
1064
  function inlineDOMAtPos(parent, pos) {
1097
1065
  let dom = parent.dom, { children } = parent, i = 0;
1098
1066
  for (let off = 0; i < children.length; i++) {
@@ -1139,12 +1107,12 @@ function coordsInChildren(view, pos, side) {
1139
1107
  if (child.children.length) {
1140
1108
  scan(child, pos - off);
1141
1109
  }
1142
- else if ((!after || after instanceof WidgetBufferView && side > 0) &&
1110
+ else if ((!after || after.isHidden && side > 0) &&
1143
1111
  (end > pos || off == end && child.getSide() > 0)) {
1144
1112
  after = child;
1145
1113
  afterPos = pos - off;
1146
1114
  }
1147
- else if (off < pos || (off == end && child.getSide() < 0)) {
1115
+ else if (off < pos || (off == end && child.getSide() < 0) && !child.isHidden) {
1148
1116
  before = child;
1149
1117
  beforePos = pos - off;
1150
1118
  }
@@ -1250,6 +1218,14 @@ class WidgetType {
1250
1218
  */
1251
1219
  ignoreEvent(event) { return true; }
1252
1220
  /**
1221
+ Override the way screen coordinates for positions at/in the
1222
+ widget are found. `pos` will be the offset into the widget, and
1223
+ `side` the side of the position that is being queried—less than
1224
+ zero for before, greater than zero for after, and zero for
1225
+ directly at that position.
1226
+ */
1227
+ coordsAt(dom, pos, side) { return null; }
1228
+ /**
1253
1229
  @internal
1254
1230
  */
1255
1231
  get customView() { return null; }
@@ -1684,6 +1660,9 @@ class BlockWidgetView extends ContentView {
1684
1660
  ignoreEvent(event) { return this.widget.ignoreEvent(event); }
1685
1661
  get isEditable() { return false; }
1686
1662
  get isWidget() { return true; }
1663
+ coordsAt(pos, side) {
1664
+ return this.widget.coordsAt(this.dom, pos, side);
1665
+ }
1687
1666
  destroy() {
1688
1667
  super.destroy();
1689
1668
  if (this.dom)
@@ -3551,6 +3530,10 @@ class InputState {
3551
3530
  }
3552
3531
  }
3553
3532
  });
3533
+ view.scrollDOM.addEventListener("drop", (event) => {
3534
+ if (event.target == view.scrollDOM && event.clientY > view.contentDOM.getBoundingClientRect().bottom)
3535
+ handleEvent(handlers.drop, event);
3536
+ });
3554
3537
  if (browser.chrome && browser.chrome_version == 102) { // FIXME remove at some point
3555
3538
  // On Chrome 102, viewport updates somehow stop wheel-based
3556
3539
  // scrolling. Turning off pointer events during the scroll seems
@@ -6067,7 +6050,7 @@ class DOMObserver {
6067
6050
  if (this.intersecting != this.view.inView)
6068
6051
  this.onScrollChanged(document.createEvent("Event"));
6069
6052
  }
6070
- }, {});
6053
+ }, { threshold: [0, .001] });
6071
6054
  this.intersection.observe(this.dom);
6072
6055
  this.gapIntersection = new IntersectionObserver(entries => {
6073
6056
  if (entries.length > 0 && entries[entries.length - 1].intersectionRatio > 0)
@@ -7724,6 +7707,8 @@ function rectanglesForRange(view, className, range) {
7724
7707
  // coordsAtPos queries, would break selection drawing.
7725
7708
  let fromCoords = view.coordsAtPos(from, (from == line.to ? -2 : 2));
7726
7709
  let toCoords = view.coordsAtPos(to, (to == line.from ? 2 : -2));
7710
+ if (!fromCoords || !toCoords)
7711
+ return;
7727
7712
  top = Math.min(fromCoords.top, toCoords.top, top);
7728
7713
  bottom = Math.max(fromCoords.bottom, toCoords.bottom, bottom);
7729
7714
  if (dir == exports.Direction.LTR)
@@ -8365,6 +8350,17 @@ class Placeholder extends WidgetType {
8365
8350
  wrap.setAttribute("aria-hidden", "true");
8366
8351
  return wrap;
8367
8352
  }
8353
+ coordsAt(dom) {
8354
+ let rects = dom.firstChild ? clientRectsFor(dom.firstChild) : [];
8355
+ if (!rects.length)
8356
+ return null;
8357
+ let style = window.getComputedStyle(dom.parentNode);
8358
+ let rect = flattenRect(rects[0], style.direction != "rtl");
8359
+ let lineHeight = parseInt(style.lineHeight);
8360
+ if (rect.bottom - rect.top > lineHeight * 1.5)
8361
+ return { left: rect.left, right: rect.right, top: rect.top, bottom: rect.top + lineHeight };
8362
+ return rect;
8363
+ }
8368
8364
  ignoreEvent() { return false; }
8369
8365
  }
8370
8366
  /**
package/dist/index.d.ts CHANGED
@@ -6,6 +6,17 @@ declare type Attrs = {
6
6
  [name: string]: string;
7
7
  };
8
8
 
9
+ /**
10
+ Basic rectangle type.
11
+ */
12
+ interface Rect {
13
+ readonly left: number;
14
+ readonly right: number;
15
+ readonly top: number;
16
+ readonly bottom: number;
17
+ }
18
+ declare type ScrollStrategy = "nearest" | "start" | "end" | "center";
19
+
9
20
  interface MarkDecorationSpec {
10
21
  /**
11
22
  Whether the mark covers its start and end position or not. This
@@ -169,6 +180,14 @@ declare abstract class WidgetType {
169
180
  */
170
181
  ignoreEvent(event: Event): boolean;
171
182
  /**
183
+ Override the way screen coordinates for positions at/in the
184
+ widget are found. `pos` will be the offset into the widget, and
185
+ `side` the side of the position that is being queried—less than
186
+ zero for before, greater than zero for after, and zero for
187
+ directly at that position.
188
+ */
189
+ coordsAt(dom: HTMLElement, pos: number, side: number): Rect | null;
190
+ /**
172
191
  This is called when the an instance of the widget is removed
173
192
  from the editor view.
174
193
  */
@@ -271,17 +290,6 @@ declare abstract class Decoration extends RangeValue {
271
290
  static none: DecorationSet;
272
291
  }
273
292
 
274
- /**
275
- Basic rectangle type.
276
- */
277
- interface Rect {
278
- readonly left: number;
279
- readonly right: number;
280
- readonly top: number;
281
- readonly bottom: number;
282
- }
283
- declare type ScrollStrategy = "nearest" | "start" | "end" | "center";
284
-
285
293
  /**
286
294
  Command functions are used in key bindings and other types of user
287
295
  actions. Given an editor view, they check whether their effect can
package/dist/index.js CHANGED
@@ -86,7 +86,6 @@ function scanFor(node, off, targetNode, targetOff, dir) {
86
86
  function maxOffset(node) {
87
87
  return node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length;
88
88
  }
89
- const Rect0 = { left: 0, right: 0, top: 0, bottom: 0 };
90
89
  function flattenRect(rect, left) {
91
90
  let x = left ? rect.left : rect.right;
92
91
  return { left: x, right: x, top: rect.top, bottom: rect.bottom };
@@ -349,10 +348,6 @@ class ContentView {
349
348
  posAfter(view) {
350
349
  return this.posBefore(view) + view.length;
351
350
  }
352
- // Will return a rectangle directly before (when side < 0), after
353
- // (side > 0) or directly on (when the browser supports it) the
354
- // given position.
355
- coordsAt(_pos, _side) { return null; }
356
351
  sync(view, track) {
357
352
  if (this.dirty & 2 /* Dirty.Node */) {
358
353
  let parent = this.dom;
@@ -811,7 +806,7 @@ function textCoords(text, pos, side) {
811
806
  }
812
807
  let rects = textRange(text, from, to).getClientRects();
813
808
  if (!rects.length)
814
- return Rect0;
809
+ return null;
815
810
  let rect = rects[(flatten ? flatten < 0 : side >= 0) ? 0 : rects.length - 1];
816
811
  if (browser.safari && !flatten && rect.width == 0)
817
812
  rect = Array.prototype.find.call(rects, r => r.width) || rect;
@@ -882,9 +877,12 @@ class WidgetView extends ContentView {
882
877
  }
883
878
  domBoundsAround() { return null; }
884
879
  coordsAt(pos, side) {
880
+ let custom = this.widget.coordsAt(this.dom, pos, side);
881
+ if (custom)
882
+ return custom;
885
883
  let rects = this.dom.getClientRects(), rect = null;
886
884
  if (!rects.length)
887
- return Rect0;
885
+ return null;
888
886
  for (let i = pos > 0 ? rects.length - 1 : 0;; i += (pos > 0 ? -1 : 1)) {
889
887
  rect = rects[i];
890
888
  if (pos > 0 ? i == 0 : i == rects.length - 1 || rect.top < rect.bottom)
@@ -1051,12 +1049,7 @@ class WidgetBufferView extends ContentView {
1051
1049
  localPosFromDOM() { return 0; }
1052
1050
  domBoundsAround() { return null; }
1053
1051
  coordsAt(pos) {
1054
- let imgRect = this.dom.getBoundingClientRect();
1055
- // Since the <img> height doesn't correspond to text height, try
1056
- // to borrow the height from some sibling node.
1057
- let siblingRect = inlineSiblingRect(this, this.side > 0 ? -1 : 1);
1058
- return siblingRect && siblingRect.top < imgRect.bottom && siblingRect.bottom > imgRect.top
1059
- ? { left: imgRect.left, right: imgRect.right, top: siblingRect.top, bottom: siblingRect.bottom } : imgRect;
1052
+ return this.dom.getBoundingClientRect();
1060
1053
  }
1061
1054
  get overrideDOMText() {
1062
1055
  return Text.empty;
@@ -1064,31 +1057,6 @@ class WidgetBufferView extends ContentView {
1064
1057
  get isHidden() { return true; }
1065
1058
  }
1066
1059
  TextView.prototype.children = WidgetView.prototype.children = WidgetBufferView.prototype.children = noChildren;
1067
- function inlineSiblingRect(view, side) {
1068
- let parent = view.parent, index = parent ? parent.children.indexOf(view) : -1;
1069
- while (parent && index >= 0) {
1070
- if (side < 0 ? index > 0 : index < parent.children.length) {
1071
- let next = parent.children[index + side];
1072
- if (next instanceof TextView) {
1073
- let nextRect = next.coordsAt(side < 0 ? next.length : 0, side);
1074
- if (nextRect)
1075
- return nextRect;
1076
- }
1077
- index += side;
1078
- }
1079
- else if (parent instanceof MarkView && parent.parent) {
1080
- index = parent.parent.children.indexOf(parent) + (side < 0 ? 0 : 1);
1081
- parent = parent.parent;
1082
- }
1083
- else {
1084
- let last = parent.dom.lastChild;
1085
- if (last && last.nodeName == "BR")
1086
- return last.getClientRects()[0];
1087
- break;
1088
- }
1089
- }
1090
- return undefined;
1091
- }
1092
1060
  function inlineDOMAtPos(parent, pos) {
1093
1061
  let dom = parent.dom, { children } = parent, i = 0;
1094
1062
  for (let off = 0; i < children.length; i++) {
@@ -1135,12 +1103,12 @@ function coordsInChildren(view, pos, side) {
1135
1103
  if (child.children.length) {
1136
1104
  scan(child, pos - off);
1137
1105
  }
1138
- else if ((!after || after instanceof WidgetBufferView && side > 0) &&
1106
+ else if ((!after || after.isHidden && side > 0) &&
1139
1107
  (end > pos || off == end && child.getSide() > 0)) {
1140
1108
  after = child;
1141
1109
  afterPos = pos - off;
1142
1110
  }
1143
- else if (off < pos || (off == end && child.getSide() < 0)) {
1111
+ else if (off < pos || (off == end && child.getSide() < 0) && !child.isHidden) {
1144
1112
  before = child;
1145
1113
  beforePos = pos - off;
1146
1114
  }
@@ -1246,6 +1214,14 @@ class WidgetType {
1246
1214
  */
1247
1215
  ignoreEvent(event) { return true; }
1248
1216
  /**
1217
+ Override the way screen coordinates for positions at/in the
1218
+ widget are found. `pos` will be the offset into the widget, and
1219
+ `side` the side of the position that is being queried—less than
1220
+ zero for before, greater than zero for after, and zero for
1221
+ directly at that position.
1222
+ */
1223
+ coordsAt(dom, pos, side) { return null; }
1224
+ /**
1249
1225
  @internal
1250
1226
  */
1251
1227
  get customView() { return null; }
@@ -1679,6 +1655,9 @@ class BlockWidgetView extends ContentView {
1679
1655
  ignoreEvent(event) { return this.widget.ignoreEvent(event); }
1680
1656
  get isEditable() { return false; }
1681
1657
  get isWidget() { return true; }
1658
+ coordsAt(pos, side) {
1659
+ return this.widget.coordsAt(this.dom, pos, side);
1660
+ }
1682
1661
  destroy() {
1683
1662
  super.destroy();
1684
1663
  if (this.dom)
@@ -3545,6 +3524,10 @@ class InputState {
3545
3524
  }
3546
3525
  }
3547
3526
  });
3527
+ view.scrollDOM.addEventListener("drop", (event) => {
3528
+ if (event.target == view.scrollDOM && event.clientY > view.contentDOM.getBoundingClientRect().bottom)
3529
+ handleEvent(handlers.drop, event);
3530
+ });
3548
3531
  if (browser.chrome && browser.chrome_version == 102) { // FIXME remove at some point
3549
3532
  // On Chrome 102, viewport updates somehow stop wheel-based
3550
3533
  // scrolling. Turning off pointer events during the scroll seems
@@ -6060,7 +6043,7 @@ class DOMObserver {
6060
6043
  if (this.intersecting != this.view.inView)
6061
6044
  this.onScrollChanged(document.createEvent("Event"));
6062
6045
  }
6063
- }, {});
6046
+ }, { threshold: [0, .001] });
6064
6047
  this.intersection.observe(this.dom);
6065
6048
  this.gapIntersection = new IntersectionObserver(entries => {
6066
6049
  if (entries.length > 0 && entries[entries.length - 1].intersectionRatio > 0)
@@ -7717,6 +7700,8 @@ function rectanglesForRange(view, className, range) {
7717
7700
  // coordsAtPos queries, would break selection drawing.
7718
7701
  let fromCoords = view.coordsAtPos(from, (from == line.to ? -2 : 2));
7719
7702
  let toCoords = view.coordsAtPos(to, (to == line.from ? 2 : -2));
7703
+ if (!fromCoords || !toCoords)
7704
+ return;
7720
7705
  top = Math.min(fromCoords.top, toCoords.top, top);
7721
7706
  bottom = Math.max(fromCoords.bottom, toCoords.bottom, bottom);
7722
7707
  if (dir == Direction.LTR)
@@ -8358,6 +8343,17 @@ class Placeholder extends WidgetType {
8358
8343
  wrap.setAttribute("aria-hidden", "true");
8359
8344
  return wrap;
8360
8345
  }
8346
+ coordsAt(dom) {
8347
+ let rects = dom.firstChild ? clientRectsFor(dom.firstChild) : [];
8348
+ if (!rects.length)
8349
+ return null;
8350
+ let style = window.getComputedStyle(dom.parentNode);
8351
+ let rect = flattenRect(rects[0], style.direction != "rtl");
8352
+ let lineHeight = parseInt(style.lineHeight);
8353
+ if (rect.bottom - rect.top > lineHeight * 1.5)
8354
+ return { left: rect.left, right: rect.right, top: rect.top, bottom: rect.top + lineHeight };
8355
+ return rect;
8356
+ }
8361
8357
  ignoreEvent() { return false; }
8362
8358
  }
8363
8359
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "6.9.6",
3
+ "version": "6.10.1",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",