prosemirror-view 1.39.2 → 1.40.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.40.0 (2025-06-02)
2
+
3
+ ### Bug fixes
4
+
5
+ Suppress lint warnings about dereferencing methods by making `EditorView.dispatch` a property instead of a method.
6
+
7
+ ### New features
8
+
9
+ The `"handleTextInput"` prop now takes an additional argument that can be used to generate the default transaction for that text change.
10
+
11
+ Widget decorations now support a `relaxedSide` option which can be used to allow the DOM cursor to appear at either side.
12
+
13
+ ## 1.39.3 (2025-05-14)
14
+
15
+ ### Bug fixes
16
+
17
+ Fix an issue where clicks using different mouse buttons could be grouped into a single double- or triple-click.
18
+
19
+ Only delete the selection when starting a composition when it crosses block boundaries.
20
+
1
21
  ## 1.39.2 (2025-04-22)
2
22
 
3
23
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -50,6 +50,7 @@ var isEquivalentPosition = function isEquivalentPosition(node, off, targetNode,
50
50
  };
51
51
  var atomElements = /^(img|br|input|textarea|hr)$/i;
52
52
  function scanFor(node, off, targetNode, targetOff, dir) {
53
+ var _a;
53
54
  for (;;) {
54
55
  if (node == targetNode && off == targetOff) return true;
55
56
  if (off == (dir < 0 ? 0 : nodeSize(node))) {
@@ -58,9 +59,13 @@ function scanFor(node, off, targetNode, targetOff, dir) {
58
59
  off = domIndex(node) + (dir < 0 ? 0 : 1);
59
60
  node = parent;
60
61
  } else if (node.nodeType == 1) {
61
- node = node.childNodes[off + (dir < 0 ? -1 : 0)];
62
- if (node.contentEditable == "false") return false;
63
- off = dir < 0 ? nodeSize(node) : 0;
62
+ var child = node.childNodes[off + (dir < 0 ? -1 : 0)];
63
+ if (child.nodeType == 1 && child.contentEditable == "false") {
64
+ if ((_a = child.pmViewDesc) === null || _a === void 0 ? void 0 : _a.ignoreForSelection) off += dir;else return false;
65
+ } else {
66
+ node = child;
67
+ off = dir < 0 ? nodeSize(node) : 0;
68
+ }
64
69
  } else {
65
70
  return false;
66
71
  }
@@ -1063,6 +1068,11 @@ var ViewDesc = function () {
1063
1068
  get: function get() {
1064
1069
  return false;
1065
1070
  }
1071
+ }, {
1072
+ key: "ignoreForSelection",
1073
+ get: function get() {
1074
+ return false;
1075
+ }
1066
1076
  }, {
1067
1077
  key: "isText",
1068
1078
  value: function isText(text) {
@@ -1132,6 +1142,11 @@ var WidgetViewDesc = function (_ViewDesc) {
1132
1142
  get: function get() {
1133
1143
  return true;
1134
1144
  }
1145
+ }, {
1146
+ key: "ignoreForSelection",
1147
+ get: function get() {
1148
+ return !!this.widget.type.spec.relaxedSide;
1149
+ }
1135
1150
  }, {
1136
1151
  key: "side",
1137
1152
  get: function get() {
@@ -2879,7 +2894,8 @@ var InputState = _createClass(function InputState() {
2879
2894
  time: 0,
2880
2895
  x: 0,
2881
2896
  y: 0,
2882
- type: ""
2897
+ type: "",
2898
+ button: 0
2883
2899
  };
2884
2900
  this.lastSelectionOrigin = null;
2885
2901
  this.lastSelectionTime = 0;
@@ -2990,9 +3006,12 @@ editHandlers.keypress = function (view, _event) {
2990
3006
  var sel = view.state.selection;
2991
3007
  if (!(sel instanceof prosemirrorState.TextSelection) || !sel.$from.sameParent(sel.$to)) {
2992
3008
  var text = String.fromCharCode(event.charCode);
3009
+ var deflt = function deflt() {
3010
+ return view.state.tr.insertText(text).scrollIntoView();
3011
+ };
2993
3012
  if (!/[\r\n]/.test(text) && !view.someProp("handleTextInput", function (f) {
2994
- return f(view, sel.$from.pos, sel.$to.pos, text);
2995
- })) view.dispatch(view.state.tr.insertText(text).scrollIntoView());
3013
+ return f(view, sel.$from.pos, sel.$to.pos, text, deflt);
3014
+ })) view.dispatch(deflt());
2996
3015
  event.preventDefault();
2997
3016
  }
2998
3017
  };
@@ -3105,14 +3124,15 @@ handlers.mousedown = function (view, _event) {
3105
3124
  var flushed = forceDOMFlush(view);
3106
3125
  var now = Date.now(),
3107
3126
  type = "singleClick";
3108
- if (now - view.input.lastClick.time < 500 && isNear(event, view.input.lastClick) && !event[selectNodeModifier]) {
3127
+ if (now - view.input.lastClick.time < 500 && isNear(event, view.input.lastClick) && !event[selectNodeModifier] && view.input.lastClick.button == event.button) {
3109
3128
  if (view.input.lastClick.type == "singleClick") type = "doubleClick";else if (view.input.lastClick.type == "doubleClick") type = "tripleClick";
3110
3129
  }
3111
3130
  view.input.lastClick = {
3112
3131
  time: now,
3113
3132
  x: event.clientX,
3114
3133
  y: event.clientY,
3115
- type: type
3134
+ type: type,
3135
+ button: event.button
3116
3136
  };
3117
3137
  var pos = view.posAtCoords(eventCoords(event));
3118
3138
  if (!pos) return;
@@ -3329,8 +3349,9 @@ function endComposition(view) {
3329
3349
  view.domObserver.forceFlush();
3330
3350
  clearComposition(view);
3331
3351
  if (restarting || view.docView && view.docView.dirty) {
3332
- var sel = selectionFromDOM(view);
3333
- if (sel && !sel.eq(view.state.selection)) view.dispatch(view.state.tr.setSelection(sel));else if ((view.markCursor || restarting) && !view.state.selection.empty) view.dispatch(view.state.tr.deleteSelection());else view.updateState(view.state);
3352
+ var sel = selectionFromDOM(view),
3353
+ cur = view.state.selection;
3354
+ if (sel && !sel.eq(cur)) view.dispatch(view.state.tr.setSelection(sel));else if ((view.markCursor || restarting) && !cur.$from.node(cur.$from.sharedDepth(cur.to)).inlineContent) view.dispatch(view.state.tr.deleteSelection());else view.updateState(view.state);
3334
3355
  return true;
3335
3356
  }
3336
3357
  return false;
@@ -4595,10 +4616,10 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
4595
4616
  if (chrome && android && view.input.lastKeyCode === 13 && Date.now() - 100 < view.input.lastKeyCodeTime && view.someProp("handleKeyDown", function (f) {
4596
4617
  return f(view, keyEvent(13, "Enter"));
4597
4618
  })) return;
4598
- var _tr = view.state.tr.setSelection(newSel);
4599
- if (origin == "pointer") _tr.setMeta("pointer", true);else if (origin == "key") _tr.scrollIntoView();
4600
- if (compositionID) _tr.setMeta("composition", compositionID);
4601
- view.dispatch(_tr);
4619
+ var tr = view.state.tr.setSelection(newSel);
4620
+ if (origin == "pointer") tr.setMeta("pointer", true);else if (origin == "key") tr.scrollIntoView();
4621
+ if (compositionID) tr.setMeta("composition", compositionID);
4622
+ view.dispatch(tr);
4602
4623
  }
4603
4624
  return;
4604
4625
  }
@@ -4640,9 +4661,9 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
4640
4661
  if (parse.sel) {
4641
4662
  var _sel2 = resolveSelection(view, view.state.doc, parse.sel);
4642
4663
  if (_sel2 && !_sel2.eq(view.state.selection)) {
4643
- var _tr2 = view.state.tr.setSelection(_sel2);
4644
- if (compositionID) _tr2.setMeta("composition", compositionID);
4645
- view.dispatch(_tr2);
4664
+ var _tr = view.state.tr.setSelection(_sel2);
4665
+ if (compositionID) _tr.setMeta("composition", compositionID);
4666
+ view.dispatch(_tr);
4646
4667
  }
4647
4668
  }
4648
4669
  return;
@@ -4692,7 +4713,16 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
4692
4713
  }
4693
4714
  var chFrom = change.start,
4694
4715
  chTo = change.endA;
4695
- var tr, storedMarks, markChange;
4716
+ var mkTr = function mkTr(base) {
4717
+ var tr = base || view.state.tr.replace(chFrom, chTo, parse.doc.slice(change.start - parse.from, change.endB - parse.from));
4718
+ if (parse.sel) {
4719
+ var _sel3 = resolveSelection(view, tr.doc, parse.sel);
4720
+ 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);
4721
+ }
4722
+ if (compositionID) tr.setMeta("composition", compositionID);
4723
+ return tr.scrollIntoView();
4724
+ };
4725
+ var markChange;
4696
4726
  if (inlineChange) {
4697
4727
  if ($from.pos == $to.pos) {
4698
4728
  if (ie && ie_version <= 11 && $from.parentOffset == 0) {
@@ -4701,27 +4731,26 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
4701
4731
  return selectionToDOM(view);
4702
4732
  }, 20);
4703
4733
  }
4704
- tr = view.state.tr["delete"](chFrom, chTo);
4705
- storedMarks = doc.resolve(change.start).marksAcross(doc.resolve(change.endA));
4734
+ var _tr2 = mkTr(view.state.tr["delete"](chFrom, chTo));
4735
+ var marks = doc.resolve(change.start).marksAcross(doc.resolve(change.endA));
4736
+ if (marks) _tr2.ensureMarks(marks);
4737
+ view.dispatch(_tr2);
4706
4738
  } else if (change.endA == change.endB && (markChange = isMarkChange($from.parent.content.cut($from.parentOffset, $to.parentOffset), $fromA.parent.content.cut($fromA.parentOffset, change.endA - $fromA.start())))) {
4707
- tr = view.state.tr;
4708
- if (markChange.type == "add") tr.addMark(chFrom, chTo, markChange.mark);else tr.removeMark(chFrom, chTo, markChange.mark);
4739
+ var _tr3 = mkTr(view.state.tr);
4740
+ if (markChange.type == "add") _tr3.addMark(chFrom, chTo, markChange.mark);else _tr3.removeMark(chFrom, chTo, markChange.mark);
4741
+ view.dispatch(_tr3);
4709
4742
  } else if ($from.parent.child($from.index()).isText && $from.index() == $to.index() - ($to.textOffset ? 0 : 1)) {
4710
4743
  var text = $from.parent.textBetween($from.parentOffset, $to.parentOffset);
4711
- if (view.someProp("handleTextInput", function (f) {
4712
- return f(view, chFrom, chTo, text);
4713
- })) return;
4714
- tr = view.state.tr.insertText(text, chFrom, chTo);
4744
+ var deflt = function deflt() {
4745
+ return mkTr(view.state.tr.insertText(text, chFrom, chTo));
4746
+ };
4747
+ if (!view.someProp("handleTextInput", function (f) {
4748
+ return f(view, chFrom, chTo, text, deflt);
4749
+ })) view.dispatch(deflt());
4715
4750
  }
4751
+ } else {
4752
+ view.dispatch(mkTr());
4716
4753
  }
4717
- if (!tr) tr = view.state.tr.replace(chFrom, chTo, parse.doc.slice(change.start - parse.from, change.endB - parse.from));
4718
- if (parse.sel) {
4719
- var _sel3 = resolveSelection(view, tr.doc, parse.sel);
4720
- 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);
4721
- }
4722
- if (storedMarks) tr.ensureMarks(storedMarks);
4723
- if (compositionID) tr.setMeta("composition", compositionID);
4724
- view.dispatch(tr.scrollIntoView());
4725
4754
  }
