prosemirror-view 1.36.0 → 1.37.1
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 +18 -0
- package/dist/index.cjs +21 -15
- package/dist/index.d.cts +25 -13
- package/dist/index.d.ts +25 -13
- package/dist/index.js +26 -21
- package/package.json +1 -1
- package/src/README.md +2 -0
- package/src/domchange.ts +6 -6
- package/src/domcoords.ts +4 -3
- package/src/domobserver.ts +2 -2
- package/src/index.ts +1 -1
- package/src/input.ts +1 -1
- package/src/selection.ts +1 -1
- package/src/viewdesc.ts +40 -27
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
## 1.37.1 (2024-12-11)
|
|
2
|
+
|
|
3
|
+
### Bug fixes
|
|
4
|
+
|
|
5
|
+
Fix an issue where `display: content` elements could break `posAtCoords`.
|
|
6
|
+
|
|
7
|
+
Work around an issue with Chrome reporting the selection incorrectly during composition.
|
|
8
|
+
|
|
9
|
+
## 1.37.0 (2024-11-23)
|
|
10
|
+
|
|
11
|
+
### Bug fixes
|
|
12
|
+
|
|
13
|
+
Fix an issue on Safari where the (incorrect) top-level selection was used, instead of the workaround that also works inside shadow roots.
|
|
14
|
+
|
|
15
|
+
### New features
|
|
16
|
+
|
|
17
|
+
Mark views can now provide an `ignoreMutation` method. Add ViewMutationRecord to doc template
|
|
18
|
+
|
|
1
19
|
## 1.36.0 (2024-11-05)
|
|
2
20
|
|
|
3
21
|
### Bug fixes
|
package/dist/index.cjs
CHANGED
|
@@ -389,10 +389,10 @@ function posFromCaret(view, node, offset, coords) {
|
|
|
389
389
|
var outsideBlock = -1;
|
|
390
390
|
for (var cur = node, sawBlock = false;;) {
|
|
391
391
|
if (cur == view.dom) break;
|
|
392
|
-
var desc = view.docView.nearestDesc(cur, true)
|
|
392
|
+
var desc = view.docView.nearestDesc(cur, true),
|
|
393
|
+
rect = void 0;
|
|
393
394
|
if (!desc) return null;
|
|
394
|
-
if (desc.dom.nodeType == 1 && (desc.node.isBlock && desc.parent || !desc.contentDOM)) {
|
|
395
|
-
var rect = desc.dom.getBoundingClientRect();
|
|
395
|
+
if (desc.dom.nodeType == 1 && (desc.node.isBlock && desc.parent || !desc.contentDOM) && ((rect = desc.dom.getBoundingClientRect()).width || rect.height)) {
|
|
396
396
|
if (desc.node.isBlock && desc.parent) {
|
|
397
397
|
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;
|
|
398
398
|
sawBlock = true;
|
|
@@ -935,19 +935,20 @@ var ViewDesc = function () {
|
|
|
935
935
|
}
|
|
936
936
|
}, {
|
|
937
937
|
key: "setSelection",
|
|
938
|
-
value: function setSelection(anchor, head,
|
|
938
|
+
value: function setSelection(anchor, head, view) {
|
|
939
939
|
var force = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
|
|
940
940
|
var from = Math.min(anchor, head),
|
|
941
941
|
to = Math.max(anchor, head);
|
|
942
942
|
for (var i = 0, offset = 0; i < this.children.length; i++) {
|
|
943
943
|
var child = this.children[i],
|
|
944
944
|
end = offset + child.size;
|
|
945
|
-
if (from > offset && to < end) return child.setSelection(anchor - offset - child.border, head - offset - child.border,
|
|
945
|
+
if (from > offset && to < end) return child.setSelection(anchor - offset - child.border, head - offset - child.border, view, force);
|
|
946
946
|
offset = end;
|
|
947
947
|
}
|
|
948
948
|
var anchorDOM = this.domFromPos(anchor, anchor ? -1 : 1);
|
|
949
949
|
var headDOM = head == anchor ? anchorDOM : this.domFromPos(head, head ? -1 : 1);
|
|
950
|
-
var domSel = root.getSelection();
|
|
950
|
+
var domSel = view.root.getSelection();
|
|
951
|
+
var selRange = view.domSelectionRange();
|
|
951
952
|
var brKludge = false;
|
|
952
953
|
if ((gecko || safari) && anchor == head) {
|
|
953
954
|
var _anchorDOM = anchorDOM,
|
|
@@ -973,11 +974,11 @@ var ViewDesc = function () {
|
|
|
973
974
|
brKludge = prev && (prev.nodeName == "BR" || prev.contentEditable == "false");
|
|
974
975
|
}
|
|
975
976
|
}
|
|
976
|
-
if (gecko &&
|
|
977
|
-
var _after2 =
|
|
977
|
+
if (gecko && selRange.focusNode && selRange.focusNode != headDOM.node && selRange.focusNode.nodeType == 1) {
|
|
978
|
+
var _after2 = selRange.focusNode.childNodes[selRange.focusOffset];
|
|
978
979
|
if (_after2 && _after2.contentEditable == "false") force = true;
|
|
979
980
|
}
|
|
980
|
-
if (!(force || brKludge && safari) && isEquivalentPosition(anchorDOM.node, anchorDOM.offset,
|
|
981
|
+
if (!(force || brKludge && safari) && isEquivalentPosition(anchorDOM.node, anchorDOM.offset, selRange.anchorNode, selRange.anchorOffset) && isEquivalentPosition(headDOM.node, headDOM.offset, selRange.focusNode, selRange.focusOffset)) return;
|
|
981
982
|
var domSelExtended = false;
|
|
982
983
|
if ((domSel.extend || anchor == head) && !brKludge) {
|
|
983
984
|
domSel.collapse(anchorDOM.node, anchorDOM.offset);
|
|
@@ -1213,6 +1214,11 @@ var MarkViewDesc = function (_ViewDesc3) {
|
|
|
1213
1214
|
copy.children = nodes;
|
|
1214
1215
|
return copy;
|
|
1215
1216
|
}
|
|
1217
|
+
}, {
|
|
1218
|
+
key: "ignoreMutation",
|
|
1219
|
+
value: function ignoreMutation(mutation) {
|
|
1220
|
+
return this.spec.ignoreMutation ? this.spec.ignoreMutation(mutation) : _get(_getPrototypeOf(MarkViewDesc.prototype), "ignoreMutation", this).call(this, mutation);
|
|
1221
|
+
}
|
|
1216
1222
|
}, {
|
|
1217
1223
|
key: "destroy",
|
|
1218
1224
|
value: function destroy() {
|
|
@@ -1593,8 +1599,8 @@ var CustomNodeViewDesc = function (_NodeViewDesc2) {
|
|
|
1593
1599
|
}
|
|
1594
1600
|
}, {
|
|
1595
1601
|
key: "setSelection",
|
|
1596
|
-
value: function setSelection(anchor, head,
|
|
1597
|
-
this.spec.setSelection ? this.spec.setSelection(anchor, head, root) : _get(_getPrototypeOf(CustomNodeViewDesc.prototype), "setSelection", this).call(this, anchor, head,
|
|
1602
|
+
value: function setSelection(anchor, head, view, force) {
|
|
1603
|
+
this.spec.setSelection ? this.spec.setSelection(anchor, head, view.root) : _get(_getPrototypeOf(CustomNodeViewDesc.prototype), "setSelection", this).call(this, anchor, head, view, force);
|
|
1598
1604
|
}
|
|
1599
1605
|
}, {
|
|
1600
1606
|
key: "destroy",
|
|
@@ -2179,7 +2185,7 @@ function selectionToDOM(view) {
|
|
|
2179
2185
|
if (!sel.$from.parent.inlineContent) resetEditableFrom = temporarilyEditableNear(view, sel.from);
|
|
2180
2186
|
if (!sel.empty && !sel.$from.parent.inlineContent) resetEditableTo = temporarilyEditableNear(view, sel.to);
|
|
2181
2187
|
}
|
|
2182
|
-
view.docView.setSelection(anchor, head, view
|
|
2188
|
+
view.docView.setSelection(anchor, head, view, force);
|
|
2183
2189
|
if (brokenSelectBetweenUneditable) {
|
|
2184
2190
|
if (resetEditableFrom) resetEditable(resetEditableFrom);
|
|
2185
2191
|
if (resetEditableTo) resetEditable(resetEditableTo);
|
|
@@ -2868,7 +2874,7 @@ var InputState = _createClass(function InputState() {
|
|
|
2868
2874
|
this.lastIOSEnterFallbackTimeout = -1;
|
|
2869
2875
|
this.lastFocus = 0;
|
|
2870
2876
|
this.lastTouch = 0;
|
|
2871
|
-
this.
|
|
2877
|
+
this.lastChromeDelete = 0;
|
|
2872
2878
|
this.composing = false;
|
|
2873
2879
|
this.compositionNode = null;
|
|
2874
2880
|
this.composingTimeout = -1;
|
|
@@ -4655,7 +4661,7 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
|
|
|
4655
4661
|
if (android && chrome) view.domObserver.suppressSelectionUpdates();
|
|
4656
4662
|
return;
|
|
4657
4663
|
}
|
|
4658
|
-
if (chrome &&
|
|
4664
|
+
if (chrome && change.endB == change.start) view.input.lastChromeDelete = Date.now();
|
|
4659
4665
|
if (android && !inlineChange && $from.start() != $to.start() && $to.parentOffset == 0 && $from.depth == $to.depth && parse.sel && parse.sel.anchor == parse.sel.head && parse.sel.head == change.endA) {
|
|
4660
4666
|
change.endB -= 2;
|
|
4661
4667
|
$to = parse.doc.resolveNoCache(change.endB - parse.from);
|
|
@@ -4692,7 +4698,7 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
|
|
|
4692
4698
|
if (!tr) tr = view.state.tr.replace(chFrom, chTo, parse.doc.slice(change.start - parse.from, change.endB - parse.from));
|
|
4693
4699
|
if (parse.sel) {
|
|
4694
4700
|
var _sel3 = resolveSelection(view, tr.doc, parse.sel);
|
|
4695
|
-
if (_sel3 && !(chrome &&
|
|
4701
|
+
if (_sel3 && !(chrome && view.composing && _sel3.empty && (change.start != change.endB || view.input.lastChromeDelete < Date.now() - 100) && (_sel3.head == chFrom || _sel3.head == tr.mapping.map(chTo) - 1) || ie && _sel3.empty && _sel3.head == chFrom)) tr.setSelection(_sel3);
|
|
4696
4702
|
}
|
|
4697
4703
|
if (storedMarks) tr.ensureMarks(storedMarks);
|
|
4698
4704
|
if (compositionID) tr.setMeta("composition", compositionID);
|
package/dist/index.d.cts
CHANGED
|
@@ -238,6 +238,17 @@ declare global {
|
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
240
|
/**
|
|
241
|
+
A ViewMutationRecord represents a DOM
|
|
242
|
+
[mutation](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver)
|
|
243
|
+
or a selection change happens within the view. When the change is
|
|
244
|
+
a selection change, the record will have a `type` property of
|
|
245
|
+
`"selection"` (which doesn't occur for native mutation records).
|
|
246
|
+
*/
|
|
247
|
+
type ViewMutationRecord = MutationRecord | {
|
|
248
|
+
type: "selection";
|
|
249
|
+
target: DOMNode;
|
|
250
|
+
};
|
|
251
|
+
/**
|
|
241
252
|
By default, document nodes are rendered using the result of the
|
|
242
253
|
[`toDOM`](https://prosemirror.net/docs/ref/#model.NodeSpec.toDOM) method of their spec, and managed
|
|
243
254
|
entirely by the editor. For some use cases, such as embedded
|
|
@@ -308,16 +319,11 @@ interface NodeView {
|
|
|
308
319
|
*/
|
|
309
320
|
stopEvent?: (event: Event) => boolean;
|
|
310
321
|
/**
|
|
311
|
-
Called when a
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
a selection change, the record will have a `type` property of
|
|
315
|
-
`"selection"` (which doesn't occur for native mutation records).
|
|
316
|
-
Return false if the editor should re-read the selection or
|
|
317
|
-
re-parse the range around the mutation, true if it can safely be
|
|
318
|
-
ignored.
|
|
322
|
+
Called when a [mutation](https://prosemirror.net/docs/ref/#view.ViewMutationRecord) happens within the
|
|
323
|
+
view. Return false if the editor should re-read the selection or re-parse
|
|
324
|
+
the range around the mutation, true if it can safely be ignored.
|
|
319
325
|
*/
|
|
320
|
-
ignoreMutation?: (mutation:
|
|
326
|
+
ignoreMutation?: (mutation: ViewMutationRecord) => boolean;
|
|
321
327
|
/**
|
|
322
328
|
Called when the node view is removed from the editor or the whole
|
|
323
329
|
editor is destroyed.
|
|
@@ -344,6 +350,12 @@ interface MarkView {
|
|
|
344
350
|
*/
|
|
345
351
|
contentDOM?: HTMLElement | null;
|
|
346
352
|
/**
|
|
353
|
+
Called when a [mutation](https://prosemirror.net/docs/ref/#view.ViewMutationRecord) happens within the
|
|
354
|
+
view. Return false if the editor should re-read the selection or re-parse
|
|
355
|
+
the range around the mutation, true if it can safely be ignored.
|
|
356
|
+
*/
|
|
357
|
+
ignoreMutation?: (mutation: ViewMutationRecord) => boolean;
|
|
358
|
+
/**
|
|
347
359
|
Called when the mark view is removed from the editor or the whole
|
|
348
360
|
editor is destroyed.
|
|
349
361
|
*/
|
|
@@ -391,8 +403,8 @@ declare class ViewDesc {
|
|
|
391
403
|
};
|
|
392
404
|
emptyChildAt(side: number): boolean;
|
|
393
405
|
domAfterPos(pos: number): DOMNode;
|
|
394
|
-
setSelection(anchor: number, head: number,
|
|
395
|
-
ignoreMutation(mutation:
|
|
406
|
+
setSelection(anchor: number, head: number, view: EditorView, force?: boolean): void;
|
|
407
|
+
ignoreMutation(mutation: ViewMutationRecord): boolean;
|
|
396
408
|
get contentLost(): boolean | null;
|
|
397
409
|
markDirty(from: number, to: number): void;
|
|
398
410
|
markParentsDirty(): void;
|
|
@@ -441,7 +453,7 @@ declare class TextViewDesc extends NodeViewDesc {
|
|
|
441
453
|
offset: number;
|
|
442
454
|
};
|
|
443
455
|
localPosFromDOM(dom: DOMNode, offset: number, bias: number): number;
|
|
444
|
-
ignoreMutation(mutation:
|
|
456
|
+
ignoreMutation(mutation: ViewMutationRecord): boolean;
|
|
445
457
|
slice(from: number, to: number, view: EditorView): TextViewDesc;
|
|
446
458
|
markDirty(from: number, to: number): void;
|
|
447
459
|
get domAtom(): boolean;
|
|
@@ -955,4 +967,4 @@ interface DirectEditorProps extends EditorProps {
|
|
|
955
967
|
dispatchTransaction?: (tr: Transaction) => void;
|
|
956
968
|
}
|
|
957
969
|
|
|
958
|
-
export { type DOMEventMap, Decoration, type DecorationAttrs, DecorationSet, type DecorationSource, type DirectEditorProps, type EditorProps, EditorView, type MarkView, type MarkViewConstructor, type NodeView, type NodeViewConstructor };
|
|
970
|
+
export { type DOMEventMap, Decoration, type DecorationAttrs, DecorationSet, type DecorationSource, type DirectEditorProps, type EditorProps, EditorView, type MarkView, type MarkViewConstructor, type NodeView, type NodeViewConstructor, type ViewMutationRecord };
|
package/dist/index.d.ts
CHANGED
|
@@ -238,6 +238,17 @@ declare global {
|
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
240
|
/**
|
|
241
|
+
A ViewMutationRecord represents a DOM
|
|
242
|
+
[mutation](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver)
|
|
243
|
+
or a selection change happens within the view. When the change is
|
|
244
|
+
a selection change, the record will have a `type` property of
|
|
245
|
+
`"selection"` (which doesn't occur for native mutation records).
|
|
246
|
+
*/
|
|
247
|
+
type ViewMutationRecord = MutationRecord | {
|
|
248
|
+
type: "selection";
|
|
249
|
+
target: DOMNode;
|
|
250
|
+
};
|
|
251
|
+
/**
|
|
241
252
|
By default, document nodes are rendered using the result of the
|
|
242
253
|
[`toDOM`](https://prosemirror.net/docs/ref/#model.NodeSpec.toDOM) method of their spec, and managed
|
|
243
254
|
entirely by the editor. For some use cases, such as embedded
|
|
@@ -308,16 +319,11 @@ interface NodeView {
|
|
|
308
319
|
*/
|
|
309
320
|
stopEvent?: (event: Event) => boolean;
|
|
310
321
|
/**
|
|
311
|
-
Called when a
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
a selection change, the record will have a `type` property of
|
|
315
|
-
`"selection"` (which doesn't occur for native mutation records).
|
|
316
|
-
Return false if the editor should re-read the selection or
|
|
317
|
-
re-parse the range around the mutation, true if it can safely be
|
|
318
|
-
ignored.
|
|
322
|
+
Called when a [mutation](https://prosemirror.net/docs/ref/#view.ViewMutationRecord) happens within the
|
|
323
|
+
view. Return false if the editor should re-read the selection or re-parse
|
|
324
|
+
the range around the mutation, true if it can safely be ignored.
|
|
319
325
|
*/
|
|
320
|
-
ignoreMutation?: (mutation:
|
|
326
|
+
ignoreMutation?: (mutation: ViewMutationRecord) => boolean;
|
|
321
327
|
/**
|
|
322
328
|
Called when the node view is removed from the editor or the whole
|
|
323
329
|
editor is destroyed.
|
|
@@ -344,6 +350,12 @@ interface MarkView {
|
|
|
344
350
|
*/
|
|
345
351
|
contentDOM?: HTMLElement | null;
|
|
346
352
|
/**
|
|
353
|
+
Called when a [mutation](https://prosemirror.net/docs/ref/#view.ViewMutationRecord) happens within the
|
|
354
|
+
view. Return false if the editor should re-read the selection or re-parse
|
|
355
|
+
the range around the mutation, true if it can safely be ignored.
|
|
356
|
+
*/
|
|
357
|
+
ignoreMutation?: (mutation: ViewMutationRecord) => boolean;
|
|
358
|
+
/**
|
|
347
359
|
Called when the mark view is removed from the editor or the whole
|
|
348
360
|
editor is destroyed.
|
|
349
361
|
*/
|
|
@@ -391,8 +403,8 @@ declare class ViewDesc {
|
|
|
391
403
|
};
|
|
392
404
|
emptyChildAt(side: number): boolean;
|
|
393
405
|
domAfterPos(pos: number): DOMNode;
|
|
394
|
-
setSelection(anchor: number, head: number,
|
|
395
|
-
ignoreMutation(mutation:
|
|
406
|
+
setSelection(anchor: number, head: number, view: EditorView, force?: boolean): void;
|
|
407
|
+
ignoreMutation(mutation: ViewMutationRecord): boolean;
|
|
396
408
|
get contentLost(): boolean | null;
|
|
397
409
|
markDirty(from: number, to: number): void;
|
|
398
410
|
markParentsDirty(): void;
|
|
@@ -441,7 +453,7 @@ declare class TextViewDesc extends NodeViewDesc {
|
|
|
441
453
|
offset: number;
|
|
442
454
|
};
|
|
443
455
|
localPosFromDOM(dom: DOMNode, offset: number, bias: number): number;
|
|
444
|
-
ignoreMutation(mutation:
|
|
456
|
+
ignoreMutation(mutation: ViewMutationRecord): boolean;
|
|
445
457
|
slice(from: number, to: number, view: EditorView): TextViewDesc;
|
|
446
458
|
markDirty(from: number, to: number): void;
|
|
447
459
|
get domAtom(): boolean;
|
|
@@ -955,4 +967,4 @@ interface DirectEditorProps extends EditorProps {
|
|
|
955
967
|
dispatchTransaction?: (tr: Transaction) => void;
|
|
956
968
|
}
|
|
957
969
|
|
|
958
|
-
export { type DOMEventMap, Decoration, type DecorationAttrs, DecorationSet, type DecorationSource, type DirectEditorProps, type EditorProps, EditorView, type MarkView, type MarkViewConstructor, type NodeView, type NodeViewConstructor };
|
|
970
|
+
export { type DOMEventMap, Decoration, type DecorationAttrs, DecorationSet, type DecorationSource, type DirectEditorProps, type EditorProps, EditorView, type MarkView, type MarkViewConstructor, type NodeView, type NodeViewConstructor, type ViewMutationRecord };
|
package/dist/index.js
CHANGED
|
@@ -396,11 +396,12 @@ function posFromCaret(view, node, offset, coords) {
|
|
|
396
396
|
for (let cur = node, sawBlock = false;;) {
|
|
397
397
|
if (cur == view.dom)
|
|
398
398
|
break;
|
|
399
|
-
let desc = view.docView.nearestDesc(cur, true);
|
|
399
|
+
let desc = view.docView.nearestDesc(cur, true), rect;
|
|
400
400
|
if (!desc)
|
|
401
401
|
return null;
|
|
402
|
-
if (desc.dom.nodeType == 1 && (desc.node.isBlock && desc.parent || !desc.contentDOM)
|
|
403
|
-
|
|
402
|
+
if (desc.dom.nodeType == 1 && (desc.node.isBlock && desc.parent || !desc.contentDOM) &&
|
|
403
|
+
// Ignore elements with zero-size bounding rectangles
|
|
404
|
+
((rect = desc.dom.getBoundingClientRect()).width || rect.height)) {
|
|
404
405
|
if (desc.node.isBlock && desc.parent) {
|
|
405
406
|
// Only apply the horizontal test to the innermost block. Vertical for any parent.
|
|
406
407
|
if (!sawBlock && rect.left > coords.left || rect.top > coords.top)
|
|
@@ -996,18 +997,19 @@ class ViewDesc {
|
|
|
996
997
|
// custom things with the selection. Note that this falls apart when
|
|
997
998
|
// a selection starts in such a node and ends in another, in which
|
|
998
999
|
// case we just use whatever domFromPos produces as a best effort.
|
|
999
|
-
setSelection(anchor, head,
|
|
1000
|
+
setSelection(anchor, head, view, force = false) {
|
|
1000
1001
|
// If the selection falls entirely in a child, give it to that child
|
|
1001
1002
|
let from = Math.min(anchor, head), to = Math.max(anchor, head);
|
|
1002
1003
|
for (let i = 0, offset = 0; i < this.children.length; i++) {
|
|
1003
1004
|
let child = this.children[i], end = offset + child.size;
|
|
1004
1005
|
if (from > offset && to < end)
|
|
1005
|
-
return child.setSelection(anchor - offset - child.border, head - offset - child.border,
|
|
1006
|
+
return child.setSelection(anchor - offset - child.border, head - offset - child.border, view, force);
|
|
1006
1007
|
offset = end;
|
|
1007
1008
|
}
|
|
1008
1009
|
let anchorDOM = this.domFromPos(anchor, anchor ? -1 : 1);
|
|
1009
1010
|
let headDOM = head == anchor ? anchorDOM : this.domFromPos(head, head ? -1 : 1);
|
|
1010
|
-
let domSel = root.getSelection();
|
|
1011
|
+
let domSel = view.root.getSelection();
|
|
1012
|
+
let selRange = view.domSelectionRange();
|
|
1011
1013
|
let brKludge = false;
|
|
1012
1014
|
// On Firefox, using Selection.collapse to put the cursor after a
|
|
1013
1015
|
// BR node for some reason doesn't always work (#1073). On Safari,
|
|
@@ -1038,14 +1040,14 @@ class ViewDesc {
|
|
|
1038
1040
|
}
|
|
1039
1041
|
// Firefox can act strangely when the selection is in front of an
|
|
1040
1042
|
// uneditable node. See #1163 and https://bugzilla.mozilla.org/show_bug.cgi?id=1709536
|
|
1041
|
-
if (gecko &&
|
|
1042
|
-
let after =
|
|
1043
|
+
if (gecko && selRange.focusNode && selRange.focusNode != headDOM.node && selRange.focusNode.nodeType == 1) {
|
|
1044
|
+
let after = selRange.focusNode.childNodes[selRange.focusOffset];
|
|
1043
1045
|
if (after && after.contentEditable == "false")
|
|
1044
1046
|
force = true;
|
|
1045
1047
|
}
|
|
1046
1048
|
if (!(force || brKludge && safari) &&
|
|
1047
|
-
isEquivalentPosition(anchorDOM.node, anchorDOM.offset,
|
|
1048
|
-
isEquivalentPosition(headDOM.node, headDOM.offset,
|
|
1049
|
+
isEquivalentPosition(anchorDOM.node, anchorDOM.offset, selRange.anchorNode, selRange.anchorOffset) &&
|
|
1050
|
+
isEquivalentPosition(headDOM.node, headDOM.offset, selRange.focusNode, selRange.focusOffset))
|
|
1049
1051
|
return;
|
|
1050
1052
|
// Selection.extend can be used to create an 'inverted' selection
|
|
1051
1053
|
// (one where the focus is before the anchor), but not all
|
|
@@ -1234,6 +1236,9 @@ class MarkViewDesc extends ViewDesc {
|
|
|
1234
1236
|
copy.children = nodes;
|
|
1235
1237
|
return copy;
|
|
1236
1238
|
}
|
|
1239
|
+
ignoreMutation(mutation) {
|
|
1240
|
+
return this.spec.ignoreMutation ? this.spec.ignoreMutation(mutation) : super.ignoreMutation(mutation);
|
|
1241
|
+
}
|
|
1237
1242
|
destroy() {
|
|
1238
1243
|
if (this.spec.destroy)
|
|
1239
1244
|
this.spec.destroy();
|
|
@@ -1577,9 +1582,9 @@ class CustomNodeViewDesc extends NodeViewDesc {
|
|
|
1577
1582
|
deselectNode() {
|
|
1578
1583
|
this.spec.deselectNode ? this.spec.deselectNode() : super.deselectNode();
|
|
1579
1584
|
}
|
|
1580
|
-
setSelection(anchor, head,
|
|
1581
|
-
this.spec.setSelection ? this.spec.setSelection(anchor, head, root)
|
|
1582
|
-
: super.setSelection(anchor, head,
|
|
1585
|
+
setSelection(anchor, head, view, force) {
|
|
1586
|
+
this.spec.setSelection ? this.spec.setSelection(anchor, head, view.root)
|
|
1587
|
+
: super.setSelection(anchor, head, view, force);
|
|
1583
1588
|
}
|
|
1584
1589
|
destroy() {
|
|
1585
1590
|
if (this.spec.destroy)
|
|
@@ -2239,7 +2244,7 @@ function selectionToDOM(view, force = false) {
|
|
|
2239
2244
|
if (!sel.empty && !sel.$from.parent.inlineContent)
|
|
2240
2245
|
resetEditableTo = temporarilyEditableNear(view, sel.to);
|
|
2241
2246
|
}
|
|
2242
|
-
view.docView.setSelection(anchor, head, view
|
|
2247
|
+
view.docView.setSelection(anchor, head, view, force);
|
|
2243
2248
|
if (brokenSelectBetweenUneditable) {
|
|
2244
2249
|
if (resetEditableFrom)
|
|
2245
2250
|
resetEditable(resetEditableFrom);
|
|
@@ -3038,7 +3043,7 @@ class InputState {
|
|
|
3038
3043
|
this.lastIOSEnterFallbackTimeout = -1;
|
|
3039
3044
|
this.lastFocus = 0;
|
|
3040
3045
|
this.lastTouch = 0;
|
|
3041
|
-
this.
|
|
3046
|
+
this.lastChromeDelete = 0;
|
|
3042
3047
|
this.composing = false;
|
|
3043
3048
|
this.compositionNode = null;
|
|
3044
3049
|
this.composingTimeout = -1;
|
|
@@ -5016,11 +5021,11 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
|
|
|
5016
5021
|
view.domObserver.suppressSelectionUpdates(); // #820
|
|
5017
5022
|
return;
|
|
5018
5023
|
}
|
|
5019
|
-
// Chrome
|
|
5024
|
+
// Chrome will occasionally, during composition, delete the
|
|
5020
5025
|
// entire composition and then immediately insert it again. This is
|
|
5021
5026
|
// used to detect that situation.
|
|
5022
|
-
if (chrome &&
|
|
5023
|
-
view.input.
|
|
5027
|
+
if (chrome && change.endB == change.start)
|
|
5028
|
+
view.input.lastChromeDelete = Date.now();
|
|
5024
5029
|
// This tries to detect Android virtual keyboard
|
|
5025
5030
|
// enter-and-pick-suggestion action. That sometimes (see issue
|
|
5026
5031
|
// #1059) first fires a DOM mutation, before moving the selection to
|
|
@@ -5071,13 +5076,13 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
|
|
|
5071
5076
|
tr = view.state.tr.replace(chFrom, chTo, parse.doc.slice(change.start - parse.from, change.endB - parse.from));
|
|
5072
5077
|
if (parse.sel) {
|
|
5073
5078
|
let sel = resolveSelection(view, tr.doc, parse.sel);
|
|
5074
|
-
// Chrome
|
|
5079
|
+
// Chrome will sometimes, during composition, report the
|
|
5075
5080
|
// selection in the wrong place. If it looks like that is
|
|
5076
5081
|
// happening, don't update the selection.
|
|
5077
5082
|
// Edge just doesn't move the cursor forward when you start typing
|
|
5078
5083
|
// in an empty block or between br nodes.
|
|
5079
|
-
if (sel && !(chrome &&
|
|
5080
|
-
(change.start != change.endB || view.input.
|
|
5084
|
+
if (sel && !(chrome && view.composing && sel.empty &&
|
|
5085
|
+
(change.start != change.endB || view.input.lastChromeDelete < Date.now() - 100) &&
|
|
5081
5086
|
(sel.head == chFrom || sel.head == tr.mapping.map(chTo) - 1) ||
|
|
5082
5087
|
ie && sel.empty && sel.head == chFrom))
|
|
5083
5088
|
tr.setSelection(sel);
|
package/package.json
CHANGED
package/src/README.md
CHANGED
package/src/domchange.ts
CHANGED
|
@@ -196,11 +196,11 @@ export function readDOMChange(view: EditorView, from: number, to: number, typeOv
|
|
|
196
196
|
return
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
-
// Chrome
|
|
199
|
+
// Chrome will occasionally, during composition, delete the
|
|
200
200
|
// entire composition and then immediately insert it again. This is
|
|
201
201
|
// used to detect that situation.
|
|
202
|
-
if (browser.chrome &&
|
|
203
|
-
view.input.
|
|
202
|
+
if (browser.chrome && change.endB == change.start)
|
|
203
|
+
view.input.lastChromeDelete = Date.now()
|
|
204
204
|
|
|
205
205
|
// This tries to detect Android virtual keyboard
|
|
206
206
|
// enter-and-pick-suggestion action. That sometimes (see issue
|
|
@@ -252,13 +252,13 @@ export function readDOMChange(view: EditorView, from: number, to: number, typeOv
|
|
|
252
252
|
tr = view.state.tr.replace(chFrom, chTo, parse.doc.slice(change.start - parse.from, change.endB - parse.from))
|
|
253
253
|
if (parse.sel) {
|
|
254
254
|
let sel = resolveSelection(view, tr.doc, parse.sel)
|
|
255
|
-
// Chrome
|
|
255
|
+
// Chrome will sometimes, during composition, report the
|
|
256
256
|
// selection in the wrong place. If it looks like that is
|
|
257
257
|
// happening, don't update the selection.
|
|
258
258
|
// Edge just doesn't move the cursor forward when you start typing
|
|
259
259
|
// in an empty block or between br nodes.
|
|
260
|
-
if (sel && !(browser.chrome &&
|
|
261
|
-
(change.start != change.endB || view.input.
|
|
260
|
+
if (sel && !(browser.chrome && view.composing && sel.empty &&
|
|
261
|
+
(change.start != change.endB || view.input.lastChromeDelete < Date.now() - 100) &&
|
|
262
262
|
(sel.head == chFrom || sel.head == tr.mapping.map(chTo) - 1) ||
|
|
263
263
|
browser.ie && sel.empty && sel.head == chFrom))
|
|
264
264
|
tr.setSelection(sel)
|
package/src/domcoords.ts
CHANGED
|
@@ -225,10 +225,11 @@ function posFromCaret(view: EditorView, node: Node, offset: number, coords: {top
|
|
|
225
225
|
let outsideBlock = -1
|
|
226
226
|
for (let cur = node, sawBlock = false;;) {
|
|
227
227
|
if (cur == view.dom) break
|
|
228
|
-
let desc = view.docView.nearestDesc(cur, true)
|
|
228
|
+
let desc = view.docView.nearestDesc(cur, true), rect
|
|
229
229
|
if (!desc) return null
|
|
230
|
-
if (desc.dom.nodeType == 1 && (desc.node.isBlock && desc.parent || !desc.contentDOM)
|
|
231
|
-
|
|
230
|
+
if (desc.dom.nodeType == 1 && (desc.node.isBlock && desc.parent || !desc.contentDOM) &&
|
|
231
|
+
// Ignore elements with zero-size bounding rectangles
|
|
232
|
+
((rect = (desc.dom as HTMLElement).getBoundingClientRect()).width || rect.height)) {
|
|
232
233
|
if (desc.node.isBlock && desc.parent) {
|
|
233
234
|
// Only apply the horizontal test to the innermost block. Vertical for any parent.
|
|
234
235
|
if (!sawBlock && rect.left > coords.left || rect.top > coords.top) outsideBlock = desc.posBefore
|
package/src/domobserver.ts
CHANGED
|
@@ -151,8 +151,8 @@ export class DOMObserver {
|
|
|
151
151
|
let desc = container && this.view.docView.nearestDesc(container)
|
|
152
152
|
if (desc && desc.ignoreMutation({
|
|
153
153
|
type: "selection",
|
|
154
|
-
target: container!.nodeType == 3 ? container!.parentNode : container
|
|
155
|
-
}
|
|
154
|
+
target: container!.nodeType == 3 ? container!.parentNode! : container!
|
|
155
|
+
})) {
|
|
156
156
|
this.setCurSelection()
|
|
157
157
|
return true
|
|
158
158
|
}
|
package/src/index.ts
CHANGED
|
@@ -14,7 +14,7 @@ import {DOMSelection, DOMNode, DOMSelectionRange, deepActiveElement, clearReused
|
|
|
14
14
|
import * as browser from "./browser"
|
|
15
15
|
|
|
16
16
|
export {Decoration, DecorationSet, DecorationAttrs, DecorationSource} from "./decoration"
|
|
17
|
-
export {NodeView, MarkView} from "./viewdesc"
|
|
17
|
+
export {NodeView, MarkView, ViewMutationRecord} from "./viewdesc"
|
|
18
18
|
|
|
19
19
|
// Exported for testing
|
|
20
20
|
import {serializeForClipboard, parseFromClipboard} from "./clipboard"
|
package/src/input.ts
CHANGED
package/src/selection.ts
CHANGED
|
@@ -84,7 +84,7 @@ export function selectionToDOM(view: EditorView, force = false) {
|
|
|
84
84
|
if (!sel.empty && !sel.$from.parent.inlineContent)
|
|
85
85
|
resetEditableTo = temporarilyEditableNear(view, sel.to)
|
|
86
86
|
}
|
|
87
|
-
view.docView.setSelection(anchor, head, view
|
|
87
|
+
view.docView.setSelection(anchor, head, view, force)
|
|
88
88
|
if (brokenSelectBetweenUneditable) {
|
|
89
89
|
if (resetEditableFrom) resetEditable(resetEditableFrom)
|
|
90
90
|
if (resetEditableTo) resetEditable(resetEditableTo)
|
package/src/viewdesc.ts
CHANGED
|
@@ -10,6 +10,13 @@ declare global {
|
|
|
10
10
|
interface Node { pmViewDesc?: ViewDesc }
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
/// A ViewMutationRecord represents a DOM
|
|
14
|
+
/// [mutation](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver)
|
|
15
|
+
/// or a selection change happens within the view. When the change is
|
|
16
|
+
/// a selection change, the record will have a `type` property of
|
|
17
|
+
/// `"selection"` (which doesn't occur for native mutation records).
|
|
18
|
+
export type ViewMutationRecord = MutationRecord | { type: "selection", target: DOMNode }
|
|
19
|
+
|
|
13
20
|
/// By default, document nodes are rendered using the result of the
|
|
14
21
|
/// [`toDOM`](#model.NodeSpec.toDOM) method of their spec, and managed
|
|
15
22
|
/// entirely by the editor. For some use cases, such as embedded
|
|
@@ -70,15 +77,10 @@ export interface NodeView {
|
|
|
70
77
|
/// which this returns true are not handled by the editor.
|
|
71
78
|
stopEvent?: (event: Event) => boolean
|
|
72
79
|
|
|
73
|
-
/// Called when a
|
|
74
|
-
///
|
|
75
|
-
///
|
|
76
|
-
|
|
77
|
-
/// `"selection"` (which doesn't occur for native mutation records).
|
|
78
|
-
/// Return false if the editor should re-read the selection or
|
|
79
|
-
/// re-parse the range around the mutation, true if it can safely be
|
|
80
|
-
/// ignored.
|
|
81
|
-
ignoreMutation?: (mutation: MutationRecord) => boolean
|
|
80
|
+
/// Called when a [mutation](#view.ViewMutationRecord) happens within the
|
|
81
|
+
/// view. Return false if the editor should re-read the selection or re-parse
|
|
82
|
+
/// the range around the mutation, true if it can safely be ignored.
|
|
83
|
+
ignoreMutation?: (mutation: ViewMutationRecord) => boolean
|
|
82
84
|
|
|
83
85
|
/// Called when the node view is removed from the editor or the whole
|
|
84
86
|
/// editor is destroyed.
|
|
@@ -99,6 +101,12 @@ export interface MarkView {
|
|
|
99
101
|
/// The DOM node that should hold the mark's content. When this is not
|
|
100
102
|
/// present, the `dom` property is used as the content DOM.
|
|
101
103
|
contentDOM?: HTMLElement | null
|
|
104
|
+
|
|
105
|
+
/// Called when a [mutation](#view.ViewMutationRecord) happens within the
|
|
106
|
+
/// view. Return false if the editor should re-read the selection or re-parse
|
|
107
|
+
/// the range around the mutation, true if it can safely be ignored.
|
|
108
|
+
ignoreMutation?: (mutation: ViewMutationRecord) => boolean
|
|
109
|
+
|
|
102
110
|
|
|
103
111
|
/// Called when the mark view is removed from the editor or the whole
|
|
104
112
|
/// editor is destroyed.
|
|
@@ -387,19 +395,20 @@ export class ViewDesc {
|
|
|
387
395
|
// custom things with the selection. Note that this falls apart when
|
|
388
396
|
// a selection starts in such a node and ends in another, in which
|
|
389
397
|
// case we just use whatever domFromPos produces as a best effort.
|
|
390
|
-
setSelection(anchor: number, head: number,
|
|
398
|
+
setSelection(anchor: number, head: number, view: EditorView, force = false): void {
|
|
391
399
|
// If the selection falls entirely in a child, give it to that child
|
|
392
400
|
let from = Math.min(anchor, head), to = Math.max(anchor, head)
|
|
393
401
|
for (let i = 0, offset = 0; i < this.children.length; i++) {
|
|
394
402
|
let child = this.children[i], end = offset + child.size
|
|
395
403
|
if (from > offset && to < end)
|
|
396
|
-
return child.setSelection(anchor - offset - child.border, head - offset - child.border,
|
|
404
|
+
return child.setSelection(anchor - offset - child.border, head - offset - child.border, view, force)
|
|
397
405
|
offset = end
|
|
398
406
|
}
|
|
399
407
|
|
|
400
408
|
let anchorDOM = this.domFromPos(anchor, anchor ? -1 : 1)
|
|
401
409
|
let headDOM = head == anchor ? anchorDOM : this.domFromPos(head, head ? -1 : 1)
|
|
402
|
-
let domSel = (root as Document).getSelection()!
|
|
410
|
+
let domSel = (view.root as Document).getSelection()!
|
|
411
|
+
let selRange = view.domSelectionRange()
|
|
403
412
|
|
|
404
413
|
let brKludge = false
|
|
405
414
|
// On Firefox, using Selection.collapse to put the cursor after a
|
|
@@ -429,14 +438,14 @@ export class ViewDesc {
|
|
|
429
438
|
}
|
|
430
439
|
// Firefox can act strangely when the selection is in front of an
|
|
431
440
|
// uneditable node. See #1163 and https://bugzilla.mozilla.org/show_bug.cgi?id=1709536
|
|
432
|
-
if (browser.gecko &&
|
|
433
|
-
let after =
|
|
441
|
+
if (browser.gecko && selRange.focusNode && selRange.focusNode != headDOM.node && selRange.focusNode.nodeType == 1) {
|
|
442
|
+
let after = selRange.focusNode.childNodes[selRange.focusOffset]
|
|
434
443
|
if (after && (after as HTMLElement).contentEditable == "false") force = true
|
|
435
444
|
}
|
|
436
445
|
|
|
437
446
|
if (!(force || brKludge && browser.safari) &&
|
|
438
|
-
isEquivalentPosition(anchorDOM.node, anchorDOM.offset,
|
|
439
|
-
isEquivalentPosition(headDOM.node, headDOM.offset,
|
|
447
|
+
isEquivalentPosition(anchorDOM.node, anchorDOM.offset, selRange.anchorNode!, selRange.anchorOffset) &&
|
|
448
|
+
isEquivalentPosition(headDOM.node, headDOM.offset, selRange.focusNode!, selRange.focusOffset))
|
|
440
449
|
return
|
|
441
450
|
|
|
442
451
|
// Selection.extend can be used to create an 'inverted' selection
|
|
@@ -468,8 +477,8 @@ export class ViewDesc {
|
|
|
468
477
|
}
|
|
469
478
|
}
|
|
470
479
|
|
|
471
|
-
ignoreMutation(mutation:
|
|
472
|
-
return !this.contentDOM &&
|
|
480
|
+
ignoreMutation(mutation: ViewMutationRecord): boolean {
|
|
481
|
+
return !this.contentDOM && mutation.type != "selection"
|
|
473
482
|
}
|
|
474
483
|
|
|
475
484
|
get contentLost() {
|
|
@@ -548,8 +557,8 @@ class WidgetViewDesc extends ViewDesc {
|
|
|
548
557
|
return stop ? stop(event) : false
|
|
549
558
|
}
|
|
550
559
|
|
|
551
|
-
ignoreMutation(mutation:
|
|
552
|
-
return
|
|
560
|
+
ignoreMutation(mutation: ViewMutationRecord) {
|
|
561
|
+
return mutation.type != "selection" || this.widget.spec.ignoreSelection
|
|
553
562
|
}
|
|
554
563
|
|
|
555
564
|
destroy() {
|
|
@@ -578,7 +587,7 @@ class CompositionViewDesc extends ViewDesc {
|
|
|
578
587
|
return {node: this.textDOM, offset: pos}
|
|
579
588
|
}
|
|
580
589
|
|
|
581
|
-
ignoreMutation(mut:
|
|
590
|
+
ignoreMutation(mut: ViewMutationRecord) {
|
|
582
591
|
return mut.type === 'characterData' && mut.target.nodeValue == mut.oldValue
|
|
583
592
|
}
|
|
584
593
|
}
|
|
@@ -629,6 +638,10 @@ class MarkViewDesc extends ViewDesc {
|
|
|
629
638
|
return copy
|
|
630
639
|
}
|
|
631
640
|
|
|
641
|
+
ignoreMutation(mutation: ViewMutationRecord) {
|
|
642
|
+
return this.spec.ignoreMutation ? this.spec.ignoreMutation(mutation) : super.ignoreMutation(mutation)
|
|
643
|
+
}
|
|
644
|
+
|
|
632
645
|
destroy() {
|
|
633
646
|
if (this.spec.destroy) this.spec.destroy()
|
|
634
647
|
super.destroy()
|
|
@@ -924,8 +937,8 @@ class TextViewDesc extends NodeViewDesc {
|
|
|
924
937
|
return super.localPosFromDOM(dom, offset, bias)
|
|
925
938
|
}
|
|
926
939
|
|
|
927
|
-
ignoreMutation(mutation:
|
|
928
|
-
return mutation.type != "characterData" &&
|
|
940
|
+
ignoreMutation(mutation: ViewMutationRecord) {
|
|
941
|
+
return mutation.type != "characterData" && mutation.type != "selection"
|
|
929
942
|
}
|
|
930
943
|
|
|
931
944
|
slice(from: number, to: number, view: EditorView) {
|
|
@@ -987,9 +1000,9 @@ class CustomNodeViewDesc extends NodeViewDesc {
|
|
|
987
1000
|
this.spec.deselectNode ? this.spec.deselectNode() : super.deselectNode()
|
|
988
1001
|
}
|
|
989
1002
|
|
|
990
|
-
setSelection(anchor: number, head: number,
|
|
991
|
-
this.spec.setSelection ? this.spec.setSelection(anchor, head, root)
|
|
992
|
-
: super.setSelection(anchor, head,
|
|
1003
|
+
setSelection(anchor: number, head: number, view: EditorView, force: boolean) {
|
|
1004
|
+
this.spec.setSelection ? this.spec.setSelection(anchor, head, view.root)
|
|
1005
|
+
: super.setSelection(anchor, head, view, force)
|
|
993
1006
|
}
|
|
994
1007
|
|
|
995
1008
|
destroy() {
|
|
@@ -1001,7 +1014,7 @@ class CustomNodeViewDesc extends NodeViewDesc {
|
|
|
1001
1014
|
return this.spec.stopEvent ? this.spec.stopEvent(event) : false
|
|
1002
1015
|
}
|
|
1003
1016
|
|
|
1004
|
-
ignoreMutation(mutation:
|
|
1017
|
+
ignoreMutation(mutation: ViewMutationRecord) {
|
|
1005
1018
|
return this.spec.ignoreMutation ? this.spec.ignoreMutation(mutation) : super.ignoreMutation(mutation)
|
|
1006
1019
|
}
|
|
1007
1020
|
}
|