prosemirror-view 1.40.0 → 1.41.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 +22 -0
- package/dist/index.cjs +15 -13
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +14 -14
- package/package.json +1 -1
- package/src/clipboard.ts +7 -3
- package/src/domchange.ts +1 -1
- package/src/domcoords.ts +1 -1
- package/src/index.ts +3 -2
- package/src/input.ts +1 -1
- package/src/selection.ts +3 -6
- package/src/viewdesc.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
## 1.41.0 (2025-09-06)
|
|
2
|
+
|
|
3
|
+
### Bug fixes
|
|
4
|
+
|
|
5
|
+
Make sure `transformPasted` is properly called when pasting into a code block.
|
|
6
|
+
|
|
7
|
+
Fix an issue where the editor didn't properly set the DOM selection in a shadow DOM on Safari in some situations.
|
|
8
|
+
|
|
9
|
+
Work around a Safari issue that broke composition right after changing the set of active marks.
|
|
10
|
+
|
|
11
|
+
### New features
|
|
12
|
+
|
|
13
|
+
Pass an argument to `transformPasted` that tells the handler whether the paste is plain text. Call transformPasted when pasting into a code block
|
|
14
|
+
|
|
15
|
+
## 1.40.1 (2025-07-18)
|
|
16
|
+
|
|
17
|
+
### Bug fixes
|
|
18
|
+
|
|
19
|
+
Fix an issue where, for some Android virtual keyboards, joining textblocks that contain an image with Backspace would run the handler for Enter instead.
|
|
20
|
+
|
|
21
|
+
Fix an issue causing `posAtCoords` to get confused by table cells sticking out of their parent row elements.
|
|
22
|
+
|
|
1
23
|
## 1.40.0 (2025-06-02)
|
|
2
24
|
|
|
3
25
|
### Bug fixes
|
package/dist/index.cjs
CHANGED
|
@@ -403,7 +403,7 @@ function posFromCaret(view, node, offset, coords) {
|
|
|
403
403
|
rect = void 0;
|
|
404
404
|
if (!desc) return null;
|
|
405
405
|
if (desc.dom.nodeType == 1 && (desc.node.isBlock && desc.parent || !desc.contentDOM) && ((rect = desc.dom.getBoundingClientRect()).width || rect.height)) {
|
|
406
|
-
if (desc.node.isBlock && desc.parent) {
|
|
406
|
+
if (desc.node.isBlock && desc.parent && !/^T(R|BODY|HEAD|FOOT)$/.test(desc.dom.nodeName)) {
|
|
407
407
|
if (!sawBlock && rect.left > coords.left || rect.top > coords.top) outsideBlock = desc.posBefore;else if (!sawBlock && rect.right < coords.left || rect.bottom < coords.top) outsideBlock = desc.posAfter;
|
|
408
408
|
sawBlock = true;
|
|
409
409
|
}
|
|
@@ -998,7 +998,7 @@ var ViewDesc = function () {
|
|
|
998
998
|
}
|
|
999
999
|
if (!(force || brKludge && safari) && isEquivalentPosition(anchorDOM.node, anchorDOM.offset, selRange.anchorNode, selRange.anchorOffset) && isEquivalentPosition(headDOM.node, headDOM.offset, selRange.focusNode, selRange.focusOffset)) return;
|
|
1000
1000
|
var domSelExtended = false;
|
|
1001
|
-
if ((domSel.extend || anchor == head) && !brKludge) {
|
|
1001
|
+
if ((domSel.extend || anchor == head) && !(brKludge && gecko)) {
|
|
1002
1002
|
domSel.collapse(anchorDOM.node, anchorDOM.offset);
|
|
1003
1003
|
try {
|
|
1004
1004
|
if (anchor != head) domSel.extend(headDOM.node, headDOM.offset);
|
|
@@ -2271,15 +2271,11 @@ function removeClassOnSelectionChange(view) {
|
|
|
2271
2271
|
});
|
|
2272
2272
|
}
|
|
2273
2273
|
function selectCursorWrapper(view) {
|
|
2274
|
-
var domSel = view.domSelection()
|
|
2275
|
-
range = document.createRange();
|
|
2274
|
+
var domSel = view.domSelection();
|
|
2276
2275
|
if (!domSel) return;
|
|
2277
2276
|
var node = view.cursorWrapper.dom,
|
|
2278
2277
|
img = node.nodeName == "IMG";
|
|
2279
|
-
if (img)
|
|
2280
|
-
range.collapse(true);
|
|
2281
|
-
domSel.removeAllRanges();
|
|
2282
|
-
domSel.addRange(range);
|
|
2278
|
+
if (img) domSel.collapse(node.parentNode, domIndex(node) + 1);else domSel.collapse(node, 0);
|
|
2283
2279
|
if (!img && !view.state.selection.visible && ie && ie_version <= 11) {
|
|
2284
2280
|
node.disabled = true;
|
|
2285
2281
|
node.disabled = false;
|
|
@@ -2677,12 +2673,18 @@ function parseFromClipboard(view, text, html, plainText, $context) {
|
|
|
2677
2673
|
var inCode = $context.parent.type.spec.code;
|
|
2678
2674
|
var dom, slice;
|
|
2679
2675
|
if (!html && !text) return null;
|
|
2680
|
-
var asText = text && (plainText || inCode || !html);
|
|
2676
|
+
var asText = !!text && (plainText || inCode || !html);
|
|
2681
2677
|
if (asText) {
|
|
2682
2678
|
view.someProp("transformPastedText", function (f) {
|
|
2683
2679
|
text = f(text, inCode || plainText, view);
|
|
2684
2680
|
});
|
|
2685
|
-
if (inCode)
|
|
2681
|
+
if (inCode) {
|
|
2682
|
+
slice = new prosemirrorModel.Slice(prosemirrorModel.Fragment.from(view.state.schema.text(text.replace(/\r\n?/g, "\n"))), 0, 0);
|
|
2683
|
+
view.someProp("transformPasted", function (f) {
|
|
2684
|
+
slice = f(slice, view, true);
|
|
2685
|
+
});
|
|
2686
|
+
return slice;
|
|
2687
|
+
}
|
|
2686
2688
|
var parsed = view.someProp("clipboardTextParser", function (f) {
|
|
2687
2689
|
return f(text, $context, plainText, view);
|
|
2688
2690
|
});
|
|
@@ -2739,7 +2741,7 @@ function parseFromClipboard(view, text, html, plainText, $context) {
|
|
|
2739
2741
|
}
|
|
2740
2742
|
}
|
|
2741
2743
|
view.someProp("transformPasted", function (f) {
|
|
2742
|
-
slice = f(slice, view);
|
|
2744
|
+
slice = f(slice, view, asText);
|
|
2743
2745
|
});
|
|
2744
2746
|
return slice;
|
|
2745
2747
|
}
|
|
@@ -3492,7 +3494,7 @@ editHandlers.drop = function (view, _event) {
|
|
|
3492
3494
|
var slice = dragging && dragging.slice;
|
|
3493
3495
|
if (slice) {
|
|
3494
3496
|
view.someProp("transformPasted", function (f) {
|
|
3495
|
-
slice = f(slice, view);
|
|
3497
|
+
slice = f(slice, view, false);
|
|
3496
3498
|
});
|
|
3497
3499
|
} else {
|
|
3498
3500
|
slice = parseFromClipboard(view, getText(event.dataTransfer), brokenClipboardAPI ? null : event.dataTransfer.getData("text/html"), false, $mouse);
|
|
@@ -4605,7 +4607,7 @@ function ruleFromNode(dom) {
|
|
|
4605
4607
|
}
|
|
4606
4608
|
return null;
|
|
4607
4609
|
}
|
|
4608
|
-
var isInline = /^(a|abbr|acronym|b|bd[io]|big|br|button|cite|code|data(list)?|del|dfn|em|i|ins|kbd|label|map|mark|meter|output|q|ruby|s|samp|small|span|strong|su[bp]|time|u|tt|var)$/i;
|
|
4610
|
+
var isInline = /^(a|abbr|acronym|b|bd[io]|big|br|button|cite|code|data(list)?|del|dfn|em|i|img|ins|kbd|label|map|mark|meter|output|q|ruby|s|samp|small|span|strong|su[bp]|time|u|tt|var)$/i;
|
|
4609
4611
|
function readDOMChange(view, from, to, typeOver, addedNodes) {
|
|
4610
4612
|
var compositionID = view.input.compositionPendingChanges || (view.composing ? view.input.compositionID : 0);
|
|
4611
4613
|
view.input.compositionPendingChanges = 0;
|
package/dist/index.d.cts
CHANGED
|
@@ -757,9 +757,10 @@ interface EditorProps<P = any> {
|
|
|
757
757
|
clipboardTextParser?: (this: P, text: string, $context: ResolvedPos, plain: boolean, view: EditorView) => Slice;
|
|
758
758
|
/**
|
|
759
759
|
Can be used to transform pasted or dragged-and-dropped content
|
|
760
|
-
before it is applied to the document.
|
|
760
|
+
before it is applied to the document. The `plain` flag will be
|
|
761
|
+
true when the text is pasted as plain text.
|
|
761
762
|
*/
|
|
762
|
-
transformPasted?: (this: P, slice: Slice, view: EditorView) => Slice;
|
|
763
|
+
transformPasted?: (this: P, slice: Slice, view: EditorView, plain: boolean) => Slice;
|
|
763
764
|
/**
|
|
764
765
|
Can be used to transform copied or cut content before it is
|
|
765
766
|
serialized to the clipboard.
|
package/dist/index.d.ts
CHANGED
|
@@ -757,9 +757,10 @@ interface EditorProps<P = any> {
|
|
|
757
757
|
clipboardTextParser?: (this: P, text: string, $context: ResolvedPos, plain: boolean, view: EditorView) => Slice;
|
|
758
758
|
/**
|
|
759
759
|
Can be used to transform pasted or dragged-and-dropped content
|
|
760
|
-
before it is applied to the document.
|
|
760
|
+
before it is applied to the document. The `plain` flag will be
|
|
761
|
+
true when the text is pasted as plain text.
|
|
761
762
|
*/
|
|
762
|
-
transformPasted?: (this: P, slice: Slice, view: EditorView) => Slice;
|
|
763
|
+
transformPasted?: (this: P, slice: Slice, view: EditorView, plain: boolean) => Slice;
|
|
763
764
|
/**
|
|
764
765
|
Can be used to transform copied or cut content before it is
|
|
765
766
|
serialized to the clipboard.
|
package/dist/index.js
CHANGED
|
@@ -414,7 +414,7 @@ function posFromCaret(view, node, offset, coords) {
|
|
|
414
414
|
if (desc.dom.nodeType == 1 && (desc.node.isBlock && desc.parent || !desc.contentDOM) &&
|
|
415
415
|
// Ignore elements with zero-size bounding rectangles
|
|
416
416
|
((rect = desc.dom.getBoundingClientRect()).width || rect.height)) {
|
|
417
|
-
if (desc.node.isBlock && desc.parent) {
|
|
417
|
+
if (desc.node.isBlock && desc.parent && !/^T(R|BODY|HEAD|FOOT)$/.test(desc.dom.nodeName)) {
|
|
418
418
|
// Only apply the horizontal test to the innermost block. Vertical for any parent.
|
|
419
419
|
if (!sawBlock && rect.left > coords.left || rect.top > coords.top)
|
|
420
420
|
outsideBlock = desc.posBefore;
|
|
@@ -1072,7 +1072,7 @@ class ViewDesc {
|
|
|
1072
1072
|
// (one where the focus is before the anchor), but not all
|
|
1073
1073
|
// browsers support it yet.
|
|
1074
1074
|
let domSelExtended = false;
|
|
1075
|
-
if ((domSel.extend || anchor == head) && !brKludge) {
|
|
1075
|
+
if ((domSel.extend || anchor == head) && !(brKludge && gecko)) {
|
|
1076
1076
|
domSel.collapse(anchorDOM.node, anchorDOM.offset);
|
|
1077
1077
|
try {
|
|
1078
1078
|
if (anchor != head)
|
|
@@ -2333,17 +2333,14 @@ function removeClassOnSelectionChange(view) {
|
|
|
2333
2333
|
});
|
|
2334
2334
|
}
|
|
2335
2335
|
function selectCursorWrapper(view) {
|
|
2336
|
-
let domSel = view.domSelection()
|
|
2336
|
+
let domSel = view.domSelection();
|
|
2337
2337
|
if (!domSel)
|
|
2338
2338
|
return;
|
|
2339
2339
|
let node = view.cursorWrapper.dom, img = node.nodeName == "IMG";
|
|
2340
2340
|
if (img)
|
|
2341
|
-
|
|
2341
|
+
domSel.collapse(node.parentNode, domIndex(node) + 1);
|
|
2342
2342
|
else
|
|
2343
|
-
|
|
2344
|
-
range.collapse(true);
|
|
2345
|
-
domSel.removeAllRanges();
|
|
2346
|
-
domSel.addRange(range);
|
|
2343
|
+
domSel.collapse(node, 0);
|
|
2347
2344
|
// Kludge to kill 'control selection' in IE11 when selecting an
|
|
2348
2345
|
// invisible cursor wrapper, since that would result in those weird
|
|
2349
2346
|
// resize handles and a selection that considers the absolutely
|
|
@@ -2821,11 +2818,14 @@ function parseFromClipboard(view, text, html, plainText, $context) {
|
|
|
2821
2818
|
let dom, slice;
|
|
2822
2819
|
if (!html && !text)
|
|
2823
2820
|
return null;
|
|
2824
|
-
let asText = text && (plainText || inCode || !html);
|
|
2821
|
+
let asText = !!text && (plainText || inCode || !html);
|
|
2825
2822
|
if (asText) {
|
|
2826
2823
|
view.someProp("transformPastedText", f => { text = f(text, inCode || plainText, view); });
|
|
2827
|
-
if (inCode)
|
|
2828
|
-
|
|
2824
|
+
if (inCode) {
|
|
2825
|
+
slice = new Slice(Fragment.from(view.state.schema.text(text.replace(/\r\n?/g, "\n"))), 0, 0);
|
|
2826
|
+
view.someProp("transformPasted", f => { slice = f(slice, view, true); });
|
|
2827
|
+
return slice;
|
|
2828
|
+
}
|
|
2829
2829
|
let parsed = view.someProp("clipboardTextParser", f => f(text, $context, plainText, view));
|
|
2830
2830
|
if (parsed) {
|
|
2831
2831
|
slice = parsed;
|
|
@@ -2883,7 +2883,7 @@ function parseFromClipboard(view, text, html, plainText, $context) {
|
|
|
2883
2883
|
slice = closeSlice(slice, openStart, openEnd);
|
|
2884
2884
|
}
|
|
2885
2885
|
}
|
|
2886
|
-
view.someProp("transformPasted", f => { slice = f(slice, view); });
|
|
2886
|
+
view.someProp("transformPasted", f => { slice = f(slice, view, asText); });
|
|
2887
2887
|
return slice;
|
|
2888
2888
|
}
|
|
2889
2889
|
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;
|
|
@@ -3743,7 +3743,7 @@ editHandlers.drop = (view, _event) => {
|
|
|
3743
3743
|
let $mouse = view.state.doc.resolve(eventPos.pos);
|
|
3744
3744
|
let slice = dragging && dragging.slice;
|
|
3745
3745
|
if (slice) {
|
|
3746
|
-
view.someProp("transformPasted", f => { slice = f(slice, view); });
|
|
3746
|
+
view.someProp("transformPasted", f => { slice = f(slice, view, false); });
|
|
3747
3747
|
}
|
|
3748
3748
|
else {
|
|
3749
3749
|
slice = parseFromClipboard(view, getText(event.dataTransfer), brokenClipboardAPI ? null : event.dataTransfer.getData("text/html"), false, $mouse);
|
|
@@ -4929,7 +4929,7 @@ function ruleFromNode(dom) {
|
|
|
4929
4929
|
}
|
|
4930
4930
|
return null;
|
|
4931
4931
|
}
|
|
4932
|
-
const isInline = /^(a|abbr|acronym|b|bd[io]|big|br|button|cite|code|data(list)?|del|dfn|em|i|ins|kbd|label|map|mark|meter|output|q|ruby|s|samp|small|span|strong|su[bp]|time|u|tt|var)$/i;
|
|
4932
|
+
const isInline = /^(a|abbr|acronym|b|bd[io]|big|br|button|cite|code|data(list)?|del|dfn|em|i|img|ins|kbd|label|map|mark|meter|output|q|ruby|s|samp|small|span|strong|su[bp]|time|u|tt|var)$/i;
|
|
4933
4933
|
function readDOMChange(view, from, to, typeOver, addedNodes) {
|
|
4934
4934
|
let compositionID = view.input.compositionPendingChanges || (view.composing ? view.input.compositionID : 0);
|
|
4935
4935
|
view.input.compositionPendingChanges = 0;
|
package/package.json
CHANGED
package/src/clipboard.ts
CHANGED
|
@@ -44,10 +44,14 @@ export function parseFromClipboard(view: EditorView, text: string, html: string
|
|
|
44
44
|
let inCode = $context.parent.type.spec.code
|
|
45
45
|
let dom: HTMLElement | undefined, slice: Slice | undefined
|
|
46
46
|
if (!html && !text) return null
|
|
47
|
-
let asText = text && (plainText || inCode || !html)
|
|
47
|
+
let asText = !!text && (plainText || inCode || !html)
|
|
48
48
|
if (asText) {
|
|
49
49
|
view.someProp("transformPastedText", f => { text = f(text, inCode || plainText, view) })
|
|
50
|
-
if (inCode)
|
|
50
|
+
if (inCode) {
|
|
51
|
+
slice = new Slice(Fragment.from(view.state.schema.text(text.replace(/\r\n?/g, "\n"))), 0, 0)
|
|
52
|
+
view.someProp("transformPasted", f => { slice = f(slice!, view, true) })
|
|
53
|
+
return slice
|
|
54
|
+
}
|
|
51
55
|
let parsed = view.someProp("clipboardTextParser", f => f(text, $context, plainText, view))
|
|
52
56
|
if (parsed) {
|
|
53
57
|
slice = parsed
|
|
@@ -101,7 +105,7 @@ export function parseFromClipboard(view: EditorView, text: string, html: string
|
|
|
101
105
|
}
|
|
102
106
|
}
|
|
103
107
|
|
|
104
|
-
view.someProp("transformPasted", f => { slice = f(slice!, view) })
|
|
108
|
+
view.someProp("transformPasted", f => { slice = f(slice!, view, asText) })
|
|
105
109
|
return slice
|
|
106
110
|
}
|
|
107
111
|
|
package/src/domchange.ts
CHANGED
|
@@ -76,7 +76,7 @@ function ruleFromNode(dom: DOMNode): Omit<TagParseRule, "tag"> | null {
|
|
|
76
76
|
return null
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
const isInline = /^(a|abbr|acronym|b|bd[io]|big|br|button|cite|code|data(list)?|del|dfn|em|i|ins|kbd|label|map|mark|meter|output|q|ruby|s|samp|small|span|strong|su[bp]|time|u|tt|var)$/i
|
|
79
|
+
const isInline = /^(a|abbr|acronym|b|bd[io]|big|br|button|cite|code|data(list)?|del|dfn|em|i|img|ins|kbd|label|map|mark|meter|output|q|ruby|s|samp|small|span|strong|su[bp]|time|u|tt|var)$/i
|
|
80
80
|
|
|
81
81
|
export function readDOMChange(view: EditorView, from: number, to: number, typeOver: boolean, addedNodes: readonly DOMNode[]) {
|
|
82
82
|
let compositionID = view.input.compositionPendingChanges || (view.composing ? view.input.compositionID : 0)
|
package/src/domcoords.ts
CHANGED
|
@@ -232,7 +232,7 @@ function posFromCaret(view: EditorView, node: Node, offset: number, coords: {top
|
|
|
232
232
|
if (desc.dom.nodeType == 1 && (desc.node.isBlock && desc.parent || !desc.contentDOM) &&
|
|
233
233
|
// Ignore elements with zero-size bounding rectangles
|
|
234
234
|
((rect = (desc.dom as HTMLElement).getBoundingClientRect()).width || rect.height)) {
|
|
235
|
-
if (desc.node.isBlock && desc.parent) {
|
|
235
|
+
if (desc.node.isBlock && desc.parent && !/^T(R|BODY|HEAD|FOOT)$/.test(desc.dom!.nodeName)) {
|
|
236
236
|
// Only apply the horizontal test to the innermost block. Vertical for any parent.
|
|
237
237
|
if (!sawBlock && rect.left > coords.left || rect.top > coords.top) outsideBlock = desc.posBefore
|
|
238
238
|
else if (!sawBlock && rect.right < coords.left || rect.bottom < coords.top) outsideBlock = desc.posAfter
|
package/src/index.ts
CHANGED
|
@@ -714,8 +714,9 @@ export interface EditorProps<P = any> {
|
|
|
714
714
|
clipboardTextParser?: (this: P, text: string, $context: ResolvedPos, plain: boolean, view: EditorView) => Slice
|
|
715
715
|
|
|
716
716
|
/// Can be used to transform pasted or dragged-and-dropped content
|
|
717
|
-
/// before it is applied to the document.
|
|
718
|
-
|
|
717
|
+
/// before it is applied to the document. The `plain` flag will be
|
|
718
|
+
/// true when the text is pasted as plain text.
|
|
719
|
+
transformPasted?: (this: P, slice: Slice, view: EditorView, plain: boolean) => Slice
|
|
719
720
|
|
|
720
721
|
/// Can be used to transform copied or cut content before it is
|
|
721
722
|
/// serialized to the clipboard.
|
package/src/input.ts
CHANGED
|
@@ -718,7 +718,7 @@ editHandlers.drop = (view, _event) => {
|
|
|
718
718
|
let $mouse = view.state.doc.resolve(eventPos.pos)
|
|
719
719
|
let slice = dragging && dragging.slice
|
|
720
720
|
if (slice) {
|
|
721
|
-
view.someProp("transformPasted", f => { slice = f(slice!, view) })
|
|
721
|
+
view.someProp("transformPasted", f => { slice = f(slice!, view, false) })
|
|
722
722
|
} else {
|
|
723
723
|
slice = parseFromClipboard(view, getText(event.dataTransfer),
|
|
724
724
|
brokenClipboardAPI ? null : event.dataTransfer.getData("text/html"), false, $mouse)
|
package/src/selection.ts
CHANGED
|
@@ -147,14 +147,11 @@ function removeClassOnSelectionChange(view: EditorView) {
|
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
function selectCursorWrapper(view: EditorView) {
|
|
150
|
-
let domSel = view.domSelection()
|
|
150
|
+
let domSel = view.domSelection()
|
|
151
151
|
if (!domSel) return
|
|
152
152
|
let node = view.cursorWrapper!.dom, img = node.nodeName == "IMG"
|
|
153
|
-
if (img)
|
|
154
|
-
else
|
|
155
|
-
range.collapse(true)
|
|
156
|
-
domSel.removeAllRanges()
|
|
157
|
-
domSel.addRange(range)
|
|
153
|
+
if (img) domSel.collapse(node.parentNode!, domIndex(node) + 1)
|
|
154
|
+
else domSel.collapse(node, 0)
|
|
158
155
|
// Kludge to kill 'control selection' in IE11 when selecting an
|
|
159
156
|
// invisible cursor wrapper, since that would result in those weird
|
|
160
157
|
// resize handles and a selection that considers the absolutely
|
package/src/viewdesc.ts
CHANGED
|
@@ -460,7 +460,7 @@ export class ViewDesc {
|
|
|
460
460
|
// (one where the focus is before the anchor), but not all
|
|
461
461
|
// browsers support it yet.
|
|
462
462
|
let domSelExtended = false
|
|
463
|
-
if ((domSel.extend || anchor == head) && !brKludge) {
|
|
463
|
+
if ((domSel.extend || anchor == head) && !(brKludge && browser.gecko)) {
|
|
464
464
|
domSel.collapse(anchorDOM.node, anchorDOM.offset)
|
|
465
465
|
try {
|
|
466
466
|
if (anchor != head)
|