4726
4755
  function resolveSelection(view, doc, parsedSel) {
4727
4756
  if (Math.max(parsedSel.anchor, parsedSel.head) > doc.content.size) return null;
@@ -5165,12 +5194,6 @@ var EditorView = function () {
5165
5194
  value: function dispatchEvent(event) {
5166
5195
  return _dispatchEvent(this, event);
5167
5196
  }
5168
- }, {
5169
- key: "dispatch",
5170
- value: function dispatch(tr) {
5171
- var dispatchTransaction = this._props.dispatchTransaction;
5172
- if (dispatchTransaction) dispatchTransaction.call(this, tr);else this.updateState(this.state.apply(tr));
5173
- }
5174
5197
  }, {
5175
5198
  key: "domSelectionRange",
5176
5199
  value: function domSelectionRange() {
@@ -5191,6 +5214,10 @@ var EditorView = function () {
5191
5214
  }]);
5192
5215
  return EditorView;
5193
5216
  }();
5217
+ EditorView.prototype.dispatch = function (tr) {
5218
+ var dispatchTransaction = this._props.dispatchTransaction;
5219
+ if (dispatchTransaction) dispatchTransaction.call(this, tr);else this.updateState(this.state.apply(tr));
5220
+ };
5194
5221
  function computeDocDeco(view) {
5195
5222
  var attrs = Object.create(null);
5196
5223
  attrs["class"] = "ProseMirror";
package/dist/index.d.cts CHANGED
@@ -48,6 +48,20 @@ declare class Decoration {
48
48
  */
49
49
  side?: number;
50
50
  /**
51
+ By default, the cursor, when at the position of the widget,
52
+ will be strictly kept on the side indicated by
53
+ [`side`](https://prosemirror.net/docs/ref/#view.Decoration^widget^spec.side). Set this to true
54
+ to allow the DOM selection to stay on the other side if the
55
+ client sets it there.
56
+
57
+ **Note**: Mapping of this decoration, which decides on which
58
+ side insertions at its position appear, will still happen
59
+ according to `side`, and keyboard cursor motion will not,
60
+ without further custom handling, visit both sides of the
61
+ widget.
62
+ */
63
+ relaxedSide?: boolean;
64
+ /**
51
65
  The precise set of marks to draw around the widget.
52
66
  */
53
67
  marks?: readonly Mark[];
@@ -367,7 +381,6 @@ editable document. Its state and behavior are determined by its
367
381
  [props](https://prosemirror.net/docs/ref/#view.DirectEditorProps).
368
382
  */
369
383
  declare class EditorView {
370
- private _props;
371
384
  private directPlugins;
372
385
  private _root;
373
386
  private mounted;
@@ -587,7 +600,7 @@ declare class EditorView {
587
600
  This method is bound to the view instance, so that it can be
588
601
  easily passed around.
589
602
  */
590
- dispatch(tr: Transaction): void;
603
+ dispatch: (tr: Transaction) => void;
591
604
  }
592
605
  /**
593
606
  The type of function [provided](https://prosemirror.net/docs/ref/#view.EditorProps.nodeViews) to
@@ -652,7 +665,7 @@ interface EditorProps<P = any> {
652
665
  before the input is applied. If it returns `true`, the default
653
666
  behavior of actually inserting the text is suppressed.
654
667
  */
655
- handleTextInput?: (this: P, view: EditorView, from: number, to: number, text: string) => boolean | void;
668
+ handleTextInput?: (this: P, view: EditorView, from: number, to: number, text: string, deflt: () => Transaction) => boolean | void;
656
669
  /**
657
670
  Called for each node around a click, from the inside out. The
658
671
  `direct` flag will be true for the inner node.
package/dist/index.d.ts CHANGED
@@ -48,6 +48,20 @@ declare class Decoration {
48
48
  */
49
49
  side?: number;
50
50
  /**
51
+ By default, the cursor, when at the position of the widget,
52
+ will be strictly kept on the side indicated by
53
+ [`side`](https://prosemirror.net/docs/ref/#view.Decoration^widget^spec.side). Set this to true
54
+ to allow the DOM selection to stay on the other side if the
55
+ client sets it there.
56
+
57
+ **Note**: Mapping of this decoration, which decides on which
58
+ side insertions at its position appear, will still happen
59
+ according to `side`, and keyboard cursor motion will not,
60
+ without further custom handling, visit both sides of the
61
+ widget.
62
+ */
63
+ relaxedSide?: boolean;
64
+ /**
51
65
  The precise set of marks to draw around the widget.
52
66
  */
53
67
  marks?: readonly Mark[];
@@ -367,7 +381,6 @@ editable document. Its state and behavior are determined by its
367
381
  [props](https://prosemirror.net/docs/ref/#view.DirectEditorProps).
368
382
  */
369
383
  declare class EditorView {
370
- private _props;
371
384
  private directPlugins;
372
385
  private _root;
373
386
  private mounted;
@@ -587,7 +600,7 @@ declare class EditorView {
587
600
  This method is bound to the view instance, so that it can be
588
601
  easily passed around.
589
602
  */
590
- dispatch(tr: Transaction): void;
603
+ dispatch: (tr: Transaction) => void;
591
604
  }
592
605
  /**
593
606
  The type of function [provided](https://prosemirror.net/docs/ref/#view.EditorProps.nodeViews) to
@@ -652,7 +665,7 @@ interface EditorProps<P = any> {
652
665
  before the input is applied. If it returns `true`, the default
653
666
  behavior of actually inserting the text is suppressed.
654
667
  */
655
- handleTextInput?: (this: P, view: EditorView, from: number, to: number, text: string) => boolean | void;
668
+ handleTextInput?: (this: P, view: EditorView, from: number, to: number, text: string, deflt: () => Transaction) => boolean | void;
656
669
  /**
657
670
  Called for each node around a click, from the inside out. The
658
671
  `direct` flag will be true for the inner node.
package/dist/index.js CHANGED
@@ -35,6 +35,7 @@ const isEquivalentPosition = function (node, off, targetNode, targetOff) {
35
35
  };
36
36
  const atomElements = /^(img|br|input|textarea|hr)$/i;
37
37
  function scanFor(node, off, targetNode, targetOff, dir) {
38
+ var _a;
38
39
  for (;;) {
39
40
  if (node == targetNode && off == targetOff)
40
41
  return true;
@@ -47,10 +48,17 @@ function scanFor(node, off, targetNode, targetOff, dir) {
47
48
  node = parent;
48
49
  }
49
50
  else if (node.nodeType == 1) {
50
- node = node.childNodes[off + (dir < 0 ? -1 : 0)];
51
- if (node.contentEditable == "false")
52
- return false;
53
- off = dir < 0 ? nodeSize(node) : 0;
51
+ let child = node.childNodes[off + (dir < 0 ? -1 : 0)];
52
+ if (child.nodeType == 1 && child.contentEditable == "false") {
53
+ if ((_a = child.pmViewDesc) === null || _a === void 0 ? void 0 : _a.ignoreForSelection)
54
+ off += dir;
55
+ else
56
+ return false;
57
+ }
58
+ else {
59
+ node = child;
60
+ off = dir < 0 ? nodeSize(node) : 0;
61
+ }
54
62
  }
55
63
  else {
56
64
  return false;
@@ -1134,6 +1142,7 @@ class ViewDesc {
1134
1142
  }
1135
1143
  get domAtom() { return false; }
1136
1144
  get ignoreForCoords() { return false; }
1145
+ get ignoreForSelection() { return false; }
1137
1146
  isText(text) { return false; }
1138
1147
  }
1139
1148
  // A widget desc represents a widget decoration, which is a DOM node
@@ -1178,6 +1187,7 @@ class WidgetViewDesc extends ViewDesc {
1178
1187
  super.destroy();
1179
1188
  }
1180
1189
  get domAtom() { return true; }
1190
+ get ignoreForSelection() { return !!this.widget.type.spec.relaxedSide; }
1181
1191
  get side() { return this.widget.type.side; }
1182
1192
  }
1183
1193
  class CompositionViewDesc extends ViewDesc {
@@ -3047,7 +3057,7 @@ class InputState {
3047
3057
  this.mouseDown = null;
3048
3058
  this.lastKeyCode = null;
3049
3059
  this.lastKeyCodeTime = 0;
3050
- this.lastClick = { time: 0, x: 0, y: 0, type: "" };
3060
+ this.lastClick = { time: 0, x: 0, y: 0, type: "", button: 0 };
3051
3061
  this.lastSelectionOrigin = null;
3052
3062
  this.lastSelectionTime = 0;
3053
3063
  this.lastIOSEnter = 0;
@@ -3175,8 +3185,9 @@ editHandlers.keypress = (view, _event) => {
3175
3185
  let sel = view.state.selection;
3176
3186
  if (!(sel instanceof TextSelection) || !sel.$from.sameParent(sel.$to)) {
3177
3187
  let text = String.fromCharCode(event.charCode);
3178
- if (!/[\r\n]/.test(text) && !view.someProp("handleTextInput", f => f(view, sel.$from.pos, sel.$to.pos, text)))
3179
- view.dispatch(view.state.tr.insertText(text).scrollIntoView());
3188
+ let deflt = () => view.state.tr.insertText(text).scrollIntoView();
3189
+ if (!/[\r\n]/.test(text) && !view.someProp("handleTextInput", f => f(view, sel.$from.pos, sel.$to.pos, text, deflt)))
3190
+ view.dispatch(deflt());
3180
3191
  event.preventDefault();
3181
3192
  }
3182
3193
  };
@@ -3289,13 +3300,14 @@ handlers.mousedown = (view, _event) => {
3289
3300
  view.input.shiftKey = event.shiftKey;
3290
3301
  let flushed = forceDOMFlush(view);
3291
3302
  let now = Date.now(), type = "singleClick";
3292
- if (now - view.input.lastClick.time < 500 && isNear(event, view.input.lastClick) && !event[selectNodeModifier]) {
3303
+ if (now - view.input.lastClick.time < 500 && isNear(event, view.input.lastClick) && !event[selectNodeModifier] &&
3304
+ view.input.lastClick.button == event.button) {
3293
3305
  if (view.input.lastClick.type == "singleClick")
3294
3306
  type = "doubleClick";
3295
3307
  else if (view.input.lastClick.type == "doubleClick")
3296
3308
  type = "tripleClick";
3297
3309
  }
3298
- view.input.lastClick = { time: now, x: event.clientX, y: event.clientY, type };
3310
+ view.input.lastClick = { time: now, x: event.clientX, y: event.clientY, type, button: event.button };
3299
3311
  let pos = view.posAtCoords(eventCoords(event));
3300
3312
  if (!pos)
3301
3313
  return;
@@ -3552,10 +3564,10 @@ function endComposition(view, restarting = false) {
3552
3564
  view.domObserver.forceFlush();
3553
3565
  clearComposition(view);
3554
3566
  if (restarting || view.docView && view.docView.dirty) {
3555
- let sel = selectionFromDOM(view);
3556
- if (sel && !sel.eq(view.state.selection))
3567
+ let sel = selectionFromDOM(view), cur = view.state.selection;
3568
+ if (sel && !sel.eq(cur))
3557
3569
  view.dispatch(view.state.tr.setSelection(sel));
3558
- else if ((view.markCursor || restarting) && !view.state.selection.empty)
3570
+ else if ((view.markCursor || restarting) && !cur.$from.node(cur.$from.sharedDepth(cur.to)).inlineContent)
3559
3571
  view.dispatch(view.state.tr.deleteSelection());
3560
3572
  else
3561
3573
  view.updateState(view.state);
@@ -5060,7 +5072,26 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
5060
5072
  }, 20);
5061
5073
  }
5062
5074
  let chFrom = change.start, chTo = change.endA;
5063
- let tr, storedMarks, markChange;
5075
+ let mkTr = (base) => {
5076
+ let tr = base || view.state.tr.replace(chFrom, chTo, parse.doc.slice(change.start - parse.from, change.endB - parse.from));
5077
+ if (parse.sel) {
5078
+ let sel = resolveSelection(view, tr.doc, parse.sel);
5079
+ // Chrome will sometimes, during composition, report the
5080
+ // selection in the wrong place. If it looks like that is
5081
+ // happening, don't update the selection.
5082
+ // Edge just doesn't move the cursor forward when you start typing
5083
+ // in an empty block or between br nodes.
5084
+ if (sel && !(chrome && view.composing && sel.empty &&
5085
+ (change.start != change.endB || view.input.lastChromeDelete < Date.now() - 100) &&
5086
+ (sel.head == chFrom || sel.head == tr.mapping.map(chTo) - 1) ||
5087
+ ie && sel.empty && sel.head == chFrom))
5088
+ tr.setSelection(sel);
5089
+ }
5090
+ if (compositionID)
5091
+ tr.setMeta("composition", compositionID);
5092
+ return tr.scrollIntoView();
5093
+ };
5094
+ let markChange;
5064
5095
  if (inlineChange) {
5065
5096
  if ($from.pos == $to.pos) { // Deletion
5066
5097
  // IE11 sometimes weirdly moves the DOM selection around after
@@ -5069,46 +5100,33 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
5069
5100
  view.domObserver.suppressSelectionUpdates();
5070
5101
  setTimeout(() => selectionToDOM(view), 20);
5071
5102
  }
5072
- tr = view.state.tr.delete(chFrom, chTo);
5073
- storedMarks = doc.resolve(change.start).marksAcross(doc.resolve(change.endA));
5103
+ let tr = mkTr(view.state.tr.delete(chFrom, chTo));
5104
+ let marks = doc.resolve(change.start).marksAcross(doc.resolve(change.endA));
5105
+ if (marks)
5106
+ tr.ensureMarks(marks);
5107
+ view.dispatch(tr);
5074
5108
  }
5075
5109
  else if ( // Adding or removing a mark
5076
5110
  change.endA == change.endB &&
5077
5111
  (markChange = isMarkChange($from.parent.content.cut($from.parentOffset, $to.parentOffset), $fromA.parent.content.cut($fromA.parentOffset, change.endA - $fromA.start())))) {
5078
- tr = view.state.tr;
5112
+ let tr = mkTr(view.state.tr);
5079
5113
  if (markChange.type == "add")
5080
5114
  tr.addMark(chFrom, chTo, markChange.mark);
5081
5115
  else
5082
5116
  tr.removeMark(chFrom, chTo, markChange.mark);
5117
+ view.dispatch(tr);
5083
5118
  }
5084
5119
  else if ($from.parent.child($from.index()).isText && $from.index() == $to.index() - ($to.textOffset ? 0 : 1)) {
5085
5120
  // Both positions in the same text node -- simply insert text
5086
5121
  let text = $from.parent.textBetween($from.parentOffset, $to.parentOffset);
5087
- if (view.someProp("handleTextInput", f => f(view, chFrom, chTo, text)))
5088
- return;
5089
- tr = view.state.tr.insertText(text, chFrom, chTo);
5090
- }
5091
- }
5092
- if (!tr)
5093
- tr = view.state.tr.replace(chFrom, chTo, parse.doc.slice(change.start - parse.from, change.endB - parse.from));
5094
- if (parse.sel) {
5095
- let sel = resolveSelection(view, tr.doc, parse.sel);
5096
- // Chrome will sometimes, during composition, report the
5097
- // selection in the wrong place. If it looks like that is
5098
- // happening, don't update the selection.
5099
- // Edge just doesn't move the cursor forward when you start typing
5100
- // in an empty block or between br nodes.
5101
- if (sel && !(chrome && view.composing && sel.empty &&
5102
- (change.start != change.endB || view.input.lastChromeDelete < Date.now() - 100) &&
5103
- (sel.head == chFrom || sel.head == tr.mapping.map(chTo) - 1) ||
5104
- ie && sel.empty && sel.head == chFrom))
5105
- tr.setSelection(sel);
5106
- }
5107
- if (storedMarks)
5108
- tr.ensureMarks(storedMarks);
5109
- if (compositionID)
5110
- tr.setMeta("composition", compositionID);
5111
- view.dispatch(tr.scrollIntoView());
5122
+ let deflt = () => mkTr(view.state.tr.insertText(text, chFrom, chTo));
5123
+ if (!view.someProp("handleTextInput", f => f(view, chFrom, chTo, text, deflt)))
5124
+ view.dispatch(deflt());
5125
+ }
5126
+ }
5127
+ else {
5128
+ view.dispatch(mkTr());
5129
+ }
5112
5130
  }
5113
5131
  function resolveSelection(view, doc, parsedSel) {
5114
5132
  if (Math.max(parsedSel.anchor, parsedSel.head) > doc.content.size)
@@ -5711,22 +5729,6 @@ class EditorView {
5711
5729
  return dispatchEvent(this, event);
5712
5730
  }
5713
5731
  /**
5714
- Dispatch a transaction. Will call
5715
- [`dispatchTransaction`](https://prosemirror.net/docs/ref/#view.DirectEditorProps.dispatchTransaction)
5716
- when given, and otherwise defaults to applying the transaction to
5717
- the current state and calling
5718
- [`updateState`](https://prosemirror.net/docs/ref/#view.EditorView.updateState) with the result.
5719
- This method is bound to the view instance, so that it can be
5720
- easily passed around.
5721
- */
5722
- dispatch(tr) {
5723
- let dispatchTransaction = this._props.dispatchTransaction;
5724
- if (dispatchTransaction)
5725
- dispatchTransaction.call(this, tr);
5726
- else
5727
- this.updateState(this.state.apply(tr));
5728
- }
5729
- /**
5730
5732
  @internal
5731
5733
  */
5732
5734
  domSelectionRange() {
@@ -5743,6 +5745,13 @@ class EditorView {
5743
5745
  return this.root.getSelection();
5744
5746
  }
5745
5747
  }
5748
+ EditorView.prototype.dispatch = function (tr) {
5749
+ let dispatchTransaction = this._props.dispatchTransaction;
5750
+ if (dispatchTransaction)
5751
+ dispatchTransaction.call(this, tr);
5752
+ else
5753
+ this.updateState(this.state.apply(tr));
5754
+ };
5746
5755
  function computeDocDeco(view) {
5747
5756
  let attrs = Object.create(null);
5748
5757
  attrs.class = "ProseMirror";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prosemirror-view",
3
- "version": "1.39.2",
3
+ "version": "1.40.0",
4
4
  "description": "ProseMirror's view component",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
package/src/decoration.ts CHANGED
@@ -156,6 +156,19 @@ export class Decoration {
156
156
  /// negative, those of the node after when positive.
157
157
  side?: number
158
158
 
159
+ /// By default, the cursor, when at the position of the widget,
160
+ /// will be strictly kept on the side indicated by
161
+ /// [`side`](#view.Decoration^widget^spec.side). Set this to true
162
+ /// to allow the DOM selection to stay on the other side if the
163
+ /// client sets it there.
164
+ ///
165
+ /// **Note**: Mapping of this decoration, which decides on which
166
+ /// side insertions at its position appear, will still happen
167
+ /// according to `side`, and keyboard cursor motion will not,
168
+ /// without further custom handling, visit both sides of the
169
+ /// widget.
170
+ relaxedSide?: boolean
171
+
159
172
  /// The precise set of marks to draw around the widget.
160
173
  marks?: readonly Mark[]
161
174
 
package/src/dom.ts CHANGED
@@ -54,9 +54,14 @@ function scanFor(node: Node, off: number, targetNode: Node, targetOff: number, d
54
54
  off = domIndex(node) + (dir < 0 ? 0 : 1)
55
55
  node = parent
56
56
  } else if (node.nodeType == 1) {
57
- node = node.childNodes[off + (dir < 0 ? -1 : 0)]
58
- if ((node as HTMLElement).contentEditable == "false") return false
59
- off = dir < 0 ? nodeSize(node) : 0
57
+ let child = node.childNodes[off + (dir < 0 ? -1 : 0)]
58
+ if (child.nodeType == 1 && (child as HTMLElement).contentEditable == "false") {
59
+ if (child.pmViewDesc?.ignoreForSelection) off += dir
60
+ else return false
61
+ } else {
62
+ node = child
63
+ off = dir < 0 ? nodeSize(node) : 0
64
+ }
60
65
  } else {
61
66
  return false
62
67
  }