prosemirror-view 1.33.9 → 1.33.10

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,13 @@
1
+ ## 1.33.10 (2024-08-13)
2
+
3
+ ### Bug fixes
4
+
5
+ Prevent triple clicks and clicks on atom nodes from dispatching a selection update when the new selection is equal to the current selection.
6
+
7
+ Don't crash when `window.getSelection()` returns null.
8
+
9
+ Create a cursor wrapper when starting a composition with a selection that spans mark boundaries, to avoid odd DOM forms created by Chrome in such a situation.
10
+
1
11
  ## 1.33.9 (2024-07-18)
2
12
 
3
13
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -603,6 +603,7 @@ function endOfTextblockHorizontal(view, state, dir) {
603
603
  atStart = !offset,
604
604
  atEnd = offset == $head.parent.content.size;
605
605
  var sel = view.domSelection();
606
+ if (!sel) return $head.pos == $head.start() || $head.pos == $head.end();
606
607
  if (!maybeRTL.test($head.parent.textContent) || !sel.modify) return dir == "left" || dir == "backward" ? atStart : atEnd;
607
608
  return withFlushedState(view, state, function () {
608
609
  var _view$domSelectionRan = view.domSelectionRange(),
@@ -2214,10 +2215,17 @@ function removeClassOnSelectionChange(view) {
2214
2215
  function selectCursorWrapper(view) {
2215
2216
  var domSel = view.domSelection(),
2216
2217
  range = document.createRange();
2218
+ if (!domSel) return;
2217
2219
  var node = view.cursorWrapper.dom,
2218
2220
  img = node.nodeName == "IMG";
2219
- if (img) range.setEnd(node.parentNode, domIndex(node) + 1);else range.setEnd(node, 0);
2220
- range.collapse(false);
2221
+ if (img) range.setStart(node.parentNode, domIndex(node) + 1);else range.setStart(node, 0);
2222
+ var sel = view.state.selection;
2223
+ if (sel.empty) {
2224
+ range.collapse(true);
2225
+ } else {
2226
+ var end = view.domAtPos(sel.to);
2227
+ range.setEnd(end.node, end.offset);
2228
+ }
2221
2229
  domSel.removeAllRanges();
2222
2230
  domSel.addRange(range);
2223
2231
  if (!img && !view.state.selection.visible && ie && ie_version <= 11) {
@@ -2449,6 +2457,7 @@ function setSelFocus(view, node, offset) {
2449
2457
  }
2450
2458
  }
2451
2459
  var sel = view.domSelection();
2460
+ if (!sel) return;
2452
2461
  if (selectionCollapsed(sel)) {
2453
2462
  var range = document.createRange();
2454
2463
  range.setEnd(node, offset);
@@ -2969,6 +2978,7 @@ function runHandlerOnContext(view, propName, pos, inside, event) {
2969
2978
  }
2970
2979
  function updateSelection(view, selection, origin) {
2971
2980
  if (!view.focused) view.focus();
2981
+ if (view.state.selection.eq(selection)) return;
2972
2982
  var tr = view.state.tr.setSelection(selection);
2973
2983
  if (origin == "pointer") tr.setMeta("pointer", true);
2974
2984
  view.dispatch(tr);
@@ -3187,8 +3197,8 @@ editHandlers.compositionstart = editHandlers.compositionupdate = function (view)
3187
3197
  if (!view.composing) {
3188
3198
  view.domObserver.flush();
3189
3199
  var state = view.state,
3190
- $pos = state.selection.$from;
3191
- if (state.selection.empty && (state.storedMarks || !$pos.textOffset && $pos.parentOffset && $pos.nodeBefore.marks.some(function (m) {
3200
+ $pos = state.selection.$to;
3201
+ if (state.selection instanceof prosemirrorState.TextSelection && (state.storedMarks || !$pos.textOffset && $pos.parentOffset && $pos.nodeBefore.marks.some(function (m) {
3192
3202
  return m.type.spec.inclusive === false;
3193
3203
  }))) {
3194
3204
  view.markCursor = view.state.storedMarks || $pos.marks();
@@ -3202,7 +3212,8 @@ editHandlers.compositionstart = editHandlers.compositionupdate = function (view)
3202
3212
  var before = offset < 0 ? node.lastChild : node.childNodes[offset - 1];
3203
3213
  if (!before) break;
3204
3214
  if (before.nodeType == 3) {
3205
- view.domSelection().collapse(before, before.nodeValue.length);
3215
+ var _sel = view.domSelection();
3216
+ if (_sel) _sel.collapse(before, before.nodeValue.length);
3206
3217
  break;
3207
3218
  } else {
3208
3219
  node = before;
@@ -4562,9 +4573,9 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
4562
4573
  };
4563
4574
  } else {
4564
4575
  if (parse.sel) {
4565
- var _sel = resolveSelection(view, view.state.doc, parse.sel);
4566
- if (_sel && !_sel.eq(view.state.selection)) {
4567
- var _tr2 = view.state.tr.setSelection(_sel);
4576
+ var _sel2 = resolveSelection(view, view.state.doc, parse.sel);
4577
+ if (_sel2 && !_sel2.eq(view.state.selection)) {
4578
+ var _tr2 = view.state.tr.setSelection(_sel2);
4568
4579
  if (compositionID) _tr2.setMeta("composition", compositionID);
4569
4580
  view.dispatch(_tr2);
4570
4581
  }
@@ -4641,8 +4652,8 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
4641
4652
  }
4642
4653
  if (!tr) tr = view.state.tr.replace(chFrom, chTo, parse.doc.slice(change.start - parse.from, change.endB - parse.from));
4643
4654
  if (parse.sel) {
4644
- var _sel2 = resolveSelection(view, tr.doc, parse.sel);
4645
- if (_sel2 && !(chrome && android && view.composing && _sel2.empty && (change.start != change.endB || view.input.lastAndroidDelete < Date.now() - 100) && (_sel2.head == chFrom || _sel2.head == tr.mapping.map(chTo) - 1) || ie && _sel2.empty && _sel2.head == chFrom)) tr.setSelection(_sel2);
4655
+ var _sel3 = resolveSelection(view, tr.doc, parse.sel);
4656
+ if (_sel3 && !(chrome && android && view.composing && _sel3.empty && (change.start != change.endB || view.input.lastAndroidDelete < Date.now() - 100) && (_sel3.head == chFrom || _sel3.head == tr.mapping.map(chTo) - 1) || ie && _sel3.empty && _sel3.head == chFrom)) tr.setSelection(_sel3);
4646
4657
  }
4647
4658
  if (storedMarks) tr.ensureMarks(storedMarks);
4648
4659
  if (compositionID) tr.setMeta("composition", compositionID);
@@ -5096,6 +5107,12 @@ var EditorView = function () {
5096
5107
  key: "domSelectionRange",
5097
5108
  value: function domSelectionRange() {
5098
5109
  var sel = this.domSelection();
5110
+ if (!sel) return {
5111
+ focusNode: null,
5112
+ focusOffset: 0,
5113
+ anchorNode: null,
5114
+ anchorOffset: 0
5115
+ };
5099
5116
  return safari && this.root.nodeType === 11 && deepActiveElement(this.dom.ownerDocument) == this.dom && safariShadowSelectionRange(this, sel) || sel;
5100
5117
  }
5101
5118
  }, {
@@ -5127,7 +5144,7 @@ function updateCursorWrapper(view) {
5127
5144
  dom.setAttribute("alt", "");
5128
5145
  view.cursorWrapper = {
5129
5146
  dom: dom,
5130
- deco: Decoration.widget(view.state.selection.head, dom, {
5147
+ deco: Decoration.widget(view.state.selection.from, dom, {
5131
5148
  raw: true,
5132
5149
  marks: view.markCursor
5133
5150
  })
package/dist/index.js CHANGED
@@ -659,6 +659,8 @@ function endOfTextblockHorizontal(view, state, dir) {
659
659
  return false;
660
660
  let offset = $head.parentOffset, atStart = !offset, atEnd = offset == $head.parent.content.size;
661
661
  let sel = view.domSelection();
662
+ if (!sel)
663
+ return $head.pos == $head.start() || $head.pos == $head.end();
662
664
  // If the textblock is all LTR, or the browser doesn't support
663
665
  // Selection.modify (Edge), fall back to a primitive approach
664
666
  if (!maybeRTL.test($head.parent.textContent) || !sel.modify)
@@ -2281,12 +2283,21 @@ function removeClassOnSelectionChange(view) {
2281
2283
  }
2282
2284
  function selectCursorWrapper(view) {
2283
2285
  let domSel = view.domSelection(), range = document.createRange();
2286
+ if (!domSel)
2287
+ return;
2284
2288
  let node = view.cursorWrapper.dom, img = node.nodeName == "IMG";
2285
2289
  if (img)
2286
- range.setEnd(node.parentNode, domIndex(node) + 1);
2290
+ range.setStart(node.parentNode, domIndex(node) + 1);
2287
2291
  else
2288
- range.setEnd(node, 0);
2289
- range.collapse(false);
2292
+ range.setStart(node, 0);
2293
+ let sel = view.state.selection;
2294
+ if (sel.empty) {
2295
+ range.collapse(true);
2296
+ }
2297
+ else {
2298
+ let end = view.domAtPos(sel.to);
2299
+ range.setEnd(end.node, end.offset);
2300
+ }
2290
2301
  domSel.removeAllRanges();
2291
2302
  domSel.addRange(range);
2292
2303
  // Kludge to kill 'control selection' in IE11 when selecting an
@@ -2574,6 +2585,8 @@ function setSelFocus(view, node, offset) {
2574
2585
  }
2575
2586
  }
2576
2587
  let sel = view.domSelection();
2588
+ if (!sel)
2589
+ return;
2577
2590
  if (selectionCollapsed(sel)) {
2578
2591
  let range = document.createRange();
2579
2592
  range.setEnd(node, offset);
@@ -3140,6 +3153,8 @@ function runHandlerOnContext(view, propName, pos, inside, event) {
3140
3153
  function updateSelection(view, selection, origin) {
3141
3154
  if (!view.focused)
3142
3155
  view.focus();
3156
+ if (view.state.selection.eq(selection))
3157
+ return;
3143
3158
  let tr = view.state.tr.setSelection(selection);
3144
3159
  if (origin == "pointer")
3145
3160
  tr.setMeta("pointer", true);
@@ -3394,8 +3409,8 @@ const timeoutComposition = android ? 5000 : -1;
3394
3409
  editHandlers.compositionstart = editHandlers.compositionupdate = view => {
3395
3410
  if (!view.composing) {
3396
3411
  view.domObserver.flush();
3397
- let { state } = view, $pos = state.selection.$from;
3398
- if (state.selection.empty &&
3412
+ let { state } = view, $pos = state.selection.$to;
3413
+ if (state.selection instanceof TextSelection &&
3399
3414
  (state.storedMarks ||
3400
3415
  (!$pos.textOffset && $pos.parentOffset && $pos.nodeBefore.marks.some(m => m.type.spec.inclusive === false)))) {
3401
3416
  // Need to wrap the cursor in mark nodes different from the ones in the DOM context
@@ -3415,7 +3430,9 @@ editHandlers.compositionstart = editHandlers.compositionupdate = view => {
3415
3430
  if (!before)
3416
3431
  break;
3417
3432
  if (before.nodeType == 3) {
3418
- view.domSelection().collapse(before, before.nodeValue.length);
3433
+ let sel = view.domSelection();
3434
+ if (sel)
3435
+ sel.collapse(before, before.nodeValue.length);
3419
3436
  break;
3420
3437
  }
3421
3438
  else {
@@ -5646,6 +5663,8 @@ class EditorView {
5646
5663
  */
5647
5664
  domSelectionRange() {
5648
5665
  let sel = this.domSelection();
5666
+ if (!sel)
5667
+ return { focusNode: null, focusOffset: 0, anchorNode: null, anchorOffset: 0 };
5649
5668
  return safari && this.root.nodeType === 11 &&
5650
5669
  deepActiveElement(this.dom.ownerDocument) == this.dom && safariShadowSelectionRange(this, sel) || sel;
5651
5670
  }
@@ -5683,7 +5702,7 @@ function updateCursorWrapper(view) {
5683
5702
  dom.className = "ProseMirror-separator";
5684
5703
  dom.setAttribute("mark-placeholder", "true");
5685
5704
  dom.setAttribute("alt", "");
5686
- view.cursorWrapper = { dom, deco: Decoration.widget(view.state.selection.head, dom, { raw: true, marks: view.markCursor }) };
5705
+ view.cursorWrapper = { dom, deco: Decoration.widget(view.state.selection.from, dom, { raw: true, marks: view.markCursor }) };
5687
5706
  }
5688
5707
  else {
5689
5708
  view.cursorWrapper = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prosemirror-view",
3
- "version": "1.33.9",
3
+ "version": "1.33.10",
4
4
  "description": "ProseMirror's view component",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -202,6 +202,7 @@ function setSelFocus(view: EditorView, node: Node, offset: number) {
202
202
  }
203
203
 
204
204
  let sel = view.domSelection()
205
+ if (!sel) return
205
206
  if (selectionCollapsed(sel)) {
206
207
  let range = document.createRange()
207
208
  range.setEnd(node, offset)
package/src/domcoords.ts CHANGED
@@ -465,7 +465,8 @@ function endOfTextblockHorizontal(view: EditorView, state: EditorState, dir: "le
465
465
  let {$head} = state.selection
466
466
  if (!$head.parent.isTextblock) return false
467
467
  let offset = $head.parentOffset, atStart = !offset, atEnd = offset == $head.parent.content.size
468
- let sel = view.domSelection()
468
+ let sel: Selection = view.domSelection()!
469
+ if (!sel) return $head.pos == $head.start() || $head.pos == $head.end()
469
470
  // If the textblock is all LTR, or the browser doesn't support
470
471
  // Selection.modify (Edge), fall back to a primitive approach
471
472
  if (!maybeRTL.test($head.parent.textContent) || !(sel as any).modify)
package/src/index.ts CHANGED
@@ -489,13 +489,14 @@ export class EditorView {
489
489
  /// @internal
490
490
  domSelectionRange(): DOMSelectionRange {
491
491
  let sel = this.domSelection()
492
+ if (!sel) return {focusNode: null, focusOffset: 0, anchorNode: null, anchorOffset: 0}
492
493
  return browser.safari && this.root.nodeType === 11 &&
493
494
  deepActiveElement(this.dom.ownerDocument) == this.dom && safariShadowSelectionRange(this, sel) || sel
494
495
  }
495
496
 
496
497
  /// @internal
497
- domSelection(): DOMSelection {
498
- return (this.root as Document).getSelection()!
498
+ domSelection(): DOMSelection | null {
499
+ return (this.root as Document).getSelection()
499
500
  }
500
501
  }
501
502
 
@@ -526,7 +527,7 @@ function updateCursorWrapper(view: EditorView) {
526
527
  dom.className = "ProseMirror-separator"
527
528
  dom.setAttribute("mark-placeholder", "true")
528
529
  dom.setAttribute("alt", "")
529
- view.cursorWrapper = {dom, deco: Decoration.widget(view.state.selection.head,
530
+ view.cursorWrapper = {dom, deco: Decoration.widget(view.state.selection.from,
530
531
  dom, {raw: true, marks: view.markCursor} as any)}
531
532
  } else {
532
533
  view.cursorWrapper = null
package/src/input.ts CHANGED
@@ -183,6 +183,7 @@ function runHandlerOnContext(
183
183
 
184
184
  function updateSelection(view: EditorView, selection: Selection, origin: string) {
185
185
  if (!view.focused) view.focus()
186
+ if (view.state.selection.eq(selection)) return
186
187
  let tr = view.state.tr.setSelection(selection)
187
188
  if (origin == "pointer") tr.setMeta("pointer", true)
188
189
  view.dispatch(tr)
@@ -453,8 +454,8 @@ const timeoutComposition = browser.android ? 5000 : -1
453
454
  editHandlers.compositionstart = editHandlers.compositionupdate = view => {
454
455
  if (!view.composing) {
455
456
  view.domObserver.flush()
456
- let {state} = view, $pos = state.selection.$from
457
- if (state.selection.empty &&
457
+ let {state} = view, $pos = state.selection.$to
458
+ if (state.selection instanceof TextSelection &&
458
459
  (state.storedMarks ||
459
460
  (!$pos.textOffset && $pos.parentOffset && $pos.nodeBefore!.marks.some(m => m.type.spec.inclusive === false)))) {
460
461
  // Need to wrap the cursor in mark nodes different from the ones in the DOM context
@@ -472,7 +473,8 @@ editHandlers.compositionstart = editHandlers.compositionupdate = view => {
472
473
  let before = offset < 0 ? node.lastChild : node.childNodes[offset - 1]
473
474
  if (!before) break
474
475
  if (before.nodeType == 3) {
475
- view.domSelection().collapse(before, before.nodeValue!.length)
476
+ let sel = view.domSelection()
477
+ if (sel) sel.collapse(before, before.nodeValue!.length)
476
478
  break
477
479
  } else {
478
480
  node = before
package/src/selection.ts CHANGED
@@ -136,10 +136,17 @@ function removeClassOnSelectionChange(view: EditorView) {
136
136
 
137
137
  function selectCursorWrapper(view: EditorView) {
138
138
  let domSel = view.domSelection(), range = document.createRange()
139
+ if (!domSel) return
139
140
  let node = view.cursorWrapper!.dom, img = node.nodeName == "IMG"
140
- if (img) range.setEnd(node.parentNode!, domIndex(node) + 1)
141
- else range.setEnd(node, 0)
142
- range.collapse(false)
141
+ if (img) range.setStart(node.parentNode!, domIndex(node) + 1)
142
+ else range.setStart(node, 0)
143
+ let sel = view.state.selection
144
+ if (sel.empty) {
145
+ range.collapse(true)
146
+ } else {
147
+ let end = view.domAtPos(sel.to)
148
+ range.setEnd(end.node, end.offset)
149
+ }
143
150
  domSel.removeAllRanges()
144
151
  domSel.addRange(range)
145
152
  // Kludge to kill 'control selection' in IE11 when selecting an