@codemirror/view 6.33.0 → 6.34.0

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.34.0 (2024-09-25)
2
+
3
+ ### Bug fixes
4
+
5
+ Fix an issue where the dots past the wrapping point were displayed incorrectly when using `highlightWhitespace` with a wrapped sequence of spaces.
6
+
7
+ Improve performance of documents displaying lots of highlighted spaces by using a CSS background instead of pseudo-element.
8
+
9
+ ### New features
10
+
11
+ `placeholder` now allows a function that constructs the placedholder DOM to be passed in, and uses `cloneNode` when a raw element is passed in, to prevent adding the same element to multiple editors.
12
+
13
+ ## 6.33.1 (2024-08-30)
14
+
15
+ ### Bug fixes
16
+
17
+ Work around odd behavior in Chrome's newly supported `caretPositionFromPoint` method, which could cause CodeMirror to crash with a null dereference.
18
+
1
19
  ## 6.33.0 (2024-08-24)
2
20
 
3
21
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -20,12 +20,6 @@ function getSelection(root) {
20
20
  function contains(dom, node) {
21
21
  return node ? dom == node || dom.contains(node.nodeType != 1 ? node.parentNode : node) : false;
22
22
  }
23
- function deepActiveElement(doc) {
24
- let elt = doc.activeElement;
25
- while (elt && elt.shadowRoot)
26
- elt = elt.shadowRoot.activeElement;
27
- return elt;
28
- }
29
23
  function hasSelection(dom, selection) {
30
24
  if (!selection.anchorNode)
31
25
  return false;
@@ -3581,6 +3575,11 @@ function posAtCoords(view, coords, precise, bias = -1) {
3581
3575
  node = undefined;
3582
3576
  }
3583
3577
  }
3578
+ // Chrome will return offsets into <input> elements without child
3579
+ // nodes, which will lead to a null deref below, so clip the
3580
+ // offset to the node size.
3581
+ if (node)
3582
+ offset = Math.min(maxOffset(node), offset);
3584
3583
  }
3585
3584
  // No luck, do our own (potentially expensive) search
