@codemirror/view 6.9.4 → 6.9.5

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.9.5 (2023-04-17)
2
+
3
+ ### Bug fixes
4
+
5
+ Avoid disrupting the composition in specific cases where Safari invasively changes the DOM structure in the middle of a composition.
6
+
7
+ Fix a bug that prevented `destroy` being called on hover tooltips.
8
+
9
+ Fix a bug where the editor could take focus when content changes required it to restore the DOM selection.
10
+
11
+ Fix height layout corruption caused by a division by zero.
12
+
13
+ Make sure styles targeting the editor's focus status are specific enough to not cause them to apply to editors nested inside another focused editor. This will require themes to adjust their selection background styles to match the new specificity.
14
+
1
15
  ## 6.9.4 (2023-04-11)
2
16
 
3
17
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -910,14 +910,14 @@ class CompositionView extends WidgetView {
910
910
  let { topView, text } = this.widget;
911
911
  if (!topView)
912
912
  return new DOMPos(text, Math.min(pos, text.nodeValue.length));
913
- return scanCompositionTree(pos, 0, topView, text, (v, p) => v.domAtPos(p), p => new DOMPos(text, Math.min(p, text.nodeValue.length)));
913
+ return scanCompositionTree(pos, 0, topView, text, this.length - topView.length, (v, p) => v.domAtPos(p), (text, p) => new DOMPos(text, Math.min(p, text.nodeValue.length)));
914
914
  }
915
915
  sync() { this.setDOM(this.widget.toDOM()); }
916
916
  localPosFromDOM(node, offset) {
917
917
  let { topView, text } = this.widget;
918
918
  if (!topView)
919
919
  return Math.min(offset, this.length);
920
- return posFromDOMInCompositionTree(node, offset, topView, text);
920
+ return posFromDOMInCompositionTree(node, offset, topView, text, this.length - topView.length);
921
921
  }
922
922
  ignoreMutation() { return false; }
923
923
  get overrideDOMText() { return null; }
@@ -925,7 +925,7 @@ class CompositionView extends WidgetView {
925
925
  let { topView, text } = this.widget;
926
926
  if (!topView)
927
927
  return textCoords(text, pos, side);
928
- return scanCompositionTree(pos, side, topView, text, (v, pos, side) => v.coordsAt(pos, side), (pos, side) => textCoords(text, pos, side));
928
+ return scanCompositionTree(pos, side, topView, text, this.length - topView.length, (v, pos, side) => v.coordsAt(pos, side), (text, pos, side) => textCoords(text, pos, side));
929
929
  }
