prosemirror-view 1.28.2 → 1.29.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/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;
@@ -2538,7 +2540,7 @@ function captureKeyDown(view, event) {
2538
2540
  }
2539
2541
 
2540
2542
  function serializeForClipboard(view, slice) {
2541
- view.someProp("transformCopied", f => { slice = f(slice); });
2543
+ view.someProp("transformCopied", f => { slice = f(slice, view); });
2542
2544
  let context = [], { content, openStart, openEnd } = slice;
2543
2545
  while (openStart > 1 && openEnd > 1 && content.childCount == 1 && content.firstChild.childCount == 1) {
2544
2546
  openStart--;
@@ -2563,7 +2565,7 @@ function serializeForClipboard(view, slice) {
2563
2565
  }
2564
2566
  if (firstChild && firstChild.nodeType == 1)
2565
2567
  firstChild.setAttribute("data-pm-slice", `${openStart} ${openEnd}${wrappers ? ` -${wrappers}` : ""} ${JSON.stringify(context)}`);
2566
- let text = view.someProp("clipboardTextSerializer", f => f(slice)) ||
2568
+ let text = view.someProp("clipboardTextSerializer", f => f(slice, view)) ||
2567
2569
  slice.content.textBetween(0, slice.content.size, "\n\n");
2568
2570
  return { dom: wrap, text };
2569
2571
  }
@@ -2575,10 +2577,10 @@ function parseFromClipboard(view, text, html, plainText, $context) {
2575
2577
  return null;
2576
2578
  let asText = text && (plainText || inCode || !html);
2577
2579
  if (asText) {
2578
- view.someProp("transformPastedText", f => { text = f(text, inCode || plainText); });
2580
+ view.someProp("transformPastedText", f => { text = f(text, inCode || plainText, view); });
2579
2581
  if (inCode)
2580
2582
  return text ? new Slice(Fragment.from(view.state.schema.text(text.replace(/\r\n?/g, "\n"))), 0, 0) : Slice.empty;
2581
- let parsed = view.someProp("clipboardTextParser", f => f(text, $context, plainText));
2583
+ let parsed = view.someProp("clipboardTextParser", f => f(text, $context, plainText, view));
2582
2584
  if (parsed) {
2583
2585
  slice = parsed;
2584
2586
  }
@@ -2594,7 +2596,7 @@ function parseFromClipboard(view, text, html, plainText, $context) {
2594
2596
  }
2595
2597
  }
2596
2598
  else {
2597
- view.someProp("transformPastedHTML", f => { html = f(html); });
2599
+ view.someProp("transformPastedHTML", f => { html = f(html, view); });
2598
2600
  dom = readHTML(html);
2599
2601
  if (webkit)
2600
2602
  restoreReplacedSpaces(dom);
@@ -2602,8 +2604,14 @@ function parseFromClipboard(view, text, html, plainText, $context) {
2602
2604
  let contextNode = dom && dom.querySelector("[data-pm-slice]");
2603
2605
  let sliceData = contextNode && /^(\d+) (\d+)(?: -(\d+))? (.*)/.exec(contextNode.getAttribute("data-pm-slice") || "");
2604
2606
  if (sliceData && sliceData[3])
2605
- for (let i = +sliceData[3]; i > 0 && dom.firstChild; i--)
2606
- dom = dom.firstChild;
2607
+ for (let i = +sliceData[3]; i > 0; i--) {
2608
+ let child = dom.firstChild;
2609
+ while (child && child.nodeType != 1)
2610
+ child = child.nextSibling;
2611
+ if (!child)
2612
+ break;
2613
+ dom = child;
2614
+ }
2607
2615
  if (!slice) {
2608
2616
  let parser = view.someProp("clipboardParser") || view.someProp("domParser") || DOMParser.fromSchema(view.state.schema);
2609
2617
  slice = parser.parseSlice(dom, {
@@ -2629,7 +2637,7 @@ function parseFromClipboard(view, text, html, plainText, $context) {
2629
2637
  slice = closeSlice(slice, openStart, openEnd);
2630
2638
  }
2631
2639
  }
2632
- view.someProp("transformPasted", f => { slice = f(slice); });
2640
+ view.someProp("transformPasted", f => { slice = f(slice, view); });
2633
2641
  return slice;
2634
2642
  }
2635
2643
  const inlineParents = /^(a|abbr|acronym|b|cite|code|del|em|i|ins|kbd|label|output|q|ruby|s|samp|span|strong|sub|sup|time|u|tt|var)$/i;
@@ -3206,13 +3214,13 @@ editHandlers.compositionstart = editHandlers.compositionupdate = view => {
3206
3214
  // the inserted text won't inherit the marks. So this moves it
3207
3215
  // inside if necessary.
3208
3216
  if (gecko && state.selection.empty && $pos.parentOffset && !$pos.textOffset && $pos.nodeBefore.marks.length) {
3209
- let sel = view.domSelection();
3217
+ let sel = view.domSelectionRange();
3210
3218
  for (let node = sel.focusNode, offset = sel.focusOffset; node && node.nodeType == 1 && offset != 0;) {
3211
3219
  let before = offset < 0 ? node.lastChild : node.childNodes[offset - 1];
3212
3220
  if (!before)
3213
3221
  break;
3214
3222
  if (before.nodeType == 3) {
3215
- sel.collapse(before, before.nodeValue.length);
3223
+ view.domSelection().collapse(before, before.nodeValue.length);
3216
3224
  break;
3217
3225
  }
3218
3226
  else {
@@ -3419,7 +3427,7 @@ editHandlers.drop = (view, _event) => {
3419
3427
  let $mouse = view.state.doc.resolve(eventPos.pos);
3420
3428
  let slice = dragging && dragging.slice;
3421
3429
  if (slice) {
3422
- view.someProp("transformPasted", f => { slice = f(slice); });
3430
+ view.someProp("transformPasted", f => { slice = f(slice, view); });
3423
3431
  }
3424
3432
  else {
3425
3433
  slice = parseFromClipboard(view, event.dataTransfer.getData(brokenClipboardAPI ? "Text" : "text/plain"), brokenClipboardAPI ? null : event.dataTransfer.getData("text/html"), false, $mouse);
@@ -3468,7 +3476,7 @@ handlers.focus = view => {
3468
3476
  view.domObserver.start();
3469
3477
  view.focused = true;
3470
3478
  setTimeout(() => {
3471
- if (view.docView && view.hasFocus() && !view.domObserver.currentSelection.eq(view.domSelection()))
3479
+ if (view.docView && view.hasFocus() && !view.domObserver.currentSelection.eq(view.domSelectionRange()))
3472
3480
  selectionToDOM(view);
3473
3481
  }, 20);
3474
3482
  }
@@ -4300,7 +4308,7 @@ class DOMObserver {
4300
4308
  // us a selection change event before the DOM changes are
4301
4309
  // reported.
4302
4310
  if (ie && ie_version <= 11 && !this.view.state.selection.empty) {
4303
- let sel = this.view.domSelection();
4311
+ let sel = this.view.domSelectionRange();
4304
4312
  // Selection.isCollapsed isn't reliable on IE
4305
4313
  if (sel.focusNode && isEquivalentPosition(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset))
4306
4314
  return this.flushSoon();
@@ -4308,13 +4316,20 @@ class DOMObserver {
4308
4316
  this.flush();
4309
4317
  }
4310
4318
  setCurSelection() {
4311
- this.currentSelection.set(this.view.domSelection());
4319
+ this.currentSelection.set(this.view.domSelectionRange());
4312
4320
  }
4313
4321
  ignoreSelectionChange(sel) {
4314
- if (sel.rangeCount == 0)
4322
+ if (!sel.focusNode)
4315
4323
  return true;
4316
- let container = sel.getRangeAt(0).commonAncestorContainer;
4317
- let desc = this.view.docView.nearestDesc(container);
4324
+ let ancestors = new Set, container;
4325
+ for (let scan = sel.focusNode; scan; scan = parentNode(scan))
4326
+ ancestors.add(scan);
4327
+ for (let scan = sel.anchorNode; scan; scan = parentNode(scan))
4328
+ if (ancestors.has(scan)) {
4329
+ container = scan;
4330
+ break;
4331
+ }
4332
+ let desc = container && this.view.docView.nearestDesc(container);
4318
4333
  if (desc && desc.ignoreMutation({
4319
4334
  type: "selection",
4320
4335
  target: container.nodeType == 3 ? container.parentNode : container
@@ -4332,7 +4347,7 @@ class DOMObserver {
4332
4347
  mutations = this.queue.concat(mutations);
4333
4348
  this.queue.length = 0;
4334
4349
  }
4335
- let sel = view.domSelection();
4350
+ let sel = view.domSelectionRange();
4336
4351
  let newSel = !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(view) && !this.ignoreSelectionChange(sel);
4337
4352
  let from = -1, to = -1, typeOver = false, added = [];
4338
4353
  if (view.editable) {
@@ -4449,6 +4464,33 @@ function checkCSS(view) {
4449
4464
  cssCheckWarned = true;
4450
4465
  }
4451
4466
  }
4467
+ // Used to work around a Safari Selection/shadow DOM bug
4468
+ // Based on https://github.com/codemirror/dev/issues/414 fix
4469
+ function safariShadowSelectionRange(view) {
4470
+ let found;
4471
+ function read(event) {
4472
+ event.preventDefault();
4473
+ event.stopImmediatePropagation();
4474
+ found = event.getTargetRanges()[0];
4475
+ }
4476
+ // Because Safari (at least in 2018-2022) doesn't provide regular
4477
+ // access to the selection inside a shadowRoot, we have to perform a
4478
+ // ridiculous hack to get at it—using `execCommand` to trigger a
4479
+ // `beforeInput` event so that we can read the target range from the
4480
+ // event.
4481
+ view.dom.addEventListener("beforeinput", read, true);
4482
+ document.execCommand("indent");
4483
+ view.dom.removeEventListener("beforeinput", read, true);
4484
+ let anchorNode = found.startContainer, anchorOffset = found.startOffset;
4485
+ let focusNode = found.endContainer, focusOffset = found.endOffset;
4486
+ let currentAnchor = view.domAtPos(view.state.selection.anchor);
4487
+ // Since such a range doesn't distinguish between anchor and head,
4488
+ // use a heuristic that flips it around if its end matches the
4489
+ // current anchor.
4490
+ if (isEquivalentPosition(currentAnchor.node, currentAnchor.offset, focusNode, focusOffset))
4491
+ [anchorNode, anchorOffset, focusNode, focusOffset] = [focusNode, focusOffset, anchorNode, anchorOffset];
4492
+ return { anchorNode, anchorOffset, focusNode, focusOffset };
4493
+ }
4452
4494
 
4453
4495
  // Note that all referencing and parsing is done with the
4454
4496
  // start-of-operation selection and document, since that's the one
@@ -4457,7 +4499,7 @@ function checkCSS(view) {
4457
4499
  // readDOMChange.
4458
4500
  function parseBetween(view, from_, to_) {
4459
4501
  let { node: parent, fromOffset, toOffset, from, to } = view.docView.parseRange(from_, to_);
4460
- let domSel = view.domSelection();
4502
+ let domSel = view.domSelectionRange();
4461
4503
  let find;
4462
4504
  let anchor = domSel.anchorNode;
4463
4505
  if (anchor && view.dom.contains(anchor.nodeType == 1 ? anchor : anchor.parentNode)) {
@@ -4557,7 +4599,7 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
4557
4599
  view.input.lastKeyCode = null;
4558
4600
  let change = findDiff(compare.content, parse.doc.content, parse.from, preferredPos, preferredSide);
4559
4601
  if ((ios && view.input.lastIOSEnter > Date.now() - 225 || android) &&
4560
- addedNodes.some(n => n.nodeName == "DIV" || n.nodeName == "P") &&
4602
+ addedNodes.some(n => n.nodeName == "DIV" || n.nodeName == "P" || n.nodeName == "LI") &&
4561
4603
  (!change || change.endA >= change.endB) &&
4562
4604
  view.someProp("handleKeyDown", f => f(view, keyEvent(13, "Enter")))) {
4563
4605
  view.input.lastIOSEnter = 0;
@@ -4985,7 +5027,7 @@ class EditorView {
4985
5027
  // Chrome sometimes starts misreporting the selection, so this
4986
5028
  // tracks that and forces a selection reset when our update
4987
5029
  // did write to the node.
4988
- let chromeKludge = chrome ? (this.trackWrites = this.domSelection().focusNode) : null;
5030
+ let chromeKludge = chrome ? (this.trackWrites = this.domSelectionRange().focusNode) : null;
4989
5031
  if (redraw || !this.docView.update(state.doc, outerDeco, innerDeco, this)) {
4990
5032
  this.docView.updateOuterDeco([]);
4991
5033
  this.docView.destroy();
@@ -4999,7 +5041,8 @@ class EditorView {
4999
5041
  // can cause a spurious DOM selection update, disrupting mouse
5000
5042
  // drag selection.
5001
5043
  if (forceSelUpdate ||
5002
- !(this.input.mouseDown && this.domObserver.currentSelection.eq(this.domSelection()) && anchorInRightPlace(this))) {
5044
+ !(this.input.mouseDown && this.domObserver.currentSelection.eq(this.domSelectionRange()) &&
5045
+ anchorInRightPlace(this))) {
5003
5046
  selectionToDOM(this, forceSelUpdate);
5004
5047
  }
5005
5048
  else {
@@ -5023,7 +5066,7 @@ class EditorView {
5023
5066
  @internal
5024
5067
  */
5025
5068
  scrollToSelection() {
5026
- let startDOM = this.domSelection().focusNode;
5069
+ let startDOM = this.domSelectionRange().focusNode;
5027
5070
  if (this.someProp("handleScrollToSelection", f => f(this))) ;
5028
5071
  else if (this.state.selection instanceof NodeSelection) {
5029
5072
  let target = this.docView.domAfterPos(this.state.selection.from);
@@ -5261,6 +5304,13 @@ class EditorView {
5261
5304
  /**
5262
5305
  @internal
5263
5306
  */
5307
+ domSelectionRange() {
5308
+ return safari && this.root.nodeType === 11 && deepActiveElement(this.dom.ownerDocument) == this.dom
5309
+ ? safariShadowSelectionRange(this) : this.domSelection();
5310
+ }
5311
+ /**
5312
+ @internal
5313
+ */
5264
5314
  domSelection() {
5265
5315
  return this.root.getSelection();
5266
5316
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prosemirror-view",
3
- "version": "1.28.2",
3
+ "version": "1.29.0",
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/clipboard.ts CHANGED
@@ -3,7 +3,7 @@ import * as browser from "./browser"
3
3
  import {EditorView} from "./index"
4
4
 
5
5
  export function serializeForClipboard(view: EditorView, slice: Slice) {
6
- view.someProp("transformCopied", f => { slice = f(slice!) })
6
+ view.someProp("transformCopied", f => { slice = f(slice!, view) })
7
7
 
8
8
  let context = [], {content, openStart, openEnd} = slice
9
9
  while (openStart > 1 && openEnd > 1 && content.childCount == 1 && content.firstChild!.childCount == 1) {
@@ -33,7 +33,7 @@ export function serializeForClipboard(view: EditorView, slice: Slice) {
33
33
  (firstChild as HTMLElement).setAttribute(
34
34
  "data-pm-slice", `${openStart} ${openEnd}${wrappers ? ` -${wrappers}` : ""} ${JSON.stringify(context)}`)
35
35
 
36
- let text = view.someProp("clipboardTextSerializer", f => f(slice)) ||
36
+ let text = view.someProp("clipboardTextSerializer", f => f(slice, view)) ||
37
37
  slice.content.textBetween(0, slice.content.size, "\n\n")
38
38
 
39
39
  return {dom: wrap, text}
@@ -46,9 +46,9 @@ export function parseFromClipboard(view: EditorView, text: string, html: string
46
46
  if (!html && !text) return null
47
47
  let asText = text && (plainText || inCode || !html)
48
48
  if (asText) {
49
- view.someProp("transformPastedText", f => { text = f(text, inCode || plainText) })
49
+ view.someProp("transformPastedText", f => { text = f(text, inCode || plainText, view) })
50
50
  if (inCode) return text ? new Slice(Fragment.from(view.state.schema.text(text.replace(/\r\n?/g, "\n"))), 0, 0) : Slice.empty
51
- let parsed = view.someProp("clipboardTextParser", f => f(text, $context, plainText))
51
+ let parsed = view.someProp("clipboardTextParser", f => f(text, $context, plainText, view))
52
52
  if (parsed) {
53
53
  slice = parsed
54
54
  } else {
@@ -61,15 +61,19 @@ export function parseFromClipboard(view: EditorView, text: string, html: string
61
61
  })
62
62
  }
63
63
  } else {
64
- view.someProp("transformPastedHTML", f => { html = f(html!) })
64
+ view.someProp("transformPastedHTML", f => { html = f(html!, view) })
65
65
  dom = readHTML(html!)
66
66
  if (browser.webkit) restoreReplacedSpaces(dom)
67
67
  }
68
68
 
69
69
  let contextNode = dom && dom.querySelector("[data-pm-slice]")
70
70
  let sliceData = contextNode && /^(\d+) (\d+)(?: -(\d+))? (.*)/.exec(contextNode.getAttribute("data-pm-slice") || "")
71
- if (sliceData && sliceData[3]) for (let i = +sliceData[3]; i > 0 && dom!.firstChild; i--)
72
- dom = dom!.firstChild as HTMLElement
71
+ if (sliceData && sliceData[3]) for (let i = +sliceData[3]; i > 0; i--) {
72
+ let child = dom!.firstChild
73
+ while (child && child.nodeType != 1) child = child.nextSibling
74
+ if (!child) break
75
+ dom = child as HTMLElement
76
+ }
73
77
 
74
78
  if (!slice) {
75
79
  let parser = view.someProp("clipboardParser") || view.someProp("domParser") || DOMParser.fromSchema(view.state.schema)
@@ -97,7 +101,7 @@ export function parseFromClipboard(view: EditorView, text: string, html: string
97
101
  }
98
102
  }
99
103
 
100
- view.someProp("transformPasted", f => { slice = f(slice!) })
104
+ view.someProp("transformPasted", f => { slice = f(slice!, view) })
101
105
  return slice
102
106
  }
103
107
 
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)) {
@@ -111,7 +111,7 @@ export function readDOMChange(view: EditorView, from: number, to: number, typeOv
111
111
 
112
112
  let change = findDiff(compare.content, parse.doc.content, parse.from, preferredPos, preferredSide)
113
113
  if ((browser.ios && view.input.lastIOSEnter > Date.now() - 225 || browser.android) &&
114
- addedNodes.some(n => n.nodeName == "DIV" || n.nodeName == "P") &&
114
+ addedNodes.some(n => n.nodeName == "DIV" || n.nodeName == "P" || n.nodeName == "LI") &&
115
115
  (!change || change.endA >= change.endB) &&
116
116
  view.someProp("handleKeyDown", f => f(view, keyEvent(13, "Enter")))) {
117
117
  view.input.lastIOSEnter = 0