prosemirror-view 1.35.0 → 1.37.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 CHANGED
@@ -1,3 +1,23 @@
1
+ ## 1.37.0 (2024-11-23)
2
+
3
+ ### Bug fixes
4
+
5
+ Fix an issue on Safari where the (incorrect) top-level selection was used, instead of the workaround that also works inside shadow roots.
6
+
7
+ ### New features
8
+
9
+ Mark views can now provide an `ignoreMutation` method. Add ViewMutationRecord to doc template
10
+
11
+ ## 1.36.0 (2024-11-05)
12
+
13
+ ### Bug fixes
14
+
15
+ Don't recreated `TrustedTypePolicy` objects for every paste.
16
+
17
+ ### New features
18
+
19
+ Add support for a `destroy` method on custom mark views. Add MarkView to the doc template
20
+
1
21
  ## 1.35.0 (2024-10-29)
2
22
 
3
23
  ### New features
package/dist/index.cjs CHANGED
@@ -935,19 +935,20 @@ var ViewDesc = function () {
935
935
  }
936
936
  }, {
937
937
  key: "setSelection",
938
- value: function setSelection(anchor, head, root) {
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, root, force);
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 && domSel.focusNode && domSel.focusNode != headDOM.node && domSel.focusNode.nodeType == 1) {
977
- var _after2 = domSel.focusNode.childNodes[domSel.focusOffset];
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, domSel.anchorNode, domSel.anchorOffset) && isEquivalentPosition(headDOM.node, headDOM.offset, domSel.focusNode, domSel.focusOffset)) return;
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);
@@ -1167,11 +1168,12 @@ var CompositionViewDesc = function (_ViewDesc2) {
1167
1168
  var MarkViewDesc = function (_ViewDesc3) {
1168
1169
  _inherits(MarkViewDesc, _ViewDesc3);
1169
1170
  var _super3 = _createSuper(MarkViewDesc);
1170
- function MarkViewDesc(parent, mark, dom, contentDOM) {
1171
+ function MarkViewDesc(parent, mark, dom, contentDOM, spec) {
1171
1172
  var _this3;
1172
1173
  _classCallCheck(this, MarkViewDesc);
1173
1174
  _this3 = _super3.call(this, parent, [], dom, contentDOM);
1174
1175
  _this3.mark = mark;
1176
+ _this3.spec = spec;
1175
1177
  return _this3;
1176
1178
  }
1177
1179
  _createClass(MarkViewDesc, [{
@@ -1212,13 +1214,24 @@ var MarkViewDesc = function (_ViewDesc3) {
1212
1214
  copy.children = nodes;
1213
1215
  return copy;
1214
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
+ }
1222
+ }, {
1223
+ key: "destroy",
1224
+ value: function destroy() {
1225
+ if (this.spec.destroy) this.spec.destroy();
1226
+ _get(_getPrototypeOf(MarkViewDesc.prototype), "destroy", this).call(this);
1227
+ }
1215
1228
  }], [{
1216
1229
  key: "create",
1217
1230
  value: function create(parent, mark, inline, view) {
1218
1231
  var custom = view.nodeViews[mark.type.name];
1219
1232
  var spec = custom && custom(mark, view, inline);
1220
1233
  if (!spec || !spec.dom) spec = prosemirrorModel.DOMSerializer.renderSpec(document, mark.type.spec.toDOM(mark, inline), null, mark.attrs);
1221
- return new MarkViewDesc(parent, mark, spec.dom, spec.contentDOM || spec.dom);
1234
+ return new MarkViewDesc(parent, mark, spec.dom, spec.contentDOM || spec.dom, spec);
1222
1235
  }
1223
1236
  }]);
1224
1237
  return MarkViewDesc;
@@ -1586,8 +1599,8 @@ var CustomNodeViewDesc = function (_NodeViewDesc2) {
1586
1599
  }
1587
1600
  }, {
1588
1601
  key: "setSelection",
1589
- value: function setSelection(anchor, head, root, force) {
1590
- this.spec.setSelection ? this.spec.setSelection(anchor, head, root) : _get(_getPrototypeOf(CustomNodeViewDesc.prototype), "setSelection", this).call(this, anchor, head, root, force);
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);
1591
1604
  }
1592
1605
  }, {
1593
1606
  key: "destroy",
@@ -2172,7 +2185,7 @@ function selectionToDOM(view) {
2172
2185
  if (!sel.$from.parent.inlineContent) resetEditableFrom = temporarilyEditableNear(view, sel.from);
2173
2186
  if (!sel.empty && !sel.$from.parent.inlineContent) resetEditableTo = temporarilyEditableNear(view, sel.to);
2174
2187
  }
2175
- view.docView.setSelection(anchor, head, view.root, force);
2188
+ view.docView.setSelection(anchor, head, view, force);
2176
2189
  if (brokenSelectBetweenUneditable) {
2177
2190
  if (resetEditableFrom) resetEditable(resetEditableFrom);
2178
2191
  if (resetEditableTo) resetEditable(resetEditableTo);
@@ -2783,14 +2796,16 @@ var _detachedDoc = null;
2783
2796
  function detachedDoc() {
2784
2797
  return _detachedDoc || (_detachedDoc = document.implementation.createHTMLDocument("title"));
2785
2798
  }
2799
+ var _policy = null;
2786
2800
  function maybeWrapTrusted(html) {
2787
2801
  var trustedTypes = window.trustedTypes;
2788
2802
  if (!trustedTypes) return html;
2789
- return trustedTypes.createPolicy("detachedDocument", {
2803
+ if (!_policy) _policy = trustedTypes.createPolicy("ProseMirrorClipboard", {
2790
2804
  createHTML: function createHTML(s) {
2791
2805
  return s;
2792
2806
  }
2793
- }).createHTML(html);
2807
+ });
2808
+ return _policy.createHTML(html);
2794
2809
  }
2795
2810
  function readHTML(html) {
2796
2811
  var metas = /^(\s*<meta [^>]*>)*/.exec(html);
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
@@ -245,9 +256,6 @@ node-specific editing interfaces, you want more control over
245
256
  the behavior of a node's in-editor representation, and need to
246
257
  [define](https://prosemirror.net/docs/ref/#view.EditorProps.nodeViews) a custom node view.
247
258
 
248
- Mark views only support `dom` and `contentDOM`, and don't support
249
- any of the node view methods.
250
-
251
259
  Objects returned as node views must conform to this interface.
252
260
  */
253
261
  interface NodeView {
@@ -311,19 +319,45 @@ interface NodeView {
311
319
  */
312
320
  stopEvent?: (event: Event) => boolean;
313
321
  /**
314
- Called when a DOM
315
- [mutation](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver)
316
- or a selection change happens within the view. When the change is
317
- a selection change, the record will have a `type` property of
318
- `"selection"` (which doesn't occur for native mutation records).
319
- Return false if the editor should re-read the selection or
320
- re-parse the range around the mutation, true if it can safely be
321
- 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.
322
325
  */
323
- ignoreMutation?: (mutation: MutationRecord) => boolean;
326
+ ignoreMutation?: (mutation: ViewMutationRecord) => boolean;
324
327
  /**
325
328
  Called when the node view is removed from the editor or the whole
326
- editor is destroyed. (Not available for marks.)
329
+ editor is destroyed.
330
+ */
331
+ destroy?: () => void;
332
+ }
333
+ /**
334
+ By default, document marks are rendered using the result of the
335
+ [`toDOM`](https://prosemirror.net/docs/ref/#model.MarkSpec.toDOM) method of their spec, and managed entirely
336
+ by the editor. For some use cases, you want more control over the behavior
337
+ of a mark's in-editor representation, and need to
338
+ [define](https://prosemirror.net/docs/ref/#view.EditorProps.markViews) a custom mark view.
339
+
340
+ Objects returned as mark views must conform to this interface.
341
+ */
342
+ interface MarkView {
343
+ /**
344
+ The outer DOM node that represents the document node.
345
+ */
346
+ dom: DOMNode;
347
+ /**
348
+ The DOM node that should hold the mark's content. When this is not
349
+ present, the `dom` property is used as the content DOM.
350
+ */
351
+ contentDOM?: HTMLElement | null;
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
+ /**
359
+ Called when the mark view is removed from the editor or the whole
360
+ editor is destroyed.
327
361
  */
328
362
  destroy?: () => void;
329
363
  }
@@ -369,8 +403,8 @@ declare class ViewDesc {
369
403
  };
370
404
  emptyChildAt(side: number): boolean;
371
405
  domAfterPos(pos: number): DOMNode;
372
- setSelection(anchor: number, head: number, root: Document | ShadowRoot, force?: boolean): void;
373
- ignoreMutation(mutation: MutationRecord): boolean;
406
+ setSelection(anchor: number, head: number, view: EditorView, force?: boolean): void;
407
+ ignoreMutation(mutation: ViewMutationRecord): boolean;
374
408
  get contentLost(): boolean | null;
375
409
  markDirty(from: number, to: number): void;
376
410
  markParentsDirty(): void;
@@ -419,7 +453,7 @@ declare class TextViewDesc extends NodeViewDesc {
419
453
  offset: number;
420
454
  };
421
455
  localPosFromDOM(dom: DOMNode, offset: number, bias: number): number;
422
- ignoreMutation(mutation: MutationRecord): boolean;
456
+ ignoreMutation(mutation: ViewMutationRecord): boolean;
423
457
  slice(from: number, to: number, view: EditorView): TextViewDesc;
424
458
  markDirty(from: number, to: number): void;
425
459
  get domAtom(): boolean;
@@ -650,10 +684,7 @@ type NodeViewConstructor = (node: Node, view: EditorView, getPos: () => number |
650
684
  The function types [used](https://prosemirror.net/docs/ref/#view.EditorProps.markViews) to create
651
685
  mark views.
652
686
  */
653
- type MarkViewConstructor = (mark: Mark, view: EditorView, inline: boolean) => {
654
- dom: HTMLElement;
655
- contentDOM?: HTMLElement;
656
- };
687
+ type MarkViewConstructor = (mark: Mark, view: EditorView, inline: boolean) => MarkView;
657
688
  /**
658
689
  Helper type that maps event names to event object types, but
659
690
  includes events that TypeScript's HTMLElementEventMap doesn't know
@@ -936,4 +967,4 @@ interface DirectEditorProps extends EditorProps {
936
967
  dispatchTransaction?: (tr: Transaction) => void;
937
968
  }
938
969
 
939
- export { type DOMEventMap, Decoration, type DecorationAttrs, DecorationSet, type DecorationSource, type DirectEditorProps, type EditorProps, EditorView, 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
@@ -245,9 +256,6 @@ node-specific editing interfaces, you want more control over
245
256
  the behavior of a node's in-editor representation, and need to
246
257
  [define](https://prosemirror.net/docs/ref/#view.EditorProps.nodeViews) a custom node view.
247
258
 
248
- Mark views only support `dom` and `contentDOM`, and don't support
249
- any of the node view methods.
250
-
251
259
  Objects returned as node views must conform to this interface.
252
260
  */
253
261
  interface NodeView {
@@ -311,19 +319,45 @@ interface NodeView {
311
319
  */
312
320
  stopEvent?: (event: Event) => boolean;
313
321
  /**
314
- Called when a DOM
315
- [mutation](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver)
316
- or a selection change happens within the view. When the change is
317
- a selection change, the record will have a `type` property of
318
- `"selection"` (which doesn't occur for native mutation records).
319
- Return false if the editor should re-read the selection or
320
- re-parse the range around the mutation, true if it can safely be
321
- 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.
322
325
  */
323
- ignoreMutation?: (mutation: MutationRecord) => boolean;
326
+ ignoreMutation?: (mutation: ViewMutationRecord) => boolean;
324
327
  /**
325
328
  Called when the node view is removed from the editor or the whole
326
- editor is destroyed. (Not available for marks.)
329
+ editor is destroyed.
330
+ */
331
+ destroy?: () => void;
332
+ }
333
+ /**
334
+ By default, document marks are rendered using the result of the
335
+ [`toDOM`](https://prosemirror.net/docs/ref/#model.MarkSpec.toDOM) method of their spec, and managed entirely
336
+ by the editor. For some use cases, you want more control over the behavior
337
+ of a mark's in-editor representation, and need to
338
+ [define](https://prosemirror.net/docs/ref/#view.EditorProps.markViews) a custom mark view.
339
+
340
+ Objects returned as mark views must conform to this interface.
341
+ */
342
+ interface MarkView {
343
+ /**
344
+ The outer DOM node that represents the document node.
345
+ */
346
+ dom: DOMNode;
347
+ /**
348
+ The DOM node that should hold the mark's content. When this is not
349
+ present, the `dom` property is used as the content DOM.
350
+ */
351
+ contentDOM?: HTMLElement | null;
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
+ /**
359
+ Called when the mark view is removed from the editor or the whole
360
+ editor is destroyed.
327
361
  */
328
362
  destroy?: () => void;
329
363
  }
@@ -369,8 +403,8 @@ declare class ViewDesc {
369
403
  };
370
404
  emptyChildAt(side: number): boolean;
371
405
  domAfterPos(pos: number): DOMNode;
372
- setSelection(anchor: number, head: number, root: Document | ShadowRoot, force?: boolean): void;
373
- ignoreMutation(mutation: MutationRecord): boolean;
406
+ setSelection(anchor: number, head: number, view: EditorView, force?: boolean): void;
407
+ ignoreMutation(mutation: ViewMutationRecord): boolean;
374
408
  get contentLost(): boolean | null;
375
409
  markDirty(from: number, to: number): void;
376
410
  markParentsDirty(): void;
@@ -419,7 +453,7 @@ declare class TextViewDesc extends NodeViewDesc {
419
453
  offset: number;
420
454
  };
421
455
  localPosFromDOM(dom: DOMNode, offset: number, bias: number): number;
422
- ignoreMutation(mutation: MutationRecord): boolean;
456
+ ignoreMutation(mutation: ViewMutationRecord): boolean;
423
457
  slice(from: number, to: number, view: EditorView): TextViewDesc;
424
458
  markDirty(from: number, to: number): void;
425
459
  get domAtom(): boolean;
@@ -650,10 +684,7 @@ type NodeViewConstructor = (node: Node, view: EditorView, getPos: () => number |
650
684
  The function types [used](https://prosemirror.net/docs/ref/#view.EditorProps.markViews) to create
651
685
  mark views.
652
686
  */
653
- type MarkViewConstructor = (mark: Mark, view: EditorView, inline: boolean) => {
654
- dom: HTMLElement;
655
- contentDOM?: HTMLElement;
656
- };
687
+ type MarkViewConstructor = (mark: Mark, view: EditorView, inline: boolean) => MarkView;
657
688
  /**
658
689
  Helper type that maps event names to event object types, but
659
690
  includes events that TypeScript's HTMLElementEventMap doesn't know
@@ -936,4 +967,4 @@ interface DirectEditorProps extends EditorProps {
936
967
  dispatchTransaction?: (tr: Transaction) => void;
937
968
  }
938
969
 
939
- export { type DOMEventMap, Decoration, type DecorationAttrs, DecorationSet, type DecorationSource, type DirectEditorProps, type EditorProps, EditorView, 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
@@ -996,18 +996,19 @@ class ViewDesc {
996
996
  // custom things with the selection. Note that this falls apart when
997
997
  // a selection starts in such a node and ends in another, in which
998
998
  // case we just use whatever domFromPos produces as a best effort.
999
- setSelection(anchor, head, root, force = false) {
999
+ setSelection(anchor, head, view, force = false) {
1000
1000
  // If the selection falls entirely in a child, give it to that child
1001
1001
  let from = Math.min(anchor, head), to = Math.max(anchor, head);
1002
1002
  for (let i = 0, offset = 0; i < this.children.length; i++) {
1003
1003
  let child = this.children[i], end = offset + child.size;
1004
1004
  if (from > offset && to < end)
1005
- return child.setSelection(anchor - offset - child.border, head - offset - child.border, root, force);
1005
+ return child.setSelection(anchor - offset - child.border, head - offset - child.border, view, force);
1006
1006
  offset = end;
1007
1007
  }
1008
1008
  let anchorDOM = this.domFromPos(anchor, anchor ? -1 : 1);
1009
1009
  let headDOM = head == anchor ? anchorDOM : this.domFromPos(head, head ? -1 : 1);
1010
- let domSel = root.getSelection();
1010
+ let domSel = view.root.getSelection();
1011
+ let selRange = view.domSelectionRange();
1011
1012
  let brKludge = false;
1012
1013
  // On Firefox, using Selection.collapse to put the cursor after a
1013
1014
  // BR node for some reason doesn't always work (#1073). On Safari,
@@ -1038,14 +1039,14 @@ class ViewDesc {
1038
1039
  }
1039
1040
  // Firefox can act strangely when the selection is in front of an
1040
1041
  // uneditable node. See #1163 and https://bugzilla.mozilla.org/show_bug.cgi?id=1709536
1041
- if (gecko && domSel.focusNode && domSel.focusNode != headDOM.node && domSel.focusNode.nodeType == 1) {
1042
- let after = domSel.focusNode.childNodes[domSel.focusOffset];
1042
+ if (gecko && selRange.focusNode && selRange.focusNode != headDOM.node && selRange.focusNode.nodeType == 1) {
1043
+ let after = selRange.focusNode.childNodes[selRange.focusOffset];
1043
1044
  if (after && after.contentEditable == "false")
1044
1045
  force = true;
1045
1046
  }
1046
1047
  if (!(force || brKludge && safari) &&
1047
- isEquivalentPosition(anchorDOM.node, anchorDOM.offset, domSel.anchorNode, domSel.anchorOffset) &&
1048
- isEquivalentPosition(headDOM.node, headDOM.offset, domSel.focusNode, domSel.focusOffset))
1048
+ isEquivalentPosition(anchorDOM.node, anchorDOM.offset, selRange.anchorNode, selRange.anchorOffset) &&
1049
+ isEquivalentPosition(headDOM.node, headDOM.offset, selRange.focusNode, selRange.focusOffset))
1049
1050
  return;
1050
1051
  // Selection.extend can be used to create an 'inverted' selection
1051
1052
  // (one where the focus is before the anchor), but not all
@@ -1192,16 +1193,17 @@ class CompositionViewDesc extends ViewDesc {
1192
1193
  // some cases they will be split more often than would appear
1193
1194
  // necessary.
1194
1195
  class MarkViewDesc extends ViewDesc {
1195
- constructor(parent, mark, dom, contentDOM) {
1196
+ constructor(parent, mark, dom, contentDOM, spec) {
1196
1197
  super(parent, [], dom, contentDOM);
1197
1198
  this.mark = mark;
1199
+ this.spec = spec;
1198
1200
  }
1199
1201
  static create(parent, mark, inline, view) {
1200
1202
  let custom = view.nodeViews[mark.type.name];
1201
1203
  let spec = custom && custom(mark, view, inline);
1202
1204
  if (!spec || !spec.dom)
1203
1205
  spec = DOMSerializer.renderSpec(document, mark.type.spec.toDOM(mark, inline), null, mark.attrs);
1204
- return new MarkViewDesc(parent, mark, spec.dom, spec.contentDOM || spec.dom);
1206
+ return new MarkViewDesc(parent, mark, spec.dom, spec.contentDOM || spec.dom, spec);
1205
1207
  }
1206
1208
  parseRule() {
1207
1209
  if ((this.dirty & NODE_DIRTY) || this.mark.type.spec.reparseInView)
@@ -1233,6 +1235,14 @@ class MarkViewDesc extends ViewDesc {
1233
1235
  copy.children = nodes;
1234
1236
  return copy;
1235
1237
  }
1238
+ ignoreMutation(mutation) {
1239
+ return this.spec.ignoreMutation ? this.spec.ignoreMutation(mutation) : super.ignoreMutation(mutation);
1240
+ }
1241
+ destroy() {
1242
+ if (this.spec.destroy)
1243
+ this.spec.destroy();
1244
+ super.destroy();
1245
+ }
1236
1246
  }
1237
1247
  // Node view descs are the main, most common type of view desc, and
1238
1248
  // correspond to an actual node in the document. Unlike mark descs,
@@ -1571,9 +1581,9 @@ class CustomNodeViewDesc extends NodeViewDesc {
1571
1581
  deselectNode() {
1572
1582
  this.spec.deselectNode ? this.spec.deselectNode() : super.deselectNode();
1573
1583
  }
1574
- setSelection(anchor, head, root, force) {
1575
- this.spec.setSelection ? this.spec.setSelection(anchor, head, root)
1576
- : super.setSelection(anchor, head, root, force);
1584
+ setSelection(anchor, head, view, force) {
1585
+ this.spec.setSelection ? this.spec.setSelection(anchor, head, view.root)
1586
+ : super.setSelection(anchor, head, view, force);
1577
1587
  }
1578
1588
  destroy() {
1579
1589
  if (this.spec.destroy)
@@ -2233,7 +2243,7 @@ function selectionToDOM(view, force = false) {
2233
2243
  if (!sel.empty && !sel.$from.parent.inlineContent)
2234
2244
  resetEditableTo = temporarilyEditableNear(view, sel.to);
2235
2245
  }
2236
- view.docView.setSelection(anchor, head, view.root, force);
2246
+ view.docView.setSelection(anchor, head, view, force);
2237
2247
  if (brokenSelectBetweenUneditable) {
2238
2248
  if (resetEditableFrom)
2239
2249
  resetEditable(resetEditableFrom);
@@ -2953,6 +2963,7 @@ let _detachedDoc = null;
2953
2963
  function detachedDoc() {
2954
2964
  return _detachedDoc || (_detachedDoc = document.implementation.createHTMLDocument("title"));
2955
2965
  }
2966
+ let _policy = null;
2956
2967
  function maybeWrapTrusted(html) {
2957
2968
  let trustedTypes = window.trustedTypes;
2958
2969
  if (!trustedTypes)
@@ -2960,7 +2971,9 @@ function maybeWrapTrusted(html) {
2960
2971
  // With the require-trusted-types-for CSP, Chrome will block
2961
2972
  // innerHTML, even on a detached document. This wraps the string in
2962
2973
  // a way that makes the browser allow us to use its parser again.
2963
- return trustedTypes.createPolicy("detachedDocument", { createHTML: (s) => s }).createHTML(html);
2974
+ if (!_policy)
2975
+ _policy = trustedTypes.createPolicy("ProseMirrorClipboard", { createHTML: (s) => s });
2976
+ return _policy.createHTML(html);
2964
2977
  }
2965
2978
  function readHTML(html) {
2966
2979
  let metas = /^(\s*<meta [^>]*>)*/.exec(html);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prosemirror-view",
3
- "version": "1.35.0",
3
+ "version": "1.37.0",
4
4
  "description": "ProseMirror's view component",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
package/src/README.md CHANGED
@@ -18,6 +18,10 @@ this module.
18
18
 
19
19
  @NodeView
20
20
 
21
+ @MarkView
22
+
23
+ @ViewMutationRecord
24
+
21
25
  @DOMEventMap
22
26
 
23
27
  ### Decorations
package/src/clipboard.ts CHANGED
@@ -204,13 +204,16 @@ function detachedDoc() {
204
204
  return _detachedDoc || (_detachedDoc = document.implementation.createHTMLDocument("title"))
205
205
  }
206
206
 
207
+ let _policy: any = null
208
+
207
209
  function maybeWrapTrusted(html: string): string {
208
210
  let trustedTypes = (window as any).trustedTypes
209
211
  if (!trustedTypes) return html
210
212
  // With the require-trusted-types-for CSP, Chrome will block
211
213
  // innerHTML, even on a detached document. This wraps the string in
212
214
  // a way that makes the browser allow us to use its parser again.
213
- return trustedTypes.createPolicy("detachedDocument", {createHTML: (s: string) => s}).createHTML(html)
215
+ if (!_policy) _policy = trustedTypes.createPolicy("ProseMirrorClipboard", {createHTML: (s: string) => s})
216
+ return _policy.createHTML(html)
214
217
  }
215
218
 
216
219
  function readHTML(html: string) {
@@ -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
- } as any)) {
154
+ target: container!.nodeType == 3 ? container!.parentNode! : container!
155
+ })) {
156
156
  this.setCurSelection()
157
157
  return true
158
158
  }
package/src/index.ts CHANGED
@@ -3,7 +3,7 @@ import {Slice, ResolvedPos, DOMParser, DOMSerializer, Node, Mark} from "prosemir
3
3
 
4
4
  import {scrollRectIntoView, posAtCoords, coordsAtPos, endOfTextblock, storeScrollPos,
5
5
  resetScrollPos, focusPreventScroll} from "./domcoords"
6
- import {docViewDesc, ViewDesc, NodeView, NodeViewDesc} from "./viewdesc"
6
+ import {docViewDesc, ViewDesc, NodeView, NodeViewDesc, MarkView} from "./viewdesc"
7
7
  import {initInput, destroyInput, dispatchEvent, ensureListeners, clearComposition,
8
8
  InputState, doPaste, Dragging, findCompositionNode} from "./input"
9
9
  import {selectionToDOM, anchorInRightPlace, syncNodeSelection} from "./selection"
@@ -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} from "./viewdesc"
17
+ export {NodeView, MarkView, ViewMutationRecord} from "./viewdesc"
18
18
 
19
19
  // Exported for testing
20
20
  import {serializeForClipboard, parseFromClipboard} from "./clipboard"
@@ -576,7 +576,7 @@ export type NodeViewConstructor = (node: Node, view: EditorView, getPos: () => n
576
576
 
577
577
  /// The function types [used](#view.EditorProps.markViews) to create
578
578
  /// mark views.
579
- export type MarkViewConstructor = (mark: Mark, view: EditorView, inline: boolean) => {dom: HTMLElement, contentDOM?: HTMLElement}
579
+ export type MarkViewConstructor = (mark: Mark, view: EditorView, inline: boolean) => MarkView
580
580
 
581
581
  type NodeViewSet = {[name: string]: NodeViewConstructor | MarkViewConstructor}
582
582
 
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.root, force)
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
@@ -17,9 +24,6 @@ declare global {
17
24
  /// the behavior of a node's in-editor representation, and need to
18
25
  /// [define](#view.EditorProps.nodeViews) a custom node view.
19
26
  ///
20
- /// Mark views only support `dom` and `contentDOM`, and don't support
21
- /// any of the node view methods.
22
- ///
23
27
  /// Objects returned as node views must conform to this interface.
24
28
  export interface NodeView {
25
29
  /// The outer DOM node that represents the document node.
@@ -73,18 +77,39 @@ export interface NodeView {
73
77
  /// which this returns true are not handled by the editor.
74
78
  stopEvent?: (event: Event) => boolean
75
79
 
76
- /// Called when a DOM
77
- /// [mutation](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver)
78
- /// or a selection change happens within the view. When the change is
79
- /// a selection change, the record will have a `type` property of
80
- /// `"selection"` (which doesn't occur for native mutation records).
81
- /// Return false if the editor should re-read the selection or
82
- /// re-parse the range around the mutation, true if it can safely be
83
- /// ignored.
84
- 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
85
84
 
86
85
  /// Called when the node view is removed from the editor or the whole
87
- /// editor is destroyed. (Not available for marks.)
86
+ /// editor is destroyed.
87
+ destroy?: () => void
88
+ }
89
+
90
+ /// By default, document marks are rendered using the result of the
91
+ /// [`toDOM`](#model.MarkSpec.toDOM) method of their spec, and managed entirely
92
+ /// by the editor. For some use cases, you want more control over the behavior
93
+ /// of a mark's in-editor representation, and need to
94
+ /// [define](#view.EditorProps.markViews) a custom mark view.
95
+ ///
96
+ /// Objects returned as mark views must conform to this interface.
97
+ export interface MarkView {
98
+ /// The outer DOM node that represents the document node.
99
+ dom: DOMNode
100
+
101
+ /// The DOM node that should hold the mark's content. When this is not
102
+ /// present, the `dom` property is used as the content DOM.
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
+
110
+
111
+ /// Called when the mark view is removed from the editor or the whole
112
+ /// editor is destroyed.
88
113
  destroy?: () => void
89
114
  }
90
115
 
@@ -370,19 +395,20 @@ export class ViewDesc {
370
395
  // custom things with the selection. Note that this falls apart when
371
396
  // a selection starts in such a node and ends in another, in which
372
397
  // case we just use whatever domFromPos produces as a best effort.
373
- setSelection(anchor: number, head: number, root: Document | ShadowRoot, force = false): void {
398
+ setSelection(anchor: number, head: number, view: EditorView, force = false): void {
374
399
  // If the selection falls entirely in a child, give it to that child
375
400
  let from = Math.min(anchor, head), to = Math.max(anchor, head)
376
401
  for (let i = 0, offset = 0; i < this.children.length; i++) {
377
402
  let child = this.children[i], end = offset + child.size
378
403
  if (from > offset && to < end)
379
- return child.setSelection(anchor - offset - child.border, head - offset - child.border, root, force)
404
+ return child.setSelection(anchor - offset - child.border, head - offset - child.border, view, force)
380
405
  offset = end
381
406
  }
382
407
 
383
408
  let anchorDOM = this.domFromPos(anchor, anchor ? -1 : 1)
384
409
  let headDOM = head == anchor ? anchorDOM : this.domFromPos(head, head ? -1 : 1)
385
- let domSel = (root as Document).getSelection()!
410
+ let domSel = (view.root as Document).getSelection()!
411
+ let selRange = view.domSelectionRange()
386
412
 
387
413
  let brKludge = false
388
414
  // On Firefox, using Selection.collapse to put the cursor after a
@@ -412,14 +438,14 @@ export class ViewDesc {
412
438
  }
413
439
  // Firefox can act strangely when the selection is in front of an
414
440
  // uneditable node. See #1163 and https://bugzilla.mozilla.org/show_bug.cgi?id=1709536
415
- if (browser.gecko && domSel.focusNode && domSel.focusNode != headDOM.node && domSel.focusNode.nodeType == 1) {
416
- let after = domSel.focusNode.childNodes[domSel.focusOffset]
441
+ if (browser.gecko && selRange.focusNode && selRange.focusNode != headDOM.node && selRange.focusNode.nodeType == 1) {
442
+ let after = selRange.focusNode.childNodes[selRange.focusOffset]
417
443
  if (after && (after as HTMLElement).contentEditable == "false") force = true
418
444
  }
419
445
 
420
446
  if (!(force || brKludge && browser.safari) &&
421
- isEquivalentPosition(anchorDOM.node, anchorDOM.offset, domSel.anchorNode!, domSel.anchorOffset) &&
422
- isEquivalentPosition(headDOM.node, headDOM.offset, domSel.focusNode!, domSel.focusOffset))
447
+ isEquivalentPosition(anchorDOM.node, anchorDOM.offset, selRange.anchorNode!, selRange.anchorOffset) &&
448
+ isEquivalentPosition(headDOM.node, headDOM.offset, selRange.focusNode!, selRange.focusOffset))
423
449
  return
424
450
 
425
451
  // Selection.extend can be used to create an 'inverted' selection
@@ -451,8 +477,8 @@ export class ViewDesc {
451
477
  }
452
478
  }
453
479
 
454
- ignoreMutation(mutation: MutationRecord): boolean {
455
- return !this.contentDOM && (mutation.type as any) != "selection"
480
+ ignoreMutation(mutation: ViewMutationRecord): boolean {
481
+ return !this.contentDOM && mutation.type != "selection"
456
482
  }
457
483
 
458
484
  get contentLost() {
@@ -531,8 +557,8 @@ class WidgetViewDesc extends ViewDesc {
531
557
  return stop ? stop(event) : false
532
558
  }
533
559
 
534
- ignoreMutation(mutation: MutationRecord) {
535
- return (mutation.type as any) != "selection" || this.widget.spec.ignoreSelection
560
+ ignoreMutation(mutation: ViewMutationRecord) {
561
+ return mutation.type != "selection" || this.widget.spec.ignoreSelection
536
562
  }
537
563
 
538
564
  destroy() {
@@ -561,7 +587,7 @@ class CompositionViewDesc extends ViewDesc {
561
587
  return {node: this.textDOM, offset: pos}
562
588
  }
563
589
 
564
- ignoreMutation(mut: MutationRecord) {
590
+ ignoreMutation(mut: ViewMutationRecord) {
565
591
  return mut.type === 'characterData' && mut.target.nodeValue == mut.oldValue
566
592
  }
567
593
  }
@@ -572,7 +598,7 @@ class CompositionViewDesc extends ViewDesc {
572
598
  // some cases they will be split more often than would appear
573
599
  // necessary.
574
600
  class MarkViewDesc extends ViewDesc {
575
- constructor(parent: ViewDesc, readonly mark: Mark, dom: DOMNode, contentDOM: HTMLElement) {
601
+ constructor(parent: ViewDesc, readonly mark: Mark, dom: DOMNode, contentDOM: HTMLElement, readonly spec: MarkView) {
576
602
  super(parent, [], dom, contentDOM)
577
603
  }
578
604
 
@@ -581,7 +607,7 @@ class MarkViewDesc extends ViewDesc {
581
607
  let spec: {dom: HTMLElement, contentDOM?: HTMLElement} = custom && (custom as any)(mark, view, inline)
582
608
  if (!spec || !spec.dom)
583
609
  spec = (DOMSerializer.renderSpec as any)(document, mark.type.spec.toDOM!(mark, inline), null, mark.attrs) as any
584
- return new MarkViewDesc(parent, mark, spec.dom, spec.contentDOM || spec.dom as HTMLElement)
610
+ return new MarkViewDesc(parent, mark, spec.dom, spec.contentDOM || spec.dom as HTMLElement, spec)
585
611
  }
586
612
 
587
613
  parseRule() {
@@ -611,6 +637,15 @@ class MarkViewDesc extends ViewDesc {
611
637
  copy.children = nodes
612
638
  return copy
613
639
  }
640
+
641
+ ignoreMutation(mutation: ViewMutationRecord) {
642
+ return this.spec.ignoreMutation ? this.spec.ignoreMutation(mutation) : super.ignoreMutation(mutation)
643
+ }
644
+
645
+ destroy() {
646
+ if (this.spec.destroy) this.spec.destroy()
647
+ super.destroy()
648
+ }
614
649
  }
615
650
 
616
651
  // Node view descs are the main, most common type of view desc, and
@@ -902,8 +937,8 @@ class TextViewDesc extends NodeViewDesc {
902
937
  return super.localPosFromDOM(dom, offset, bias)
903
938
  }
904
939
 
905
- ignoreMutation(mutation: MutationRecord) {
906
- return mutation.type != "characterData" && (mutation.type as any) != "selection"
940
+ ignoreMutation(mutation: ViewMutationRecord) {
941
+ return mutation.type != "characterData" && mutation.type != "selection"
907
942
  }
908
943
 
909
944
  slice(from: number, to: number, view: EditorView) {
@@ -965,9 +1000,9 @@ class CustomNodeViewDesc extends NodeViewDesc {
965
1000
  this.spec.deselectNode ? this.spec.deselectNode() : super.deselectNode()
966
1001
  }
967
1002
 
968
- setSelection(anchor: number, head: number, root: Document | ShadowRoot, force: boolean) {
969
- this.spec.setSelection ? this.spec.setSelection(anchor, head, root)
970
- : super.setSelection(anchor, head, root, force)
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)
971
1006
  }
972
1007
 
973
1008
  destroy() {
@@ -979,7 +1014,7 @@ class CustomNodeViewDesc extends NodeViewDesc {
979
1014
  return this.spec.stopEvent ? this.spec.stopEvent(event) : false
980
1015
  }
981
1016
 
982
- ignoreMutation(mutation: MutationRecord) {
1017
+ ignoreMutation(mutation: ViewMutationRecord) {
983
1018
  return this.spec.ignoreMutation ? this.spec.ignoreMutation(mutation) : super.ignoreMutation(mutation)
984
1019
  }
985
1020
  }