930
930
  destroy() {
931
931
  var _a;
@@ -938,35 +938,68 @@ class CompositionView extends WidgetView {
938
938
  // Uses the old structure of a chunk of content view frozen for
939
939
  // composition to try and find a reasonable DOM location for the given
940
940
  // offset.
941
- function scanCompositionTree(pos, side, view, text, enterView, fromText) {
941
+ function scanCompositionTree(pos, side, view, text, dLen, enterView, fromText) {
942
942
  if (view instanceof MarkView) {
943
943
  for (let child = view.dom.firstChild; child; child = child.nextSibling) {
944
944
  let desc = ContentView.get(child);
945
- if (!desc)
946
- return fromText(pos, side);
947
- let hasComp = contains(child, text);
948
- let len = desc.length + (hasComp ? text.nodeValue.length : 0);
949
- if (pos < len || pos == len && desc.getSide() <= 0)
950
- return hasComp ? scanCompositionTree(pos, side, desc, text, enterView, fromText) : enterView(desc, pos, side);
951
- pos -= len;
945
+ if (!desc) {
946
+ let inner = scanCompositionNode(pos, side, child, fromText);
947
+ if (typeof inner != "number")
948
+ return inner;
949
+ pos = inner;
950
+ }
951
+ else {
952
+ let hasComp = contains(child, text);
953
+ let len = desc.length + (hasComp ? dLen : 0);
954
+ if (pos < len || pos == len && desc.getSide() <= 0)
955
+ return hasComp ? scanCompositionTree(pos, side, desc, text, dLen, enterView, fromText) : enterView(desc, pos, side);
956
+ pos -= len;
957
+ }
952
958
  }
953
959
  return enterView(view, view.length, -1);
954
960
  }
955
961
  else if (view.dom == text) {
956
- return fromText(pos, side);
962
+ return fromText(text, pos, side);
957
963
  }
958
964
  else {
959
965
  return enterView(view, pos, side);
960
966
  }
961
967
  }
962
- function posFromDOMInCompositionTree(node, offset, view, text) {
968
+ function scanCompositionNode(pos, side, node, fromText) {
969
+ if (node.nodeType == 3) {
970
+ let len = node.nodeValue.length;
971
+ if (pos <= len)
972
+ return fromText(node, pos, side);
973
+ pos -= len;
974
+ }
975
+ else if (node.nodeType == 1 && node.contentEditable != "false") {
976
+ for (let child = node.firstChild; child; child = child.nextSibling) {
977
+ let inner = scanCompositionNode(pos, side, child, fromText);
978
+ if (typeof inner != "number")
979
+ return inner;
980
+ pos = inner;
981
+ }
982
+ }
983
+ return pos;
984
+ }
985
+ function posFromDOMInCompositionTree(node, offset, view, text, dLen) {
963
986
  if (view instanceof MarkView) {
964
987
  let pos = 0;
965
- for (let child of view.children) {
966
- let hasComp = contains(child.dom, text);
967
- if (contains(child.dom, node))
968
- return pos + (hasComp ? posFromDOMInCompositionTree(node, offset, child, text) : child.localPosFromDOM(node, offset));
969
- pos += hasComp ? text.nodeValue.length : child.length;
988
+ for (let child = view.dom.firstChild; child; child = child.nextSibling) {
989
+ let childView = ContentView.get(child);
990
+ if (childView) {
991
+ let hasComp = contains(child, text);
992
+ if (contains(child, node))
993
+ return pos + (hasComp ? posFromDOMInCompositionTree(node, offset, childView, text, dLen)
994
+ : childView.localPosFromDOM(node, offset));
995
+ pos += childView.length + (hasComp ? dLen : 0);
996
+ }
997
+ else {
998
+ let inner = posFromDOMInOpaqueNode(node, offset, child);
999
+ if (inner.result != null)
1000
+ return pos + inner.result;
1001
+ pos += inner.size;
1002
+ }
970
1003
  }
971
1004
  }
972
1005
  else if (view.dom == text) {
@@ -974,6 +1007,27 @@ function posFromDOMInCompositionTree(node, offset, view, text) {
974
1007
  }
975
1008
  return view.localPosFromDOM(node, offset);
976
1009
  }
1010
+ function posFromDOMInOpaqueNode(node, offset, target) {
1011
+ if (target.nodeType == 3) {
1012
+ return node == target ? { result: offset } : { size: target.nodeValue.length };
1013
+ }
1014
+ else if (target.nodeType == 1 && target.contentEditable != "false") {
1015
+ let pos = 0;
1016
+ for (let child = target.firstChild, i = 0;; child = child.nextSibling, i++) {
1017
+ if (node == target && i == offset)
1018
+ return { result: pos };
1019
+ if (!child)
1020
+ return { size: pos };
1021
+ let inner = posFromDOMInOpaqueNode(node, offset, child);
1022
+ if (inner.result != null)
1023
+ return { result: offset + inner.result };
1024
+ pos += inner.size;
1025
+ }
1026
+ }
1027
+ else {
1028
+ return target.contains(node) ? { result: 0 } : { size: 0 };
1029
+ }
1030
+ }
977
1031
  // These are drawn around uneditable widgets to avoid a number of
978
1032
  // browser bugs that show up when the cursor is directly next to
979
1033
  // uneditable inline content.
@@ -2648,7 +2702,10 @@ class DocView extends ContentView {
2648
2702
  updateSelection(mustRead = false, fromPointer = false) {
2649
2703
  if (mustRead || !this.view.observer.selectionRange.focusNode)
2650
2704
  this.view.observer.readSelectionRange();
2651
- if (!(fromPointer || this.mayControlSelection()))
2705
+ let activeElt = this.view.root.activeElement, focused = activeElt == this.dom;
2706
+ let selectionNotFocus = !focused &&
2707
+ hasSelection(this.dom, this.view.observer.selectionRange) && !(activeElt && this.dom.contains(activeElt));
2708
+ if (!(focused || fromPointer || selectionNotFocus))
2652
2709
  return;
2653
2710
  let force = this.forceSelection;
2654
2711
  this.forceSelection = false;
@@ -2718,6 +2775,11 @@ class DocView extends ContentView {
2718
2775
  rawSel.removeAllRanges();
2719
2776
  rawSel.addRange(range);
2720
2777
  }
2778
+ if (selectionNotFocus && this.view.root.activeElement == this.dom) {
2779
+ this.dom.blur();
2780
+ if (activeElt)
2781
+ activeElt.focus();
2782
+ }
2721
2783
  });
2722
2784
  this.view.observer.setSelectionRange(anchor, head);
2723
2785
  }
@@ -2751,11 +2813,6 @@ class DocView extends ContentView {
2751
2813
  if (view.docView.posFromDOM(newRange.anchorNode, newRange.anchorOffset) != cursor.from)
2752
2814
  sel.collapse(anchorNode, anchorOffset);
2753
2815
  }
2754
- mayControlSelection() {
2755
- let active = this.view.root.activeElement;
2756
- return active == this.dom ||
2757
- hasSelection(this.dom, this.view.observer.selectionRange) && !(active && this.dom.contains(active));
2758
- }
2759
2816
  nearest(dom) {
2760
2817
  for (let cur = dom; cur;) {
2761
2818
  let domView = ContentView.get(cur);
@@ -4492,7 +4549,8 @@ class HeightMapGap extends HeightMap {
4492
4549
  if (oracle.lineWrapping) {
4493
4550
  let totalPerLine = Math.min(this.height, oracle.lineHeight * lines);
4494
4551
  perLine = totalPerLine / lines;
4495
- perChar = (this.height - totalPerLine) / (this.length - lines - 1);
4552
+ if (this.length > lines + 1)
4553
+ perChar = (this.height - totalPerLine) / (this.length - lines - 1);
4496
4554
  }
4497
4555
  else {
4498
4556
  perLine = this.height / lines;
@@ -5508,16 +5566,16 @@ const baseTheme$1 = buildTheme("." + baseThemeID, {
5508
5566
  "&dark .cm-selectionBackground": {
5509
5567
  background: "#222"
5510
5568
  },
5511
- "&light.cm-focused .cm-selectionBackground": {
5569
+ "&light.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
5512
5570
  background: "#d7d4f0"
5513
5571
  },
5514
- "&dark.cm-focused .cm-selectionBackground": {
5572
+ "&dark.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
5515
5573
  background: "#233"
5516
5574
  },
5517
5575
  ".cm-cursorLayer": {
5518
5576
  pointerEvents: "none"
5519
5577
  },
5520
- "&.cm-focused .cm-cursorLayer": {
5578
+ "&.cm-focused > .cm-scroller > .cm-cursorLayer": {
5521
5579
  animation: "steps(1) cm-blink 1.2s infinite"
5522
5580
  },
5523
5581
  // Two animations defined so that we can switch between them to
@@ -5539,7 +5597,7 @@ const baseTheme$1 = buildTheme("." + baseThemeID, {
5539
5597
  ".cm-dropCursor": {
5540
5598
  position: "absolute"
5541
5599
  },
5542
- "&.cm-focused .cm-cursor": {
5600
+ "&.cm-focused > .cm-scroller > .cm-cursorLayer .cm-cursor": {
5543
5601
  display: "block"
5544
5602
  },
5545
5603
  "&light .cm-activeLine": { backgroundColor: "#cceeff44" },
@@ -8811,6 +8869,11 @@ class HoverTooltipHost {
8811
8869
  update(update) {
8812
8870
  this.manager.update(update);
8813
8871
  }
8872
+ destroy() {
8873
+ var _a;
8874
+ for (let t of this.manager.tooltipViews)
8875
+ (_a = t.destroy) === null || _a === void 0 ? void 0 : _a.call(t);
8876
+ }
8814
8877
  }
8815
8878
  const showHoverTooltipHost = showTooltip.compute([showHoverTooltip], state => {
8816
8879
  let tooltips = state.facet(showHoverTooltip).filter(t => t);
package/dist/index.js CHANGED
@@ -906,14 +906,14 @@ class CompositionView extends WidgetView {
906
906
  let { topView, text } = this.widget;
907
907
  if (!topView)
908
908
  return new DOMPos(text, Math.min(pos, text.nodeValue.length));
909
- return scanCompositionTree(pos, 0, topView, text, (v, p) => v.domAtPos(p), p => new DOMPos(text, Math.min(p, text.nodeValue.length)));
909
+ return scanCompositionTree(pos, 0, topView, text, this.length - topView.length, (v, p) => v.domAtPos(p), (text, p) => new DOMPos(text, Math.min(p, text.nodeValue.length)));
910
910
  }
911
911
  sync() { this.setDOM(this.widget.toDOM()); }
912
912
  localPosFromDOM(node, offset) {
913
913
  let { topView, text } = this.widget;
914
914
  if (!topView)
915
915
  return Math.min(offset, this.length);
916
- return posFromDOMInCompositionTree(node, offset, topView, text);
916
+ return posFromDOMInCompositionTree(node, offset, topView, text, this.length - topView.length);
917
917
  }
918
918
  ignoreMutation() { return false; }
919
919
  get overrideDOMText() { return null; }
@@ -921,7 +921,7 @@ class CompositionView extends WidgetView {
921
921
  let { topView, text } = this.widget;
922
922
  if (!topView)
923
923
  return textCoords(text, pos, side);
924
- return scanCompositionTree(pos, side, topView, text, (v, pos, side) => v.coordsAt(pos, side), (pos, side) => textCoords(text, pos, side));
924
+ return scanCompositionTree(pos, side, topView, text, this.length - topView.length, (v, pos, side) => v.coordsAt(pos, side), (text, pos, side) => textCoords(text, pos, side));
925
925
  }
926
926
  destroy() {
927
927
  var _a;
@@ -934,35 +934,68 @@ class CompositionView extends WidgetView {
934
934
  // Uses the old structure of a chunk of content view frozen for
935
935
  // composition to try and find a reasonable DOM location for the given
936
936
  // offset.
937
- function scanCompositionTree(pos, side, view, text, enterView, fromText) {
937
+ function scanCompositionTree(pos, side, view, text, dLen, enterView, fromText) {
938
938
  if (view instanceof MarkView) {
939
939
  for (let child = view.dom.firstChild; child; child = child.nextSibling) {
940
940
  let desc = ContentView.get(child);
941
- if (!desc)
942
- return fromText(pos, side);
943
- let hasComp = contains(child, text);
944
- let len = desc.length + (hasComp ? text.nodeValue.length : 0);
945
- if (pos < len || pos == len && desc.getSide() <= 0)
946
- return hasComp ? scanCompositionTree(pos, side, desc, text, enterView, fromText) : enterView(desc, pos, side);
947
- pos -= len;
941
+ if (!desc) {
942
+ let inner = scanCompositionNode(pos, side, child, fromText);
943
+ if (typeof inner != "number")
944
+ return inner;
945
+ pos = inner;
946
+ }
947
+ else {
948
+ let hasComp = contains(child, text);
949
+ let len = desc.length + (hasComp ? dLen : 0);
950
+ if (pos < len || pos == len && desc.getSide() <= 0)
951
+ return hasComp ? scanCompositionTree(pos, side, desc, text, dLen, enterView, fromText) : enterView(desc, pos, side);
952
+ pos -= len;
953
+ }
948
954
  }
949
955
  return enterView(view, view.length, -1);
950
956
  }
951
957
  else if (view.dom == text) {
952
- return fromText(pos, side);
958
+ return fromText(text, pos, side);
953
959
  }
954
960
  else {
955
961
  return enterView(view, pos, side);
956
962
  }
957
963
  }
958
- function posFromDOMInCompositionTree(node, offset, view, text) {
964
+ function scanCompositionNode(pos, side, node, fromText) {
965
+ if (node.nodeType == 3) {
966
+ let len = node.nodeValue.length;
967
+ if (pos <= len)
968
+ return fromText(node, pos, side);
969
+ pos -= len;
970
+ }
971
+ else if (node.nodeType == 1 && node.contentEditable != "false") {
972
+ for (let child = node.firstChild; child; child = child.nextSibling) {
973
+ let inner = scanCompositionNode(pos, side, child, fromText);
974
+ if (typeof inner != "number")
975
+ return inner;
976
+ pos = inner;
977
+ }
978
+ }
979
+ return pos;
980
+ }
981
+ function posFromDOMInCompositionTree(node, offset, view, text, dLen) {
959
982
  if (view instanceof MarkView) {
960
983
  let pos = 0;
961
- for (let child of view.children) {
962
- let hasComp = contains(child.dom, text);
963
- if (contains(child.dom, node))
964
- return pos + (hasComp ? posFromDOMInCompositionTree(node, offset, child, text) : child.localPosFromDOM(node, offset));
965
- pos += hasComp ? text.nodeValue.length : child.length;
984
+ for (let child = view.dom.firstChild; child; child = child.nextSibling) {
985
+ let childView = ContentView.get(child);
986
+ if (childView) {
987
+ let hasComp = contains(child, text);
988
+ if (contains(child, node))
989
+ return pos + (hasComp ? posFromDOMInCompositionTree(node, offset, childView, text, dLen)
990
+ : childView.localPosFromDOM(node, offset));
991
+ pos += childView.length + (hasComp ? dLen : 0);
992
+ }
993
+ else {
994
+ let inner = posFromDOMInOpaqueNode(node, offset, child);
995
+ if (inner.result != null)
996
+ return pos + inner.result;
997
+ pos += inner.size;
998
+ }
966
999
  }
967
1000
  }
968
1001
  else if (view.dom == text) {
@@ -970,6 +1003,27 @@ function posFromDOMInCompositionTree(node, offset, view, text) {
970
1003
  }
971
1004
  return view.localPosFromDOM(node, offset);
972
1005
  }
1006
+ function posFromDOMInOpaqueNode(node, offset, target) {
1007
+ if (target.nodeType == 3) {
1008
+ return node == target ? { result: offset } : { size: target.nodeValue.length };
1009
+ }
1010
+ else if (target.nodeType == 1 && target.contentEditable != "false") {
1011
+ let pos = 0;
1012
+ for (let child = target.firstChild, i = 0;; child = child.nextSibling, i++) {
1013
+ if (node == target && i == offset)
1014
+ return { result: pos };
1015
+ if (!child)
1016
+ return { size: pos };
1017
+ let inner = posFromDOMInOpaqueNode(node, offset, child);
1018
+ if (inner.result != null)
1019
+ return { result: offset + inner.result };
1020
+ pos += inner.size;
1021
+ }
1022
+ }
1023
+ else {
1024
+ return target.contains(node) ? { result: 0 } : { size: 0 };
1025
+ }
1026
+ }
973
1027
  // These are drawn around uneditable widgets to avoid a number of
974
1028
  // browser bugs that show up when the cursor is directly next to
975
1029
  // uneditable inline content.
@@ -2642,7 +2696,10 @@ class DocView extends ContentView {
2642
2696
  updateSelection(mustRead = false, fromPointer = false) {
2643
2697
  if (mustRead || !this.view.observer.selectionRange.focusNode)
2644
2698
  this.view.observer.readSelectionRange();
2645
- if (!(fromPointer || this.mayControlSelection()))
2699
+ let activeElt = this.view.root.activeElement, focused = activeElt == this.dom;
2700
+ let selectionNotFocus = !focused &&
2701
+ hasSelection(this.dom, this.view.observer.selectionRange) && !(activeElt && this.dom.contains(activeElt));
2702
+ if (!(focused || fromPointer || selectionNotFocus))
2646
2703
  return;
2647
2704
  let force = this.forceSelection;
2648
2705
  this.forceSelection = false;
@@ -2712,6 +2769,11 @@ class DocView extends ContentView {
2712
2769
  rawSel.removeAllRanges();
2713
2770
  rawSel.addRange(range);
2714
2771
  }
2772
+ if (selectionNotFocus && this.view.root.activeElement == this.dom) {
2773
+ this.dom.blur();
2774
+ if (activeElt)
2775
+ activeElt.focus();
2776
+ }
2715
2777
  });
2716
2778
  this.view.observer.setSelectionRange(anchor, head);
2717
2779
  }
@@ -2745,11 +2807,6 @@ class DocView extends ContentView {
2745
2807
  if (view.docView.posFromDOM(newRange.anchorNode, newRange.anchorOffset) != cursor.from)
2746
2808
  sel.collapse(anchorNode, anchorOffset);
2747
2809
  }
2748
- mayControlSelection() {
2749
- let active = this.view.root.activeElement;
2750
- return active == this.dom ||
2751
- hasSelection(this.dom, this.view.observer.selectionRange) && !(active && this.dom.contains(active));
2752
- }
2753
2810
  nearest(dom) {
2754
2811
  for (let cur = dom; cur;) {
2755
2812
  let domView = ContentView.get(cur);
@@ -4485,7 +4542,8 @@ class HeightMapGap extends HeightMap {
4485
4542
  if (oracle.lineWrapping) {
4486
4543
  let totalPerLine = Math.min(this.height, oracle.lineHeight * lines);
4487
4544
  perLine = totalPerLine / lines;
4488
- perChar = (this.height - totalPerLine) / (this.length - lines - 1);
4545
+ if (this.length > lines + 1)
4546
+ perChar = (this.height - totalPerLine) / (this.length - lines - 1);
4489
4547
  }
4490
4548
  else {
4491
4549
  perLine = this.height / lines;
@@ -5501,16 +5559,16 @@ const baseTheme$1 = /*@__PURE__*/buildTheme("." + baseThemeID, {
5501
5559
  "&dark .cm-selectionBackground": {
5502
5560
  background: "#222"
5503
5561
  },
5504
- "&light.cm-focused .cm-selectionBackground": {
5562
+ "&light.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
5505
5563
  background: "#d7d4f0"
5506
5564
  },
5507
- "&dark.cm-focused .cm-selectionBackground": {
5565
+ "&dark.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
5508
5566
  background: "#233"
5509
5567
  },
5510
5568
  ".cm-cursorLayer": {
5511
5569
  pointerEvents: "none"
5512
5570
  },
5513
- "&.cm-focused .cm-cursorLayer": {
5571
+ "&.cm-focused > .cm-scroller > .cm-cursorLayer": {
5514
5572
  animation: "steps(1) cm-blink 1.2s infinite"
5515
5573
  },
5516
5574
  // Two animations defined so that we can switch between them to
@@ -5532,7 +5590,7 @@ const baseTheme$1 = /*@__PURE__*/buildTheme("." + baseThemeID, {
5532
5590
  ".cm-dropCursor": {
5533
5591
  position: "absolute"
5534
5592
  },
5535
- "&.cm-focused .cm-cursor": {
5593
+ "&.cm-focused > .cm-scroller > .cm-cursorLayer .cm-cursor": {
5536
5594
  display: "block"
5537
5595
  },
5538
5596
  "&light .cm-activeLine": { backgroundColor: "#cceeff44" },
@@ -8804,6 +8862,11 @@ class HoverTooltipHost {
8804
8862
  update(update) {
8805
8863
  this.manager.update(update);
8806
8864
  }
8865
+ destroy() {
8866
+ var _a;
8867
+ for (let t of this.manager.tooltipViews)
8868
+ (_a = t.destroy) === null || _a === void 0 ? void 0 : _a.call(t);
8869
+ }
8807
8870
  }
8808
8871
  const showHoverTooltipHost = /*@__PURE__*/showTooltip.compute([showHoverTooltip], state => {
8809
8872
  let tooltips = state.facet(showHoverTooltip).filter(t => t);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "6.9.4",
3
+ "version": "6.9.5",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",