prosemirror-view 1.28.2 → 1.28.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,9 @@
1
+ ## 1.28.3 (2022-10-05)
2
+
3
+ ### Bug fixes
4
+
5
+ Work around the five-year-old Safari bug where it won't accurately report the selection inside shadow roots, which would break ProseMirror when put in shadow DOM.
6
+
1
7
  ## 1.28.2 (2022-09-24)
2
8
 
3
9
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -36,28 +36,6 @@ var prosemirrorModel = require('prosemirror-model');
36
36
 
37
37
  var prosemirrorTransform = require('prosemirror-transform');
38
38
 
39
- var nav = typeof navigator != "undefined" ? navigator : null;
40
- var doc = typeof document != "undefined" ? document : null;
41
- var agent = nav && nav.userAgent || "";
42
- var ie_edge = /Edge\/(\d+)/.exec(agent);
43
- var ie_upto10 = /MSIE \d/.exec(agent);
44
- var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(agent);
45
- var ie = !!(ie_upto10 || ie_11up || ie_edge);
46
- var ie_version = ie_upto10 ? document.documentMode : ie_11up ? +ie_11up[1] : ie_edge ? +ie_edge[1] : 0;
47
- var gecko = !ie && /gecko\/(\d+)/i.test(agent);
48
- gecko && +(/Firefox\/(\d+)/.exec(agent) || [0, 0])[1];
49
-
50
- var _chrome = !ie && /Chrome\/(\d+)/.exec(agent);
51
-
52
- var chrome = !!_chrome;
53
- var chrome_version = _chrome ? +_chrome[1] : 0;
54
- var safari = !ie && !!nav && /Apple Computer/.test(nav.vendor);
55
- var ios = safari && (/Mobile\/\w+/.test(agent) || !!nav && nav.maxTouchPoints > 2);
56
- var mac = ios || (nav ? /Mac/.test(nav.platform) : false);
57
- var android = /Android \d/.test(agent);
58
- var webkit = !!doc && "webkitFontSmoothing" in doc.documentElement.style;
59
- var webkit_version = webkit ? +(/\bAppleWebKit\/(\d+)/.exec(navigator.userAgent) || [0, 0])[1] : 0;
60
-
61
39
  var domIndex = function domIndex(node) {
62
40
  for (var index = 0;; index++) {
63
41
  node = node.previousSibling;
@@ -130,9 +108,7 @@ function hasBlockDesc(dom) {
130
108
  }
131
109
 
132
110
  var selectionCollapsed = function selectionCollapsed(domSel) {
133
- var collapsed = domSel.isCollapsed;
134
- if (collapsed && chrome && domSel.rangeCount && !domSel.getRangeAt(0).collapsed) collapsed = false;
135
- return collapsed;
111
+ return domSel.focusNode && isEquivalentPosition(domSel.focusNode, domSel.focusOffset, domSel.anchorNode, domSel.anchorOffset);
136
112
  };
137
113
 
138
114
  function keyEvent(keyCode, key) {
@@ -143,6 +119,38 @@ function keyEvent(keyCode, key) {
143
119
  return event;
144
120
  }
145
121
 
122
+ function deepActiveElement(doc) {
123
+ var elt = doc.activeElement;
124
+
125
+ while (elt && elt.shadowRoot) {
126
+ elt = elt.shadowRoot.activeElement;
127
+ }
128
+
129
+ return elt;
130
+ }
131
+
132
+ var nav = typeof navigator != "undefined" ? navigator : null;
133
+ var doc = typeof document != "undefined" ? document : null;
134
+ var agent = nav && nav.userAgent || "";
135
+ var ie_edge = /Edge\/(\d+)/.exec(agent);
136
+ var ie_upto10 = /MSIE \d/.exec(agent);
137
+ var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(agent);
138
+ var ie = !!(ie_upto10 || ie_11up || ie_edge);
139
+ var ie_version = ie_upto10 ? document.documentMode : ie_11up ? +ie_11up[1] : ie_edge ? +ie_edge[1] : 0;
140
+ var gecko = !ie && /gecko\/(\d+)/i.test(agent);
141
+ gecko && +(/Firefox\/(\d+)/.exec(agent) || [0, 0])[1];
142
+
143
+ var _chrome = !ie && /Chrome\/(\d+)/.exec(agent);
144
+
145
+ var chrome = !!_chrome;
146
+ var chrome_version = _chrome ? +_chrome[1] : 0;
147
+ var safari = !ie && !!nav && /Apple Computer/.test(nav.vendor);
148
+ var ios = safari && (/Mobile\/\w+/.test(agent) || !!nav && nav.maxTouchPoints > 2);
149
+ var mac = ios || (nav ? /Mac/.test(nav.platform) : false);
150
+ var android = /Android \d/.test(agent);
151
+ var webkit = !!doc && "webkitFontSmoothing" in doc.documentElement.style;
152
+ var webkit_version = webkit ? +(/\bAppleWebKit\/(\d+)/.exec(navigator.userAgent) || [0, 0])[1] : 0;
153
+
146
154
  function windowRect(doc) {
147
155
  return {
148
156
  left: 0,
@@ -654,15 +662,27 @@ function endOfTextblockHorizontal(view, state, dir) {
654
662
  var sel = view.domSelection();
655
663
  if (!maybeRTL.test($head.parent.textContent) || !sel.modify) return dir == "left" || dir == "backward" ? atStart : atEnd;
656
664
  return withFlushedState(view, state, function () {
657
- var oldRange = sel.getRangeAt(0),
658
- oldNode = sel.focusNode,
659
- oldOff = sel.focusOffset;
665
+ var _view$domSelectionRan = view.domSelectionRange(),
666
+ oldNode = _view$domSelectionRan.focusNode,
667
+ oldOff = _view$domSelectionRan.focusOffset,
668
+ anchorNode = _view$domSelectionRan.anchorNode,
669
+ anchorOffset = _view$domSelectionRan.anchorOffset;
670
+
660
671
  var oldBidiLevel = sel.caretBidiLevel;
661
672
  sel.modify("move", dir, "character");
662
673
  var parentDOM = $head.depth ? view.docView.domAfterPos($head.before()) : view.dom;
663
- var result = !parentDOM.contains(sel.focusNode.nodeType == 1 ? sel.focusNode : sel.focusNode.parentNode) || oldNode == sel.focusNode && oldOff == sel.focusOffset;
664
- sel.removeAllRanges();
665
- sel.addRange(oldRange);
674
+
675
+ var _view$domSelectionRan2 = view.domSelectionRange(),
676
+ newNode = _view$domSelectionRan2.focusNode,
677
+ newOff = _view$domSelectionRan2.focusOffset;
678
+
679
+ var result = newNode && !parentDOM.contains(newNode.nodeType == 1 ? newNode : newNode.parentNode) || oldNode == newNode && oldOff == newOff;
680
+
681
+ try {
682
+ sel.collapse(anchorNode, anchorOffset);
683
+ if (oldNode && (oldNode != anchorNode || oldOff != anchorOffset) && sel.extend) sel.extend(oldNode, oldOff);
684
+ } catch (_) {}
685
+
666
686
  if (oldBidiLevel != null) sel.caretBidiLevel = oldBidiLevel;
667
687
  return result;
668
688
  });
@@ -1102,16 +1122,9 @@ var ViewDesc = function () {
1102
1122
  domSel.collapse(anchorDOM.node, anchorDOM.offset);
1103
1123
 
1104
1124
  try {
1105
- if (anchor != head) {
1106
- try {
1107
- domSel.extend(headDOM.node, headDOM.offset);
1108
- } catch (_) {}
1109
- }
1110
-
1125
+ if (anchor != head) domSel.extend(headDOM.node, headDOM.offset);
1111
1126
  domSelExtended = true;
1112
- } catch (err) {
1113
- if (!(err instanceof DOMException)) throw err;
1114
- }
1127
+ } catch (_) {}
1115
1128
  }
1116
1129
 
1117
1130
  if (!domSelExtended) {
@@ -1496,7 +1509,7 @@ var NodeViewDesc = function (_ViewDesc4) {
1496
1509
  from = _view$state$selection.from,
1497
1510
  to = _view$state$selection.to;
1498
1511
  if (!(view.state.selection instanceof prosemirrorState.TextSelection) || from < pos || to > pos + this.node.content.size) return null;
1499
- var sel = view.domSelection();
1512
+ var sel = view.domSelectionRange();
1500
1513
  var textNode = nearbyTextNode(sel.focusNode, sel.focusOffset);
1501
1514
  if (!textNode || !this.dom.contains(textNode.parentNode)) return null;
1502
1515
 
@@ -2406,7 +2419,7 @@ function replaceNodes(nodes, from, to, view, replacement) {
2406
2419
 
2407
2420
  function selectionFromDOM(view) {
2408
2421
  var origin = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
2409
- var domSel = view.domSelection(),
2422
+ var domSel = view.domSelectionRange(),
2410
2423
  doc = view.state.doc;
2411
2424
  if (!domSel.focusNode) return null;
2412
2425
  var nearestDesc = view.docView.nearestDesc(domSel.focusNode),
@@ -2455,7 +2468,7 @@ function selectionToDOM(view) {
2455
2468
  if (!editorOwnsSelection(view)) return;
2456
2469
 
2457
2470
  if (!force && view.input.mouseDown && view.input.mouseDown.allowDefault && chrome) {
2458
- var domSel = view.domSelection(),
2471
+ var domSel = view.domSelectionRange(),
2459
2472
  curSel = view.domObserver.currentSelection;
2460
2473
 
2461
2474
  if (domSel.anchorNode && curSel.anchorNode && isEquivalentPosition(domSel.anchorNode, domSel.anchorOffset, curSel.anchorNode, curSel.anchorOffset)) {
@@ -2538,7 +2551,7 @@ function resetEditable(element) {
2538
2551
  function removeClassOnSelectionChange(view) {
2539
2552
  var doc = view.dom.ownerDocument;
2540
2553
  doc.removeEventListener("selectionchange", view.input.hideSelectionGuard);
2541
- var domSel = view.domSelection();
2554
+ var domSel = view.domSelectionRange();
2542
2555
  var node = domSel.anchorNode,
2543
2556
  offset = domSel.anchorOffset;
2544
2557
  doc.addEventListener("selectionchange", view.input.hideSelectionGuard = function () {
@@ -2600,7 +2613,7 @@ function hasFocusAndSelection(view) {
2600
2613
  }
2601
2614
 
2602
2615
  function hasSelection(view) {
2603
- var sel = view.domSelection();
2616
+ var sel = view.domSelectionRange();
2604
2617
  if (!sel.anchorNode) return false;
2605
2618
 
2606
2619
  try {
@@ -2612,7 +2625,7 @@ function hasSelection(view) {
2612
2625
 
2613
2626
  function anchorInRightPlace(view) {
2614
2627
  var anchorDOM = view.docView.domFromPos(view.state.selection.anchor, 0);
2615
- var domSel = view.domSelection();
2628
+ var domSel = view.domSelectionRange();
2616
2629
  return isEquivalentPosition(anchorDOM.node, anchorDOM.offset, domSel.anchorNode, domSel.anchorOffset);
2617
2630
  }
2618
2631
 
@@ -2676,7 +2689,7 @@ function isIgnorable(dom) {
2676
2689
  }
2677
2690
 
2678
2691
  function skipIgnoredNodesLeft(view) {
2679
- var sel = view.domSelection();
2692
+ var sel = view.domSelectionRange();
2680
2693
  var node = sel.focusNode,
2681
2694
  offset = sel.focusOffset;
2682
2695
  if (!node) return;
@@ -2722,11 +2735,11 @@ function skipIgnoredNodesLeft(view) {
2722
2735
  }
2723
2736
  }
2724
2737
 
2725
- if (force) setSelFocus(view, sel, node, offset);else if (moveNode) setSelFocus(view, sel, moveNode, moveOffset);
2738
+ if (force) setSelFocus(view, node, offset);else if (moveNode) setSelFocus(view, moveNode, moveOffset);
2726
2739
  }
2727
2740
 
2728
2741
  function skipIgnoredNodesRight(view) {
2729
- var sel = view.domSelection();
2742
+ var sel = view.domSelectionRange();
2730
2743
  var node = sel.focusNode,
2731
2744
  offset = sel.focusOffset;
2732
2745
  if (!node) return;
@@ -2765,7 +2778,7 @@ function skipIgnoredNodesRight(view) {
2765
2778
  }
2766
2779
  }
2767
2780
 
2768
- if (moveNode) setSelFocus(view, sel, moveNode, moveOffset);
2781
+ if (moveNode) setSelFocus(view, moveNode, moveOffset);
2769
2782
  }
2770
2783
 
2771
2784
  function isBlockNode(dom) {
@@ -2773,7 +2786,9 @@ function isBlockNode(dom) {
2773
2786
  return desc && desc.node && desc.node.isBlock;
2774
2787
  }
2775
2788
 
2776
- function setSelFocus(view, sel, node, offset) {
2789
+ function setSelFocus(view, node, offset) {
2790
+ var sel = view.domSelection();
2791
+
2777
2792
  if (selectionCollapsed(sel)) {
2778
2793
  var range = document.createRange();
2779
2794
  range.setEnd(node, offset);
@@ -2842,9 +2857,9 @@ function switchEditable(view, node, state) {
2842
2857
  function safariDownArrowBug(view) {
2843
2858
  if (!safari || view.state.selection.$head.parentOffset > 0) return false;
2844
2859
 
2845
- var _view$domSelection = view.domSelection(),
2846
- focusNode = _view$domSelection.focusNode,
2847
- focusOffset = _view$domSelection.focusOffset;
2860
+ var _view$domSelectionRan3 = view.domSelectionRange(),
2861
+ focusNode = _view$domSelectionRan3.focusNode,
2862
+ focusOffset = _view$domSelectionRan3.focusOffset;
2848
2863
 
2849
2864
  if (focusNode && focusNode.nodeType == 1 && focusOffset == 0 && focusNode.firstChild && focusNode.firstChild.contentEditable == "false") {
2850
2865
  var child = focusNode.firstChild;
@@ -3639,14 +3654,14 @@ editHandlers.compositionstart = editHandlers.compositionupdate = function (view)
3639
3654
  endComposition(view);
3640
3655
 
3641
3656
  if (gecko && state.selection.empty && $pos.parentOffset && !$pos.textOffset && $pos.nodeBefore.marks.length) {
3642
- var sel = view.domSelection();
3657
+ var sel = view.domSelectionRange();
3643
3658
 
3644
3659
  for (var node = sel.focusNode, offset = sel.focusOffset; node && node.nodeType == 1 && offset != 0;) {
3645
3660
  var before = offset < 0 ? node.lastChild : node.childNodes[offset - 1];
3646
3661
  if (!before) break;
3647
3662
 
3648
3663
  if (before.nodeType == 3) {
3649
- sel.collapse(before, before.nodeValue.length);
3664
+ view.domSelection().collapse(before, before.nodeValue.length);
3650
3665
  break;
3651
3666
  } else {
3652
3667
  node = before;
@@ -3898,7 +3913,7 @@ handlers.focus = function (view) {
3898
3913
  view.domObserver.start();
3899
3914
  view.focused = true;
3900
3915
  setTimeout(function () {
3901
- if (view.docView && view.hasFocus() && !view.domObserver.currentSelection.eq(view.domSelection())) selectionToDOM(view);
3916
+ if (view.docView && view.hasFocus() && !view.domObserver.currentSelection.eq(view.domSelectionRange())) selectionToDOM(view);
3902
3917
  }, 20);
3903
3918
  }
3904
3919
  };
@@ -4826,7 +4841,7 @@ var DOMObserver = function () {
4826
4841
  if (this.suppressingSelectionUpdates) return selectionToDOM(this.view);
4827
4842
 
4828
4843
  if (ie && ie_version <= 11 && !this.view.state.selection.empty) {
4829
- var sel = this.view.domSelection();
4844
+ var sel = this.view.domSelectionRange();
4830
4845
  if (sel.focusNode && isEquivalentPosition(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset)) return this.flushSoon();
4831
4846
  }
4832
4847
 
@@ -4835,14 +4850,27 @@ var DOMObserver = function () {
4835
4850
  }, {
4836
4851
  key: "setCurSelection",
4837
4852
  value: function setCurSelection() {
4838
- this.currentSelection.set(this.view.domSelection());
4853
+ this.currentSelection.set(this.view.domSelectionRange());
4839
4854
  }
4840
4855
  }, {
4841
4856
  key: "ignoreSelectionChange",
4842
4857
  value: function ignoreSelectionChange(sel) {
4843
- if (sel.rangeCount == 0) return true;
4844
- var container = sel.getRangeAt(0).commonAncestorContainer;
4845
- var desc = this.view.docView.nearestDesc(container);
4858
+ if (!sel.focusNode) return true;
4859
+ var ancestors = new Set(),
4860
+ container;
4861
+
4862
+ for (var scan = sel.focusNode; scan; scan = parentNode(scan)) {
4863
+ ancestors.add(scan);
4864
+ }
4865
+
4866
+ for (var _scan = sel.anchorNode; _scan; _scan = parentNode(_scan)) {
4867
+ if (ancestors.has(_scan)) {
4868
+ container = _scan;
4869
+ break;
4870
+ }
4871
+ }
4872
+
4873
+ var desc = container && this.view.docView.nearestDesc(container);
4846
4874
 
4847
4875
  if (desc && desc.ignoreMutation({
4848
4876
  type: "selection",
@@ -4864,7 +4892,7 @@ var DOMObserver = function () {
4864
4892
  this.queue.length = 0;
4865
4893
  }
4866
4894
 
4867
- var sel = view.domSelection();
4895
+ var sel = view.domSelectionRange();
4868
4896
  var newSel = !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(view) && !this.ignoreSelectionChange(sel);
4869
4897
  var from = -1,
4870
4898
  to = -1,
@@ -4984,6 +5012,40 @@ function checkCSS(view) {
4984
5012
  }
4985
5013
  }
4986
5014
 
5015
+ function safariShadowSelectionRange(view) {
5016
+ var found;
5017
+
5018
+ function read(event) {
5019
+ event.preventDefault();
5020
+ event.stopImmediatePropagation();
5021
+ found = event.getTargetRanges()[0];
5022
+ }
5023
+
5024
+ view.dom.addEventListener("beforeinput", read, true);
5025
+ document.execCommand("indent");
5026
+ view.dom.removeEventListener("beforeinput", read, true);
5027
+ var anchorNode = found.startContainer,
5028
+ anchorOffset = found.startOffset;
5029
+ var focusNode = found.endContainer,
5030
+ focusOffset = found.endOffset;
5031
+ var currentAnchor = view.domAtPos(view.state.selection.anchor);
5032
+
5033
+ if (isEquivalentPosition(currentAnchor.node, currentAnchor.offset, focusNode, focusOffset)) {
5034
+ var _ref3 = [focusNode, focusOffset, anchorNode, anchorOffset];
5035
+ anchorNode = _ref3[0];
5036
+ anchorOffset = _ref3[1];
5037
+ focusNode = _ref3[2];
5038
+ focusOffset = _ref3[3];
5039
+ }
5040
+
5041
+ return {
5042
+ anchorNode: anchorNode,
5043
+ anchorOffset: anchorOffset,
5044
+ focusNode: focusNode,
5045
+ focusOffset: focusOffset
5046
+ };
5047
+ }
5048
+
4987
5049
  function parseBetween(view, from_, to_) {
4988
5050
  var _view$docView$parseRa = view.docView.parseRange(from_, to_),
4989
5051
  parent = _view$docView$parseRa.node,
@@ -4992,7 +5054,7 @@ function parseBetween(view, from_, to_) {
4992
5054
  from = _view$docView$parseRa.from,
4993
5055
  to = _view$docView$parseRa.to;
4994
5056
 
4995
- var domSel = view.domSelection();
5057
+ var domSel = view.domSelectionRange();
4996
5058
  var find;
4997
5059
  var anchor = domSel.anchorNode;
4998
5060
 
@@ -5502,7 +5564,7 @@ var EditorView = function () {
5502
5564
  var forceSelUpdate = updateDoc && (ie || chrome) && !this.composing && !prev.selection.empty && !state.selection.empty && selectionContextChanged(prev.selection, state.selection);
5503
5565
 
5504
5566
  if (updateDoc) {
5505
- var chromeKludge = chrome ? this.trackWrites = this.domSelection().focusNode : null;
5567
+ var chromeKludge = chrome ? this.trackWrites = this.domSelectionRange().focusNode : null;
5506
5568
 
5507
5569
  if (redraw || !this.docView.update(state.doc, outerDeco, innerDeco, this)) {
5508
5570
  this.docView.updateOuterDeco([]);
@@ -5513,7 +5575,7 @@ var EditorView = function () {
5513
5575
  if (chromeKludge && !this.trackWrites) forceSelUpdate = true;
5514
5576
  }
5515
5577
 
5516
- if (forceSelUpdate || !(this.input.mouseDown && this.domObserver.currentSelection.eq(this.domSelection()) && anchorInRightPlace(this))) {
5578
+ if (forceSelUpdate || !(this.input.mouseDown && this.domObserver.currentSelection.eq(this.domSelectionRange()) && anchorInRightPlace(this))) {
5517
5579
  selectionToDOM(this, forceSelUpdate);
5518
5580
  } else {
5519
5581
  syncNodeSelection(this, state.selection);
@@ -5538,7 +5600,7 @@ var EditorView = function () {
5538
5600
  value: function scrollToSelection() {
5539
5601
  var _this16 = this;
5540
5602
 
5541
- var startDOM = this.domSelection().focusNode;
5603
+ var startDOM = this.domSelectionRange().focusNode;
5542
5604
  if (this.someProp("handleScrollToSelection", function (f) {
5543
5605
  return f(_this16);
5544
5606
  })) ;else if (this.state.selection instanceof prosemirrorState.NodeSelection) {
@@ -5721,6 +5783,11 @@ var EditorView = function () {
5721
5783
  var dispatchTransaction = this._props.dispatchTransaction;
5722
5784
  if (dispatchTransaction) dispatchTransaction.call(this, tr);else this.updateState(this.state.apply(tr));
5723
5785
  }
5786
+ }, {
5787
+ key: "domSelectionRange",
5788
+ value: function domSelectionRange() {
5789
+ return safari && this.root.nodeType === 11 && deepActiveElement(this.dom.ownerDocument) == this.dom ? safariShadowSelectionRange(this) : this.domSelection();
5790
+ }
5724
5791
  }, {
5725
5792
  key: "domSelection",
5726
5793
  value: function domSelection() {
package/dist/index.js CHANGED
@@ -2,27 +2,6 @@ import { TextSelection, NodeSelection, Selection, AllSelection } from 'prosemirr
2
2
  import { DOMSerializer, Fragment, Mark, Slice, DOMParser } from 'prosemirror-model';
3
3
  import { dropPoint } from 'prosemirror-transform';
4
4
 
5
- const nav = typeof navigator != "undefined" ? navigator : null;
6
- const doc = typeof document != "undefined" ? document : null;
7
- const agent = (nav && nav.userAgent) || "";
8
- const ie_edge = /Edge\/(\d+)/.exec(agent);
9
- const ie_upto10 = /MSIE \d/.exec(agent);
10
- const ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(agent);
11
- const ie = !!(ie_upto10 || ie_11up || ie_edge);
12
- const ie_version = ie_upto10 ? document.documentMode : ie_11up ? +ie_11up[1] : ie_edge ? +ie_edge[1] : 0;
13
- const gecko = !ie && /gecko\/(\d+)/i.test(agent);
14
- gecko && +(/Firefox\/(\d+)/.exec(agent) || [0, 0])[1];
15
- const _chrome = !ie && /Chrome\/(\d+)/.exec(agent);
16
- const chrome = !!_chrome;
17
- const chrome_version = _chrome ? +_chrome[1] : 0;
18
- const safari = !ie && !!nav && /Apple Computer/.test(nav.vendor);
19
- // Is true for both iOS and iPadOS for convenience
20
- const ios = safari && (/Mobile\/\w+/.test(agent) || !!nav && nav.maxTouchPoints > 2);
21
- const mac = ios || (nav ? /Mac/.test(nav.platform) : false);
22
- const android = /Android \d/.test(agent);
23
- const webkit = !!doc && "webkitFontSmoothing" in doc.documentElement.style;
24
- const webkit_version = webkit ? +(/\bAppleWebKit\/(\d+)/.exec(navigator.userAgent) || [0, 0])[1] : 0;
25
-
26
5
  const domIndex = function (node) {
27
6
  for (var index = 0;; index++) {
28
7
  node = node.previousSibling;
@@ -100,10 +79,7 @@ function hasBlockDesc(dom) {
100
79
  // Work around Chrome issue https://bugs.chromium.org/p/chromium/issues/detail?id=447523
101
80
  // (isCollapsed inappropriately returns true in shadow dom)
102
81
  const selectionCollapsed = function (domSel) {
103
- let collapsed = domSel.isCollapsed;
104
- if (collapsed && chrome && domSel.rangeCount && !domSel.getRangeAt(0).collapsed)
105
- collapsed = false;
106
- return collapsed;
82
+ return domSel.focusNode && isEquivalentPosition(domSel.focusNode, domSel.focusOffset, domSel.anchorNode, domSel.anchorOffset);
107
83
  };
108
84
  function keyEvent(keyCode, key) {
109
85
  let event = document.createEvent("Event");
@@ -112,6 +88,33 @@ function keyEvent(keyCode, key) {
112
88
  event.key = event.code = key;
113
89
  return event;
114
90
  }
91
+ function deepActiveElement(doc) {
92
+ let elt = doc.activeElement;
93
+ while (elt && elt.shadowRoot)
94
+ elt = elt.shadowRoot.activeElement;
95
+ return elt;
96
+ }
97
+
98
+ const nav = typeof navigator != "undefined" ? navigator : null;
99
+ const doc = typeof document != "undefined" ? document : null;
100
+ const agent = (nav && nav.userAgent) || "";
101
+ const ie_edge = /Edge\/(\d+)/.exec(agent);
102
+ const ie_upto10 = /MSIE \d/.exec(agent);
103
+ const ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(agent);
104
+ const ie = !!(ie_upto10 || ie_11up || ie_edge);
105
+ const ie_version = ie_upto10 ? document.documentMode : ie_11up ? +ie_11up[1] : ie_edge ? +ie_edge[1] : 0;
106
+ const gecko = !ie && /gecko\/(\d+)/i.test(agent);
107
+ gecko && +(/Firefox\/(\d+)/.exec(agent) || [0, 0])[1];
108
+ const _chrome = !ie && /Chrome\/(\d+)/.exec(agent);
109
+ const chrome = !!_chrome;
110
+ const chrome_version = _chrome ? +_chrome[1] : 0;
111
+ const safari = !ie && !!nav && /Apple Computer/.test(nav.vendor);
112
+ // Is true for both iOS and iPadOS for convenience
113
+ const ios = safari && (/Mobile\/\w+/.test(agent) || !!nav && nav.maxTouchPoints > 2);
114
+ const mac = ios || (nav ? /Mac/.test(nav.platform) : false);
115
+ const android = /Android \d/.test(agent);
116
+ const webkit = !!doc && "webkitFontSmoothing" in doc.documentElement.style;
117
+ const webkit_version = webkit ? +(/\bAppleWebKit\/(\d+)/.exec(navigator.userAgent) || [0, 0])[1] : 0;
115
118
 
116
119
  function windowRect(doc) {
117
120
  return { left: 0, right: doc.documentElement.clientWidth,
@@ -580,16 +583,21 @@ function endOfTextblockHorizontal(view, state, dir) {
580
583
  // one character, and see if that moves the cursor out of the
581
584
  // textblock (or doesn't move it at all, when at the start/end of
582
585
  // the document).
583
- let oldRange = sel.getRangeAt(0), oldNode = sel.focusNode, oldOff = sel.focusOffset;
586
+ let { focusNode: oldNode, focusOffset: oldOff, anchorNode, anchorOffset } = view.domSelectionRange();
584
587
  let oldBidiLevel = sel.caretBidiLevel // Only for Firefox
585
588
  ;
586
589
  sel.modify("move", dir, "character");
587
590
  let parentDOM = $head.depth ? view.docView.domAfterPos($head.before()) : view.dom;
588
- let result = !parentDOM.contains(sel.focusNode.nodeType == 1 ? sel.focusNode : sel.focusNode.parentNode) ||
589
- (oldNode == sel.focusNode && oldOff == sel.focusOffset);
591
+ let { focusNode: newNode, focusOffset: newOff } = view.domSelectionRange();
592
+ let result = newNode && !parentDOM.contains(newNode.nodeType == 1 ? newNode : newNode.parentNode) ||
593
+ (oldNode == newNode && oldOff == newOff);
590
594
  // Restore the previous selection
591
- sel.removeAllRanges();
592
- sel.addRange(oldRange);
595
+ try {
596
+ sel.collapse(anchorNode, anchorOffset);
597
+ if (oldNode && (oldNode != anchorNode || oldOff != anchorOffset) && sel.extend)
598
+ sel.extend(oldNode, oldOff);
599
+ }
600
+ catch (_) { }
593
601
  if (oldBidiLevel != null)
594
602
  sel.caretBidiLevel = oldBidiLevel;
595
603
  return result;
@@ -957,24 +965,17 @@ class ViewDesc {
957
965
  if ((domSel.extend || anchor == head) && !brKludge) {
958
966
  domSel.collapse(anchorDOM.node, anchorDOM.offset);
959
967
  try {
960
- if (anchor != head) {
961
- // This can crash on Safari if the editor is hidden, and
962
- // there was no selection. (#1308)
963
- try {
964
- domSel.extend(headDOM.node, headDOM.offset);
965
- }
966
- catch (_) { }
967
- }
968
+ if (anchor != head)
969
+ domSel.extend(headDOM.node, headDOM.offset);
968
970
  domSelExtended = true;
969
971
  }
970
- catch (err) {
972
+ catch (_) {
971
973
  // In some cases with Chrome the selection is empty after calling
972
974
  // collapse, even when it should be valid. This appears to be a bug, but
973
975
  // it is difficult to isolate. If this happens fallback to the old path
974
976
  // without using extend.
975
- if (!(err instanceof DOMException))
976
- throw err;
977
- // declare global: DOMException
977
+ // Similarly, this could crash on Safari if the editor is hidden, and
978
+ // there was no selection.
978
979
  }
979
980
  }
980
981
  if (!domSelExtended) {
@@ -1295,7 +1296,7 @@ class NodeViewDesc extends ViewDesc {
1295
1296
  let { from, to } = view.state.selection;
1296
1297
  if (!(view.state.selection instanceof TextSelection) || from < pos || to > pos + this.node.content.size)
1297
1298
  return null;
1298
- let sel = view.domSelection();
1299
+ let sel = view.domSelectionRange();
1299
1300
  let textNode = nearbyTextNode(sel.focusNode, sel.focusOffset);
1300
1301
  if (!textNode || !this.dom.contains(textNode.parentNode))
1301
1302
  return null;
@@ -2031,7 +2032,7 @@ function replaceNodes(nodes, from, to, view, replacement) {
2031
2032
  }
2032
2033
 
2033
2034
  function selectionFromDOM(view, origin = null) {
2034
- let domSel = view.domSelection(), doc = view.state.doc;
2035
+ let domSel = view.domSelectionRange(), doc = view.state.doc;
2035
2036
  if (!domSel.focusNode)
2036
2037
  return null;
2037
2038
  let nearestDesc = view.docView.nearestDesc(domSel.focusNode), inWidget = nearestDesc && nearestDesc.size == 0;
@@ -2075,7 +2076,7 @@ function selectionToDOM(view, force = false) {
2075
2076
  // in Safari. And the drag selection delay is to workarond issues
2076
2077
  // which only present in Chrome.
2077
2078
  if (!force && view.input.mouseDown && view.input.mouseDown.allowDefault && chrome) {
2078
- let domSel = view.domSelection(), curSel = view.domObserver.currentSelection;
2079
+ let domSel = view.domSelectionRange(), curSel = view.domObserver.currentSelection;
2079
2080
  if (domSel.anchorNode && curSel.anchorNode &&
2080
2081
  isEquivalentPosition(domSel.anchorNode, domSel.anchorOffset, curSel.anchorNode, curSel.anchorOffset)) {
2081
2082
  view.input.mouseDown.delayedSelectionSync = true;
@@ -2150,7 +2151,7 @@ function resetEditable(element) {
2150
2151
  function removeClassOnSelectionChange(view) {
2151
2152
  let doc = view.dom.ownerDocument;
2152
2153
  doc.removeEventListener("selectionchange", view.input.hideSelectionGuard);
2153
- let domSel = view.domSelection();
2154
+ let domSel = view.domSelectionRange();
2154
2155
  let node = domSel.anchorNode, offset = domSel.anchorOffset;
2155
2156
  doc.addEventListener("selectionchange", view.input.hideSelectionGuard = () => {
2156
2157
  if (domSel.anchorNode != node || domSel.anchorOffset != offset) {
@@ -2214,7 +2215,7 @@ function hasFocusAndSelection(view) {
2214
2215
  return hasSelection(view);
2215
2216
  }
2216
2217
  function hasSelection(view) {
2217
- let sel = view.domSelection();
2218
+ let sel = view.domSelectionRange();
2218
2219
  if (!sel.anchorNode)
2219
2220
  return false;
2220
2221
  try {
@@ -2230,7 +2231,7 @@ function hasSelection(view) {
2230
2231
  }
2231
2232
  function anchorInRightPlace(view) {
2232
2233
  let anchorDOM = view.docView.domFromPos(view.state.selection.anchor, 0);
2233
- let domSel = view.domSelection();
2234
+ let domSel = view.domSelectionRange();
2234
2235
  return isEquivalentPosition(anchorDOM.node, anchorDOM.offset, domSel.anchorNode, domSel.anchorOffset);
2235
2236
  }
2236
2237
 
@@ -2297,7 +2298,7 @@ function isIgnorable(dom) {
2297
2298
  // Make sure the cursor isn't directly after one or more ignored
2298
2299
  // nodes, which will confuse the browser's cursor motion logic.
2299
2300
  function skipIgnoredNodesLeft(view) {
2300
- let sel = view.domSelection();
2301
+ let sel = view.domSelectionRange();
2301
2302
  let node = sel.focusNode, offset = sel.focusOffset;
2302
2303
  if (!node)
2303
2304
  return;
@@ -2349,14 +2350,14 @@ function skipIgnoredNodesLeft(view) {
2349
2350
  }
2350
2351
  }
2351
2352
  if (force)
2352
- setSelFocus(view, sel, node, offset);
2353
+ setSelFocus(view, node, offset);
2353
2354
  else if (moveNode)
2354
- setSelFocus(view, sel, moveNode, moveOffset);
2355
+ setSelFocus(view, moveNode, moveOffset);
2355
2356
  }
2356
2357
  // Make sure the cursor isn't directly before one or more ignored
2357
2358
  // nodes.
2358
2359
  function skipIgnoredNodesRight(view) {
2359
- let sel = view.domSelection();
2360
+ let sel = view.domSelectionRange();
2360
2361
  let node = sel.focusNode, offset = sel.focusOffset;
2361
2362
  if (!node)
2362
2363
  return;
@@ -2398,13 +2399,14 @@ function skipIgnoredNodesRight(view) {
2398
2399
  }
2399
2400
  }
2400
2401
  if (moveNode)
2401
- setSelFocus(view, sel, moveNode, moveOffset);
2402
+ setSelFocus(view, moveNode, moveOffset);
2402
2403
  }
2403
2404
  function isBlockNode(dom) {
2404
2405
  let desc = dom.pmViewDesc;
2405
2406
  return desc && desc.node && desc.node.isBlock;
2406
2407
  }
2407
- function setSelFocus(view, sel, node, offset) {
2408
+ function setSelFocus(view, node, offset) {
2409
+ let sel = view.domSelection();
2408
2410
  if (selectionCollapsed(sel)) {
2409
2411
  let range = document.createRange();
2410
2412
  range.setEnd(node, offset);
@@ -2480,7 +2482,7 @@ function switchEditable(view, node, state) {
2480
2482
  function safariDownArrowBug(view) {
2481
2483
  if (!safari || view.state.selection.$head.parentOffset > 0)
2482
2484
  return false;
2483
- let { focusNode, focusOffset } = view.domSelection();
2485
+ let { focusNode, focusOffset } = view.domSelectionRange();
2484
2486
  if (focusNode && focusNode.nodeType == 1 && focusOffset == 0 &&
2485
2487
  focusNode.firstChild && focusNode.firstChild.contentEditable == "false") {
2486
2488
  let child = focusNode.firstChild;
@@ -3206,13 +3208,13 @@ editHandlers.compositionstart = editHandlers.compositionupdate = view => {
3206
3208
  // the inserted text won't inherit the marks. So this moves it
3207
3209
  // inside if necessary.
3208
3210
  if (gecko && state.selection.empty && $pos.parentOffset && !$pos.textOffset && $pos.nodeBefore.marks.length) {
3209
- let sel = view.domSelection();
3211
+ let sel = view.domSelectionRange();
3210
3212
  for (let node = sel.focusNode, offset = sel.focusOffset; node && node.nodeType == 1 && offset != 0;) {
3211
3213
  let before = offset < 0 ? node.lastChild : node.childNodes[offset - 1];
3212
3214
  if (!before)
3213
3215
  break;
3214
3216
  if (before.nodeType == 3) {
3215
- sel.collapse(before, before.nodeValue.length);
3217
+ view.domSelection().collapse(before, before.nodeValue.length);
3216
3218
  break;
3217
3219
  }
3218
3220
  else {
@@ -3468,7 +3470,7 @@ handlers.focus = view => {
3468
3470
  view.domObserver.start();
3469
3471
  view.focused = true;
3470
3472
  setTimeout(() => {
3471
- if (view.docView && view.hasFocus() && !view.domObserver.currentSelection.eq(view.domSelection()))
3473
+ if (view.docView && view.hasFocus() && !view.domObserver.currentSelection.eq(view.domSelectionRange()))
3472
3474
  selectionToDOM(view);
3473
3475
  }, 20);
3474
3476
  }
@@ -4300,7 +4302,7 @@ class DOMObserver {
4300
4302
  // us a selection change event before the DOM changes are
4301
4303
  // reported.
4302
4304
  if (ie && ie_version <= 11 && !this.view.state.selection.empty) {
4303
- let sel = this.view.domSelection();
4305
+ let sel = this.view.domSelectionRange();
4304
4306
  // Selection.isCollapsed isn't reliable on IE
4305
4307
  if (sel.focusNode && isEquivalentPosition(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset))
4306
4308
  return this.flushSoon();
@@ -4308,13 +4310,20 @@ class DOMObserver {
4308
4310
  this.flush();
4309
4311
  }
4310
4312
  setCurSelection() {
4311
- this.currentSelection.set(this.view.domSelection());
4313
+ this.currentSelection.set(this.view.domSelectionRange());
4312
4314
  }
4313
4315
  ignoreSelectionChange(sel) {
4314
- if (sel.rangeCount == 0)
4316
+ if (!sel.focusNode)
4315
4317
  return true;
4316
- let container = sel.getRangeAt(0).commonAncestorContainer;
4317
- let desc = this.view.docView.nearestDesc(container);
4318
+ let ancestors = new Set, container;
4319
+ for (let scan = sel.focusNode; scan; scan = parentNode(scan))
4320
+ ancestors.add(scan);
4321
+ for (let scan = sel.anchorNode; scan; scan = parentNode(scan))
4322
+ if (ancestors.has(scan)) {
4323
+ container = scan;
4324
+ break;
4325
+ }
4326
+ let desc = container && this.view.docView.nearestDesc(container);
4318
4327
  if (desc && desc.ignoreMutation({
4319
4328
  type: "selection",
4320
4329
  target: container.nodeType == 3 ? container.parentNode : container
@@ -4332,7 +4341,7 @@ class DOMObserver {
4332
4341
  mutations = this.queue.concat(mutations);
4333
4342
  this.queue.length = 0;
4334
4343
  }
4335
- let sel = view.domSelection();
4344
+ let sel = view.domSelectionRange();
4336
4345
  let newSel = !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(view) && !this.ignoreSelectionChange(sel);
4337
4346
  let from = -1, to = -1, typeOver = false, added = [];
4338
4347
  if (view.editable) {
@@ -4449,6 +4458,33 @@ function checkCSS(view) {
4449
4458
  cssCheckWarned = true;
4450
4459
  }
4451
4460
  }
4461
+ // Used to work around a Safari Selection/shadow DOM bug
4462
+ // Based on https://github.com/codemirror/dev/issues/414 fix
4463
+ function safariShadowSelectionRange(view) {
4464
+ let found;
4465
+ function read(event) {
4466
+ event.preventDefault();
4467
+ event.stopImmediatePropagation();
4468
+ found = event.getTargetRanges()[0];
4469
+ }
4470
+ // Because Safari (at least in 2018-2022) doesn't provide regular
4471
+ // access to the selection inside a shadowRoot, we have to perform a
4472
+ // ridiculous hack to get at it—using `execCommand` to trigger a
4473
+ // `beforeInput` event so that we can read the target range from the
4474
+ // event.
4475
+ view.dom.addEventListener("beforeinput", read, true);
4476
+ document.execCommand("indent");
4477
+ view.dom.removeEventListener("beforeinput", read, true);
4478
+ let anchorNode = found.startContainer, anchorOffset = found.startOffset;
4479
+ let focusNode = found.endContainer, focusOffset = found.endOffset;
4480
+ let currentAnchor = view.domAtPos(view.state.selection.anchor);
4481
+ // Since such a range doesn't distinguish between anchor and head,
4482
+ // use a heuristic that flips it around if its end matches the
4483
+ // current anchor.
4484
+ if (isEquivalentPosition(currentAnchor.node, currentAnchor.offset, focusNode, focusOffset))
4485
+ [anchorNode, anchorOffset, focusNode, focusOffset] = [focusNode, focusOffset, anchorNode, anchorOffset];
4486
+ return { anchorNode, anchorOffset, focusNode, focusOffset };
4487
+ }
4452
4488
 
4453
4489
  // Note that all referencing and parsing is done with the
4454
4490
  // start-of-operation selection and document, since that's the one
@@ -4457,7 +4493,7 @@ function checkCSS(view) {
4457
4493
  // readDOMChange.
4458
4494
  function parseBetween(view, from_, to_) {
4459
4495
  let { node: parent, fromOffset, toOffset, from, to } = view.docView.parseRange(from_, to_);
4460
- let domSel = view.domSelection();
4496
+ let domSel = view.domSelectionRange();
4461
4497
  let find;
4462
4498
  let anchor = domSel.anchorNode;
4463
4499
  if (anchor && view.dom.contains(anchor.nodeType == 1 ? anchor : anchor.parentNode)) {
@@ -4985,7 +5021,7 @@ class EditorView {
4985
5021
  // Chrome sometimes starts misreporting the selection, so this
4986
5022
  // tracks that and forces a selection reset when our update
4987
5023
  // did write to the node.
4988
- let chromeKludge = chrome ? (this.trackWrites = this.domSelection().focusNode) : null;
5024
+ let chromeKludge = chrome ? (this.trackWrites = this.domSelectionRange().focusNode) : null;
4989
5025
  if (redraw || !this.docView.update(state.doc, outerDeco, innerDeco, this)) {
4990
5026
  this.docView.updateOuterDeco([]);
4991
5027
  this.docView.destroy();
@@ -4999,7 +5035,8 @@ class EditorView {
4999
5035
  // can cause a spurious DOM selection update, disrupting mouse
5000
5036
  // drag selection.
5001
5037
  if (forceSelUpdate ||
5002
- !(this.input.mouseDown && this.domObserver.currentSelection.eq(this.domSelection()) && anchorInRightPlace(this))) {
5038
+ !(this.input.mouseDown && this.domObserver.currentSelection.eq(this.domSelectionRange()) &&
5039
+ anchorInRightPlace(this))) {
5003
5040
  selectionToDOM(this, forceSelUpdate);
5004
5041
  }
5005
5042
  else {
@@ -5023,7 +5060,7 @@ class EditorView {
5023
5060
  @internal
5024
5061
  */
5025
5062
  scrollToSelection() {
5026
- let startDOM = this.domSelection().focusNode;
5063
+ let startDOM = this.domSelectionRange().focusNode;
5027
5064
  if (this.someProp("handleScrollToSelection", f => f(this))) ;
5028
5065
  else if (this.state.selection instanceof NodeSelection) {
5029
5066
  let target = this.docView.domAfterPos(this.state.selection.from);
@@ -5261,6 +5298,13 @@ class EditorView {
5261
5298
  /**
5262
5299
  @internal
5263
5300
  */
5301
+ domSelectionRange() {
5302
+ return safari && this.root.nodeType === 11 && deepActiveElement(this.dom.ownerDocument) == this.dom
5303
+ ? safariShadowSelectionRange(this) : this.domSelection();
5304
+ }
5305
+ /**
5306
+ @internal
5307
+ */
5264
5308
  domSelection() {
5265
5309
  return this.root.getSelection();
5266
5310
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prosemirror-view",
3
- "version": "1.28.2",
3
+ "version": "1.28.3",
4
4
  "description": "ProseMirror's view component",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -1,7 +1,7 @@
1
1
  import {Selection, NodeSelection, TextSelection, AllSelection, EditorState} from "prosemirror-state"
2
2
  import {EditorView} from "./index"
3
3
  import * as browser from "./browser"
4
- import {domIndex, selectionCollapsed, DOMSelection} from "./dom"
4
+ import {domIndex, selectionCollapsed} from "./dom"
5
5
  import {selectionToDOM} from "./selection"
6
6
 
7
7
  function moveSelectionBlock(state: EditorState, dir: number) {
@@ -62,7 +62,7 @@ function isIgnorable(dom: Node) {
62
62
  // Make sure the cursor isn't directly after one or more ignored
63
63
  // nodes, which will confuse the browser's cursor motion logic.
64
64
  function skipIgnoredNodesLeft(view: EditorView) {
65
- let sel = view.domSelection()
65
+ let sel = view.domSelectionRange()
66
66
  let node = sel.focusNode!, offset = sel.focusOffset
67
67
  if (!node) return
68
68
  let moveNode, moveOffset: number | undefined, force = false
@@ -103,14 +103,14 @@ function skipIgnoredNodesLeft(view: EditorView) {
103
103
  }
104
104
  }
105
105
  }
106
- if (force) setSelFocus(view, sel, node, offset)
107
- else if (moveNode) setSelFocus(view, sel, moveNode, moveOffset!)
106
+ if (force) setSelFocus(view, node, offset)
107
+ else if (moveNode) setSelFocus(view, moveNode, moveOffset!)
108
108
  }
109
109
 
110
110
  // Make sure the cursor isn't directly before one or more ignored
111
111
  // nodes.
112
112
  function skipIgnoredNodesRight(view: EditorView) {
113
- let sel = view.domSelection()
113
+ let sel = view.domSelectionRange()
114
114
  let node = sel.focusNode!, offset = sel.focusOffset
115
115
  if (!node) return
116
116
  let len = nodeLen(node)
@@ -144,7 +144,7 @@ function skipIgnoredNodesRight(view: EditorView) {
144
144
  }
145
145
  }
146
146
  }
147
- if (moveNode) setSelFocus(view, sel, moveNode, moveOffset!)
147
+ if (moveNode) setSelFocus(view, moveNode, moveOffset!)
148
148
  }
149
149
 
150
150
  function isBlockNode(dom: Node) {
@@ -152,7 +152,8 @@ function isBlockNode(dom: Node) {
152
152
  return desc && desc.node && desc.node.isBlock
153
153
  }
154
154
 
155
- function setSelFocus(view: EditorView, sel: DOMSelection, node: Node, offset: number) {
155
+ function setSelFocus(view: EditorView, node: Node, offset: number) {
156
+ let sel = view.domSelection()
156
157
  if (selectionCollapsed(sel)) {
157
158
  let range = document.createRange()
158
159
  range.setEnd(node, offset)
@@ -222,7 +223,7 @@ function switchEditable(view: EditorView, node: HTMLElement, state: string) {
222
223
  // after it
223
224
  function safariDownArrowBug(view: EditorView) {
224
225
  if (!browser.safari || view.state.selection.$head.parentOffset > 0) return false
225
- let {focusNode, focusOffset} = view.domSelection()
226
+ let {focusNode, focusOffset} = view.domSelectionRange()
226
227
  if (focusNode && focusNode.nodeType == 1 && focusOffset == 0 &&
227
228
  focusNode.firstChild && (focusNode.firstChild as HTMLElement).contentEditable == "false") {
228
229
  let child = focusNode.firstChild as HTMLElement
package/src/dom.ts CHANGED
@@ -1,7 +1,9 @@
1
- import * as browser from "./browser"
2
-
3
1
  export type DOMNode = InstanceType<typeof window.Node>
4
2
  export type DOMSelection = InstanceType<typeof window.Selection>
3
+ export type DOMSelectionRange = {
4
+ focusNode: DOMNode | null, focusOffset: number,
5
+ anchorNode: DOMNode | null, anchorOffset: number
6
+ }
5
7
 
6
8
  export const domIndex = function(node: Node) {
7
9
  for (var index = 0;; index++) {
@@ -10,7 +12,7 @@ export const domIndex = function(node: Node) {
10
12
  }
11
13
  }
12
14
 
13
- export const parentNode = function(node: Node) {
15
+ export const parentNode = function(node: Node): Node | null {
14
16
  let parent = (node as HTMLSlotElement).assignedSlot || node.parentNode
15
17
  return parent && parent.nodeType == 11 ? (parent as ShadowRoot).host : parent
16
18
  }
@@ -80,11 +82,9 @@ function hasBlockDesc(dom: Node) {
80
82
 
81
83
  // Work around Chrome issue https://bugs.chromium.org/p/chromium/issues/detail?id=447523
82
84
  // (isCollapsed inappropriately returns true in shadow dom)
83
- export const selectionCollapsed = function(domSel: Selection) {
84
- let collapsed = domSel.isCollapsed
85
- if (collapsed && browser.chrome && domSel.rangeCount && !domSel.getRangeAt(0).collapsed)
86
- collapsed = false
87
- return collapsed
85
+ export const selectionCollapsed = function(domSel: DOMSelectionRange) {
86
+ return domSel.focusNode && isEquivalentPosition(domSel.focusNode, domSel.focusOffset,
87
+ domSel.anchorNode!, domSel.anchorOffset)
88
88
  }
89
89
 
90
90
  export function keyEvent(keyCode: number, key: string) {
@@ -94,3 +94,9 @@ export function keyEvent(keyCode: number, key: string) {
94
94
  ;(event as any).key = (event as any).code = key
95
95
  return event
96
96
  }
97
+
98
+ export function deepActiveElement(doc: Document) {
99
+ let elt = doc.activeElement
100
+ while (elt && elt.shadowRoot) elt = elt.shadowRoot.activeElement
101
+ return elt
102
+ }
package/src/domchange.ts CHANGED
@@ -15,7 +15,7 @@ import {EditorView} from "./index"
15
15
  function parseBetween(view: EditorView, from_: number, to_: number) {
16
16
  let {node: parent, fromOffset, toOffset, from, to} = view.docView.parseRange(from_, to_)
17
17
 
18
- let domSel = view.domSelection()
18
+ let domSel = view.domSelectionRange()
19
19
  let find: {node: DOMNode, offset: number, pos?: number}[] | undefined
20
20
  let anchor = domSel.anchorNode
21
21
  if (anchor && view.dom.contains(anchor.nodeType == 1 ? anchor : anchor.parentNode)) {
package/src/domcoords.ts CHANGED
@@ -452,15 +452,18 @@ function endOfTextblockHorizontal(view: EditorView, state: EditorState, dir: "le
452
452
  // one character, and see if that moves the cursor out of the
453
453
  // textblock (or doesn't move it at all, when at the start/end of
454
454
  // the document).
455
- let oldRange = sel.getRangeAt(0), oldNode = sel.focusNode, oldOff = sel.focusOffset
455
+ let {focusNode: oldNode, focusOffset: oldOff, anchorNode, anchorOffset} = view.domSelectionRange()
456
456
  let oldBidiLevel = (sel as any).caretBidiLevel // Only for Firefox
457
457
  ;(sel as any).modify("move", dir, "character")
458
458
  let parentDOM = $head.depth ? view.docView.domAfterPos($head.before()) : view.dom
459
- let result = !parentDOM.contains(sel.focusNode!.nodeType == 1 ? sel.focusNode : sel.focusNode!.parentNode) ||
460
- (oldNode == sel.focusNode && oldOff == sel.focusOffset)
459
+ let {focusNode: newNode, focusOffset: newOff} = view.domSelectionRange()
460
+ let result = newNode && !parentDOM.contains(newNode.nodeType == 1 ? newNode : newNode.parentNode) ||
461
+ (oldNode == newNode && oldOff == newOff)
461
462
  // Restore the previous selection
462
- sel.removeAllRanges()
463
- sel.addRange(oldRange)
463
+ try {
464
+ sel.collapse(anchorNode, anchorOffset)
465
+ if (oldNode && (oldNode != anchorNode || oldOff != anchorOffset) && sel.extend) sel.extend(oldNode, oldOff)
466
+ } catch (_) {}
464
467
  if (oldBidiLevel != null) (sel as any).caretBidiLevel = oldBidiLevel
465
468
  return result
466
469
  })
@@ -1,6 +1,6 @@
1
1
  import {Selection} from "prosemirror-state"
2
2
  import * as browser from "./browser"
3
- import {domIndex, isEquivalentPosition, selectionCollapsed, DOMSelection} from "./dom"
3
+ import {domIndex, isEquivalentPosition, selectionCollapsed, parentNode, DOMSelectionRange, DOMNode} from "./dom"
4
4
  import {hasFocusAndSelection, selectionToDOM, selectionFromDOM} from "./selection"
5
5
  import {EditorView} from "./index"
6
6
 
@@ -21,7 +21,7 @@ class SelectionState {
21
21
  focusNode: Node | null = null
22
22
  focusOffset: number = 0
23
23
 
24
- set(sel: DOMSelection) {
24
+ set(sel: DOMSelectionRange) {
25
25
  this.anchorNode = sel.anchorNode; this.anchorOffset = sel.anchorOffset
26
26
  this.focusNode = sel.focusNode; this.focusOffset = sel.focusOffset
27
27
  }
@@ -30,7 +30,7 @@ class SelectionState {
30
30
  this.anchorNode = this.focusNode = null
31
31
  }
32
32
 
33
- eq(sel: DOMSelection) {
33
+ eq(sel: DOMSelectionRange) {
34
34
  return sel.anchorNode == this.anchorNode && sel.anchorOffset == this.anchorOffset &&
35
35
  sel.focusNode == this.focusNode && sel.focusOffset == this.focusOffset
36
36
  }
@@ -127,7 +127,7 @@ export class DOMObserver {
127
127
  // us a selection change event before the DOM changes are
128
128
  // reported.
129
129
  if (browser.ie && browser.ie_version <= 11 && !this.view.state.selection.empty) {
130
- let sel = this.view.domSelection()
130
+ let sel = this.view.domSelectionRange()
131
131
  // Selection.isCollapsed isn't reliable on IE
132
132
  if (sel.focusNode && isEquivalentPosition(sel.focusNode, sel.focusOffset, sel.anchorNode!, sel.anchorOffset))
133
133
  return this.flushSoon()
@@ -136,16 +136,21 @@ export class DOMObserver {
136
136
  }
137
137
 
138
138
  setCurSelection() {
139
- this.currentSelection.set(this.view.domSelection())
139
+ this.currentSelection.set(this.view.domSelectionRange())
140
140
  }
141
141
 
142
- ignoreSelectionChange(sel: DOMSelection) {
143
- if (sel.rangeCount == 0) return true
144
- let container = sel.getRangeAt(0).commonAncestorContainer
145
- let desc = this.view.docView.nearestDesc(container)
142
+ ignoreSelectionChange(sel: DOMSelectionRange) {
143
+ if (!sel.focusNode) return true
144
+ let ancestors: Set<Node> = new Set, container: DOMNode | undefined
145
+ for (let scan: DOMNode | null = sel.focusNode; scan; scan = parentNode(scan)) ancestors.add(scan)
146
+ for (let scan = sel.anchorNode; scan; scan = parentNode(scan)) if (ancestors.has(scan)) {
147
+ container = scan
148
+ break
149
+ }
150
+ let desc = container && this.view.docView.nearestDesc(container)
146
151
  if (desc && desc.ignoreMutation({
147
152
  type: "selection",
148
- target: container.nodeType == 3 ? container.parentNode : container
153
+ target: container!.nodeType == 3 ? container!.parentNode : container
149
154
  } as any)) {
150
155
  this.setCurSelection()
151
156
  return true
@@ -161,7 +166,7 @@ export class DOMObserver {
161
166
  this.queue.length = 0
162
167
  }
163
168
 
164
- let sel = view.domSelection()
169
+ let sel = view.domSelectionRange()
165
170
  let newSel = !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(view) && !this.ignoreSelectionChange(sel)
166
171
 
167
172
  let from = -1, to = -1, typeOver = false, added: Node[] = []
@@ -270,3 +275,34 @@ function checkCSS(view: EditorView) {
270
275
  cssCheckWarned = true
271
276
  }
272
277
  }
278
+
279
+ // Used to work around a Safari Selection/shadow DOM bug
280
+ // Based on https://github.com/codemirror/dev/issues/414 fix
281
+ export function safariShadowSelectionRange(view: EditorView): DOMSelectionRange {
282
+ let found: StaticRange | undefined
283
+ function read(event: InputEvent) {
284
+ event.preventDefault()
285
+ event.stopImmediatePropagation()
286
+ found = event.getTargetRanges()[0]
287
+ }
288
+
289
+ // Because Safari (at least in 2018-2022) doesn't provide regular
290
+ // access to the selection inside a shadowRoot, we have to perform a
291
+ // ridiculous hack to get at it—using `execCommand` to trigger a
292
+ // `beforeInput` event so that we can read the target range from the
293
+ // event.
294
+ view.dom.addEventListener("beforeinput", read, true)
295
+ document.execCommand("indent")
296
+ view.dom.removeEventListener("beforeinput", read, true)
297
+
298
+ let anchorNode = found!.startContainer, anchorOffset = found!.startOffset
299
+ let focusNode = found!.endContainer, focusOffset = found!.endOffset
300
+
301
+ let currentAnchor = view.domAtPos(view.state.selection.anchor)
302
+ // Since such a range doesn't distinguish between anchor and head,
303
+ // use a heuristic that flips it around if its end matches the
304
+ // current anchor.
305
+ if (isEquivalentPosition(currentAnchor.node, currentAnchor.offset, focusNode, focusOffset))
306
+ [anchorNode, anchorOffset, focusNode, focusOffset] = [focusNode, focusOffset, anchorNode, anchorOffset]
307
+ return {anchorNode, anchorOffset, focusNode, focusOffset}
308
+ }
package/src/index.ts CHANGED
@@ -7,9 +7,9 @@ import {docViewDesc, ViewDesc, NodeView, NodeViewDesc} from "./viewdesc"
7
7
  import {initInput, destroyInput, dispatchEvent, ensureListeners, clearComposition, InputState} from "./input"
8
8
  import {selectionToDOM, anchorInRightPlace, syncNodeSelection} from "./selection"
9
9
  import {Decoration, viewDecorations, DecorationSource} from "./decoration"
10
- import {DOMObserver} from "./domobserver"
10
+ import {DOMObserver, safariShadowSelectionRange} from "./domobserver"
11
11
  import {readDOMChange} from "./domchange"
12
- import {DOMSelection, DOMNode} from "./dom"
12
+ import {DOMSelection, DOMNode, DOMSelectionRange, deepActiveElement} from "./dom"
13
13
  import * as browser from "./browser"
14
14
 
15
15
  export {Decoration, DecorationSet, DecorationAttrs, DecorationSource} from "./decoration"
@@ -195,7 +195,7 @@ export class EditorView {
195
195
  // Chrome sometimes starts misreporting the selection, so this
196
196
  // tracks that and forces a selection reset when our update
197
197
  // did write to the node.
198
- let chromeKludge = browser.chrome ? (this.trackWrites = this.domSelection().focusNode) : null
198
+ let chromeKludge = browser.chrome ? (this.trackWrites = this.domSelectionRange().focusNode) : null
199
199
  if (redraw || !this.docView.update(state.doc, outerDeco, innerDeco, this)) {
200
200
  this.docView.updateOuterDeco([])
201
201
  this.docView.destroy()
@@ -208,7 +208,8 @@ export class EditorView {
208
208
  // can cause a spurious DOM selection update, disrupting mouse
209
209
  // drag selection.
210
210
  if (forceSelUpdate ||
211
- !(this.input.mouseDown && this.domObserver.currentSelection.eq(this.domSelection()) && anchorInRightPlace(this))) {
211
+ !(this.input.mouseDown && this.domObserver.currentSelection.eq(this.domSelectionRange()) &&
212
+ anchorInRightPlace(this))) {
212
213
  selectionToDOM(this, forceSelUpdate)
213
214
  } else {
214
215
  syncNodeSelection(this, state.selection)
@@ -230,7 +231,7 @@ export class EditorView {
230
231
 
231
232
  /// @internal
232
233
  scrollToSelection() {
233
- let startDOM = this.domSelection().focusNode!
234
+ let startDOM = this.domSelectionRange().focusNode!
234
235
  if (this.someProp("handleScrollToSelection", f => f(this))) {
235
236
  // Handled
236
237
  } else if (this.state.selection instanceof NodeSelection) {
@@ -449,6 +450,12 @@ export class EditorView {
449
450
  else this.updateState(this.state.apply(tr))
450
451
  }
451
452
 
453
+ /// @internal
454
+ domSelectionRange(): DOMSelectionRange {
455
+ return browser.safari && this.root.nodeType === 11 && deepActiveElement(this.dom.ownerDocument) == this.dom
456
+ ? safariShadowSelectionRange(this) : this.domSelection()
457
+ }
458
+
452
459
  /// @internal
453
460
  domSelection(): DOMSelection {
454
461
  return (this.root as Document).getSelection()!
package/src/input.ts CHANGED
@@ -463,12 +463,12 @@ editHandlers.compositionstart = editHandlers.compositionupdate = view => {
463
463
  // the inserted text won't inherit the marks. So this moves it
464
464
  // inside if necessary.
465
465
  if (browser.gecko && state.selection.empty && $pos.parentOffset && !$pos.textOffset && $pos.nodeBefore!.marks.length) {
466
- let sel = view.domSelection()
466
+ let sel = view.domSelectionRange()
467
467
  for (let node = sel.focusNode, offset = sel.focusOffset; node && node.nodeType == 1 && offset != 0;) {
468
468
  let before = offset < 0 ? node.lastChild : node.childNodes[offset - 1]
469
469
  if (!before) break
470
470
  if (before.nodeType == 3) {
471
- sel.collapse(before, before.nodeValue!.length)
471
+ view.domSelection().collapse(before, before.nodeValue!.length)
472
472
  break
473
473
  } else {
474
474
  node = before
@@ -715,7 +715,7 @@ handlers.focus = view => {
715
715
  view.domObserver.start()
716
716
  view.focused = true
717
717
  setTimeout(() => {
718
- if (view.docView && view.hasFocus() && !view.domObserver.currentSelection.eq(view.domSelection()))
718
+ if (view.docView && view.hasFocus() && !view.domObserver.currentSelection.eq(view.domSelectionRange()))
719
719
  selectionToDOM(view)
720
720
  }, 20)
721
721
  }
package/src/selection.ts CHANGED
@@ -2,12 +2,12 @@ import {TextSelection, NodeSelection, Selection} from "prosemirror-state"
2
2
  import {ResolvedPos} from "prosemirror-model"
3
3
 
4
4
  import * as browser from "./browser"
5
- import {selectionCollapsed, isEquivalentPosition, domIndex, isOnEdge} from "./dom"
5
+ import {isEquivalentPosition, domIndex, isOnEdge, selectionCollapsed} from "./dom"
6
6
  import {EditorView} from "./index"
7
7
  import {NodeViewDesc} from "./viewdesc"
8
8
 
9
9
  export function selectionFromDOM(view: EditorView, origin: string | null = null) {
10
- let domSel = view.domSelection(), doc = view.state.doc
10
+ let domSel = view.domSelectionRange(), doc = view.state.doc
11
11
  if (!domSel.focusNode) return null
12
12
  let nearestDesc = view.docView.nearestDesc(domSel.focusNode), inWidget = nearestDesc && nearestDesc.size == 0
13
13
  let head = view.docView.posFromDOM(domSel.focusNode, domSel.focusOffset, 1)
@@ -50,7 +50,7 @@ export function selectionToDOM(view: EditorView, force = false) {
50
50
  // in Safari. And the drag selection delay is to workarond issues
51
51
  // which only present in Chrome.
52
52
  if (!force && view.input.mouseDown && view.input.mouseDown.allowDefault && browser.chrome) {
53
- let domSel = view.domSelection(), curSel = view.domObserver.currentSelection
53
+ let domSel = view.domSelectionRange(), curSel = view.domObserver.currentSelection
54
54
  if (domSel.anchorNode && curSel.anchorNode &&
55
55
  isEquivalentPosition(domSel.anchorNode, domSel.anchorOffset,
56
56
  curSel.anchorNode, curSel.anchorOffset)) {
@@ -121,7 +121,7 @@ function resetEditable(element: HTMLElement) {
121
121
  function removeClassOnSelectionChange(view: EditorView) {
122
122
  let doc = view.dom.ownerDocument
123
123
  doc.removeEventListener("selectionchange", view.input.hideSelectionGuard!)
124
- let domSel = view.domSelection()
124
+ let domSel = view.domSelectionRange()
125
125
  let node = domSel.anchorNode, offset = domSel.anchorOffset
126
126
  doc.addEventListener("selectionchange", view.input.hideSelectionGuard = () => {
127
127
  if (domSel.anchorNode != node || domSel.anchorOffset != offset) {
@@ -186,7 +186,7 @@ export function hasFocusAndSelection(view: EditorView) {
186
186
  }
187
187
 
188
188
  export function hasSelection(view: EditorView) {
189
- let sel = view.domSelection()
189
+ let sel = view.domSelectionRange()
190
190
  if (!sel.anchorNode) return false
191
191
  try {
192
192
  // Firefox will raise 'permission denied' errors when accessing
@@ -201,6 +201,6 @@ export function hasSelection(view: EditorView) {
201
201
 
202
202
  export function anchorInRightPlace(view: EditorView) {
203
203
  let anchorDOM = view.docView.domFromPos(view.state.selection.anchor, 0)
204
- let domSel = view.domSelection()
204
+ let domSel = view.domSelectionRange()
205
205
  return isEquivalentPosition(anchorDOM.node, anchorDOM.offset, domSel.anchorNode!, domSel.anchorOffset)
206
206
  }
package/src/viewdesc.ts CHANGED
@@ -419,20 +419,16 @@ export class ViewDesc {
419
419
  if ((domSel.extend || anchor == head) && !brKludge) {
420
420
  domSel.collapse(anchorDOM.node, anchorDOM.offset)
421
421
  try {
422
- if (anchor != head) {
423
- // This can crash on Safari if the editor is hidden, and
424
- // there was no selection. (#1308)
425
- try { domSel.extend(headDOM.node, headDOM.offset) }
426
- catch (_) {}
427
- }
422
+ if (anchor != head)
423
+ domSel.extend(headDOM.node, headDOM.offset)
428
424
  domSelExtended = true
429
- } catch (err) {
425
+ } catch (_) {
430
426
  // In some cases with Chrome the selection is empty after calling
431
427
  // collapse, even when it should be valid. This appears to be a bug, but
432
428
  // it is difficult to isolate. If this happens fallback to the old path
433
429
  // without using extend.
434
- if (!(err instanceof DOMException)) throw err
435
- // declare global: DOMException
430
+ // Similarly, this could crash on Safari if the editor is hidden, and
431
+ // there was no selection.
436
432
  }
437
433
  }
438
434
  if (!domSelExtended) {
@@ -762,7 +758,7 @@ export class NodeViewDesc extends ViewDesc {
762
758
  // are inside of this node
763
759
  let {from, to} = view.state.selection
764
760
  if (!(view.state.selection instanceof TextSelection) || from < pos || to > pos + this.node.content.size) return null
765
- let sel = view.domSelection()
761
+ let sel = view.domSelectionRange()
766
762
  let textNode = nearbyTextNode(sel.focusNode!, sel.focusOffset)
767
763
  if (!textNode || !this.dom.contains(textNode.parentNode)) return null
768
764