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/CHANGELOG.md +20 -2
- package/README.md +1 -1
- package/dist/index.cjs +151 -77
- package/dist/index.d.ts +6 -6
- package/dist/index.js +128 -78
- package/package.json +1 -1
- package/src/capturekeys.ts +9 -8
- package/src/clipboard.ts +12 -8
- package/src/dom.ts +14 -8
- package/src/domchange.ts +2 -2
- package/src/domcoords.ts +8 -5
- package/src/domobserver.ts +47 -11
- package/src/index.ts +18 -11
- package/src/input.ts +4 -4
- package/src/selection.ts +6 -6
- package/src/viewdesc.ts +6 -10
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
|
-
|
|
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
|
|
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
|
|
589
|
-
|
|
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
|
-
|
|
592
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
|
976
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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,
|
|
2353
|
+
setSelFocus(view, node, offset);
|
|
2353
2354
|
else if (moveNode)
|
|
2354
|
-
setSelFocus(view,
|
|
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.
|
|
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,
|
|
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,
|
|
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.
|
|
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
|
|
2606
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
4319
|
+
this.currentSelection.set(this.view.domSelectionRange());
|
|
4312
4320
|
}
|
|
4313
4321
|
ignoreSelectionChange(sel) {
|
|
4314
|
-
if (sel.
|
|
4322
|
+
if (!sel.focusNode)
|
|
4315
4323
|
return true;
|
|
4316
|
-
let
|
|
4317
|
-
let
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
package/src/capturekeys.ts
CHANGED
|
@@ -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
|
|
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.
|
|
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,
|
|
107
|
-
else if (moveNode) setSelFocus(view,
|
|
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.
|
|
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,
|
|
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,
|
|
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.
|
|
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
|
|
72
|
-
|
|
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:
|
|
84
|
-
|
|
85
|
-
|
|
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.
|
|
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
|