3586
3585
  if (!node || !view.docView.dom.contains(node)) {
@@ -4130,7 +4129,6 @@ function selectionFromPoints(points, base) {
4130
4129
  return anchor > -1 && head > -1 ? state.EditorSelection.single(anchor + base, head + base) : null;
4131
4130
  }
4132
4131
 
4133
- // This will also be where dragging info and such goes
4134
4132
  class InputState {
4135
4133
  setSelectionOrigin(origin) {
4136
4134
  this.lastSelectionOrigin = origin;
@@ -6574,11 +6572,9 @@ const baseTheme$1 = buildTheme("." + baseThemeID, {
6574
6572
  display: "inline-block",
6575
6573
  verticalAlign: "top",
6576
6574
  },
6577
- ".cm-highlightSpace:before": {
6578
- content: "attr(data-display)",
6579
- position: "absolute",
6580
- pointerEvents: "none",
6581
- color: "#888"
6575
+ ".cm-highlightSpace": {
6576
+ backgroundImage: "radial-gradient(circle at 50% 55%, #aaa 20%, transparent 5%)",
6577
+ backgroundPosition: "center",
6582
6578
  },
6583
6579
  ".cm-highlightTab": {
6584
6580
  backgroundImage: `url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="200" height="20"><path stroke="%23888" stroke-width="1" fill="none" d="M1 10H196L190 5M190 15L196 10M197 4L197 16"/></svg>')`,
@@ -6776,7 +6772,7 @@ class DOMObserver {
6776
6772
  if (!this.readSelectionRange() || this.delayedAndroidKey)
6777
6773
  return;
6778
6774
  let { view } = this, sel = this.selectionRange;
6779
- if (view.state.facet(editable) ? view.root.activeElement != this.dom : !hasSelection(view.dom, sel))
6775
+ if (view.state.facet(editable) ? view.root.activeElement != this.dom : !hasSelection(this.dom, sel))
6780
6776
  return;
6781
6777
  let context = sel.anchorNode && view.docView.nearest(sel.anchorNode);
6782
6778
  if (context && context.ignoreEvent(event)) {
@@ -6804,7 +6800,7 @@ class DOMObserver {
6804
6800
  if (!selection)
6805
6801
  return false;
6806
6802
  let range = browser.safari && view.root.nodeType == 11 &&
6807
- deepActiveElement(this.dom.ownerDocument) == this.dom &&
6803
+ view.root.activeElement == this.dom &&
6808
6804
  safariSelectionRangeHack(this.view, selection) || selection;
6809
6805
  if (!range || this.selectionRange.eq(range))
6810
6806
  return false;
@@ -9492,11 +9488,13 @@ class Placeholder extends WidgetType {
9492
9488
  super();
9493
9489
  this.content = content;
9494
9490
  }
9495
- toDOM() {
9491
+ toDOM(view) {
9496
9492
  let wrap = document.createElement("span");
9497
9493
  wrap.className = "cm-placeholder";
9498
9494
  wrap.style.pointerEvents = "none";
9499
- wrap.appendChild(typeof this.content == "string" ? document.createTextNode(this.content) : this.content);
9495
+ wrap.appendChild(typeof this.content == "string" ? document.createTextNode(this.content) :
9496
+ typeof this.content == "function" ? this.content(view) :
9497
+ this.content.cloneNode(true));
9500
9498
  if (typeof this.content == "string")
9501
9499
  wrap.setAttribute("aria-label", "placeholder " + this.content);
9502
9500
  else
@@ -11031,20 +11029,6 @@ function highlightActiveLineGutter() {
11031
11029
  return activeLineGutterHighlighter;
11032
11030
  }
11033
11031
 
11034
- const WhitespaceDeco = new Map();
11035
- function getWhitespaceDeco(space) {
11036
- let deco = WhitespaceDeco.get(space);
11037
- if (!deco)
11038
- WhitespaceDeco.set(space, deco = Decoration.mark({
11039
- attributes: space === "\t" ? {
11040
- class: "cm-highlightTab",
11041
- } : {
11042
- class: "cm-highlightSpace",
11043
- "data-display": space.replace(/ /g, "·")
11044
- }
11045
- }));
11046
- return deco;
11047
- }
11048
11032
  function matcher(decorator) {
11049
11033
  return ViewPlugin.define(view => ({
11050
11034
  decorations: decorator.createDeco(view),
@@ -11055,9 +11039,11 @@ function matcher(decorator) {
11055
11039
  decorations: v => v.decorations
11056
11040
  });
11057
11041
  }
11042
+ const tabDeco = Decoration.mark({ class: "cm-highlightTab" });
11043
+ const spaceDeco = Decoration.mark({ class: "cm-highlightSpace" });
11058
11044
  const whitespaceHighlighter = matcher(new MatchDecorator({
11059
- regexp: /\t| +/g,
11060
- decoration: match => getWhitespaceDeco(match[0]),
11045
+ regexp: /\t| /g,
11046
+ decoration: match => match[0] == "\t" ? tabDeco : spaceDeco,
11061
11047
  boundary: /\S/,
11062
11048
  }));
11063
11049
  /**
package/dist/index.d.cts CHANGED
@@ -1585,7 +1585,7 @@ declare function highlightActiveLine(): Extension;
1585
1585
  Extension that enables a placeholder—a piece of example content
1586
1586
  to show when the editor is empty.
1587
1587
  */
1588
- declare function placeholder(content: string | HTMLElement): Extension;
1588
+ declare function placeholder(content: string | HTMLElement | ((view: EditorView) => HTMLElement)): Extension;
1589
1589
 
1590
1590
  /**
1591
1591
  Markers shown in a [layer](https://codemirror.net/6/docs/ref/#view.layer) must conform to this
package/dist/index.d.ts CHANGED
@@ -1585,7 +1585,7 @@ declare function highlightActiveLine(): Extension;
1585
1585
  Extension that enables a placeholder—a piece of example content
1586
1586
  to show when the editor is empty.
1587
1587
  */
1588
- declare function placeholder(content: string | HTMLElement): Extension;
1588
+ declare function placeholder(content: string | HTMLElement | ((view: EditorView) => HTMLElement)): Extension;
1589
1589
 
1590
1590
  /**
1591
1591
  Markers shown in a [layer](https://codemirror.net/6/docs/ref/#view.layer) must conform to this
package/dist/index.js CHANGED
@@ -18,12 +18,6 @@ function getSelection(root) {
18
18
  function contains(dom, node) {
19
19
  return node ? dom == node || dom.contains(node.nodeType != 1 ? node.parentNode : node) : false;
20
20
  }
21
- function deepActiveElement(doc) {
22
- let elt = doc.activeElement;
23
- while (elt && elt.shadowRoot)
24
- elt = elt.shadowRoot.activeElement;
25
- return elt;
26
- }
27
21
  function hasSelection(dom, selection) {
28
22
  if (!selection.anchorNode)
29
23
  return false;
@@ -3577,6 +3571,11 @@ function posAtCoords(view, coords, precise, bias = -1) {
3577
3571
  node = undefined;
3578
3572
  }
3579
3573
  }
3574
+ // Chrome will return offsets into <input> elements without child
3575
+ // nodes, which will lead to a null deref below, so clip the
3576
+ // offset to the node size.
3577
+ if (node)
3578
+ offset = Math.min(maxOffset(node), offset);
3580
3579
  }
3581
3580
  // No luck, do our own (potentially expensive) search
3582
3581
  if (!node || !view.docView.dom.contains(node)) {
@@ -4126,7 +4125,6 @@ function selectionFromPoints(points, base) {
4126
4125
  return anchor > -1 && head > -1 ? EditorSelection.single(anchor + base, head + base) : null;
4127
4126
  }
4128
4127
 
4129
- // This will also be where dragging info and such goes
4130
4128
  class InputState {
4131
4129
  setSelectionOrigin(origin) {
4132
4130
  this.lastSelectionOrigin = origin;
@@ -6569,11 +6567,9 @@ const baseTheme$1 = /*@__PURE__*/buildTheme("." + baseThemeID, {
6569
6567
  display: "inline-block",
6570
6568
  verticalAlign: "top",
6571
6569
  },
6572
- ".cm-highlightSpace:before": {
6573
- content: "attr(data-display)",
6574
- position: "absolute",
6575
- pointerEvents: "none",
6576
- color: "#888"
6570
+ ".cm-highlightSpace": {
6571
+ backgroundImage: "radial-gradient(circle at 50% 55%, #aaa 20%, transparent 5%)",
6572
+ backgroundPosition: "center",
6577
6573
  },
6578
6574
  ".cm-highlightTab": {
6579
6575
  backgroundImage: `url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="200" height="20"><path stroke="%23888" stroke-width="1" fill="none" d="M1 10H196L190 5M190 15L196 10M197 4L197 16"/></svg>')`,
@@ -6771,7 +6767,7 @@ class DOMObserver {
6771
6767
  if (!this.readSelectionRange() || this.delayedAndroidKey)
6772
6768
  return;
6773
6769
  let { view } = this, sel = this.selectionRange;
6774
- if (view.state.facet(editable) ? view.root.activeElement != this.dom : !hasSelection(view.dom, sel))
6770
+ if (view.state.facet(editable) ? view.root.activeElement != this.dom : !hasSelection(this.dom, sel))
6775
6771
  return;
6776
6772
  let context = sel.anchorNode && view.docView.nearest(sel.anchorNode);
6777
6773
  if (context && context.ignoreEvent(event)) {
@@ -6799,7 +6795,7 @@ class DOMObserver {
6799
6795
  if (!selection)
6800
6796
  return false;
6801
6797
  let range = browser.safari && view.root.nodeType == 11 &&
6802
- deepActiveElement(this.dom.ownerDocument) == this.dom &&
6798
+ view.root.activeElement == this.dom &&
6803
6799
  safariSelectionRangeHack(this.view, selection) || selection;
6804
6800
  if (!range || this.selectionRange.eq(range))
6805
6801
  return false;
@@ -9487,11 +9483,13 @@ class Placeholder extends WidgetType {
9487
9483
  super();
9488
9484
  this.content = content;
9489
9485
  }
9490
- toDOM() {
9486
+ toDOM(view) {
9491
9487
  let wrap = document.createElement("span");
9492
9488
  wrap.className = "cm-placeholder";
9493
9489
  wrap.style.pointerEvents = "none";
9494
- wrap.appendChild(typeof this.content == "string" ? document.createTextNode(this.content) : this.content);
9490
+ wrap.appendChild(typeof this.content == "string" ? document.createTextNode(this.content) :
9491
+ typeof this.content == "function" ? this.content(view) :
9492
+ this.content.cloneNode(true));
9495
9493
  if (typeof this.content == "string")
9496
9494
  wrap.setAttribute("aria-label", "placeholder " + this.content);
9497
9495
  else
@@ -11026,20 +11024,6 @@ function highlightActiveLineGutter() {
11026
11024
  return activeLineGutterHighlighter;
11027
11025
  }
11028
11026
 
11029
- const WhitespaceDeco = /*@__PURE__*/new Map();
11030
- function getWhitespaceDeco(space) {
11031
- let deco = WhitespaceDeco.get(space);
11032
- if (!deco)
11033
- WhitespaceDeco.set(space, deco = Decoration.mark({
11034
- attributes: space === "\t" ? {
11035
- class: "cm-highlightTab",
11036
- } : {
11037
- class: "cm-highlightSpace",
11038
- "data-display": space.replace(/ /g, "·")
11039
- }
11040
- }));
11041
- return deco;
11042
- }
11043
11027
  function matcher(decorator) {
11044
11028
  return ViewPlugin.define(view => ({
11045
11029
  decorations: decorator.createDeco(view),
@@ -11050,9 +11034,11 @@ function matcher(decorator) {
11050
11034
  decorations: v => v.decorations
11051
11035
  });
11052
11036
  }
11037
+ const tabDeco = /*@__PURE__*/Decoration.mark({ class: "cm-highlightTab" });
11038
+ const spaceDeco = /*@__PURE__*/Decoration.mark({ class: "cm-highlightSpace" });
11053
11039
  const whitespaceHighlighter = /*@__PURE__*/matcher(/*@__PURE__*/new MatchDecorator({
11054
- regexp: /\t| +/g,
11055
- decoration: match => getWhitespaceDeco(match[0]),
11040
+ regexp: /\t| /g,
11041
+ decoration: match => match[0] == "\t" ? tabDeco : spaceDeco,
11056
11042
  boundary: /\S/,
11057
11043
  }));
11058
11044
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "6.33.0",
3
+ "version": "6.34.0",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",