prosemirror-view 1.41.3 → 1.41.4

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.41.4 (2025-12-01)
2
+
3
+ ### Bug fixes
4
+
5
+ `EditorView.dragging` is now only cleared after the drop event has entirely been handled.
6
+
7
+ Add a workaround for an issue where Chrome and Safari will, if you backspace out the last text before an inline-flex node, insert a bogus hard break.
8
+
9
+ Fix a composition problem on Chrome Windows when starting a composition before an absolutely positioned widget.
10
+
1
11
  ## 1.41.3 (2025-10-09)
2
12
 
3
13
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -360,18 +360,23 @@ function findOffsetInNode(node, coords) {
360
360
  }
361
361
  function findOffsetInText(node, coords) {
362
362
  var len = node.nodeValue.length;
363
- var range = document.createRange();
363
+ var range = document.createRange(),
364
+ result;
364
365
  for (var i = 0; i < len; i++) {
365
366
  range.setEnd(node, i + 1);
366
367
  range.setStart(node, i);
367
368
  var rect = singleRect(range, 1);
368
369
  if (rect.top == rect.bottom) continue;
369
- if (inRect(coords, rect)) return {
370
- node: node,
371
- offset: i + (coords.left >= (rect.left + rect.right) / 2 ? 1 : 0)
372
- };
370
+ if (inRect(coords, rect)) {
371
+ result = {
372
+ node: node,
373
+ offset: i + (coords.left >= (rect.left + rect.right) / 2 ? 1 : 0)
374
+ };
375
+ break;
376
+ }
373
377
  }
374
- return {
378
+ range.detach();
379
+ return result || {
375
380
  node: node,
376
381
  offset: 0
377
382
  };
@@ -3272,7 +3277,7 @@ editHandlers.compositionstart = editHandlers.compositionupdate = function (view)
3272
3277
  $pos = state.selection.$to;
3273
3278
  if (state.selection instanceof prosemirrorState.TextSelection && (state.storedMarks || !$pos.textOffset && $pos.parentOffset && $pos.nodeBefore.marks.some(function (m) {
3274
3279
  return m.type.spec.inclusive === false;
3275
- }))) {
3280
+ }) || chrome && windows && selectionBeforeUneditable(view))) {
3276
3281
  view.markCursor = view.state.storedMarks || $pos.marks();
3277
3282
  endComposition(view, true);
3278
3283
  view.markCursor = null;
@@ -3298,6 +3303,14 @@ editHandlers.compositionstart = editHandlers.compositionupdate = function (view)
3298
3303
  }
3299
3304
  scheduleComposeEnd(view, timeoutComposition);
3300
3305
  };
3306
+ function selectionBeforeUneditable(view) {
3307
+ var _view$domSelectionRan4 = view.domSelectionRange(),
3308
+ focusNode = _view$domSelectionRan4.focusNode,
3309
+ focusOffset = _view$domSelectionRan4.focusOffset;
3310
+ if (!focusNode || focusNode.nodeType != 1 || focusOffset >= focusNode.childNodes.length) return false;
3311
+ var next = focusNode.childNodes[focusOffset];
3312
+ return next.nodeType == 1 && next.contentEditable == "false";
3313
+ }
3301
3314
  editHandlers.compositionend = function (view, event) {
3302
3315
  if (view.composing) {
3303
3316
  view.input.composing = false;
@@ -3485,10 +3498,14 @@ handlers.dragend = function (view) {
3485
3498
  editHandlers.dragover = editHandlers.dragenter = function (_, e) {
3486
3499
  return e.preventDefault();
3487
3500
  };
3488
- editHandlers.drop = function (view, _event) {
3489
- var event = _event;
3490
- var dragging = view.dragging;
3491
- view.dragging = null;
3501
+ editHandlers.drop = function (view, event) {
3502
+ try {
3503
+ handleDrop(view, event, view.dragging);
3504
+ } finally {
3505
+ view.dragging = null;
3506
+ }
3507
+ };
3508
+ function handleDrop(view, event, dragging) {
3492
3509
  if (!event.dataTransfer) return;
3493
3510
  var eventPos = view.posAtCoords(eventCoords(event));
3494
3511
  if (!eventPos) return;
@@ -3534,7 +3551,7 @@ editHandlers.drop = function (view, _event) {
3534
3551
  }
3535
3552
  view.focus();
3536
3553
  view.dispatch(tr.setMeta("uiEvent", "drop"));
3537
- };
3554
+ }
3538
3555
  handlers.focus = function (view) {
3539
3556
  view.input.lastFocus = Date.now();
3540
3557
  if (!view.focused) {
@@ -4399,6 +4416,24 @@ var DOMObserver = function () {
4399
4416
  _iterator3.f();
4400
4417
  }
4401
4418
  }
4419
+ } else if ((chrome || safari) && added.some(function (n) {
4420
+ return n.nodeName == "BR";
4421
+ }) && (view.input.lastKeyCode == 8 || view.input.lastKeyCode == 46)) {
4422
+ var _iterator4 = _createForOfIteratorHelper(added),
4423
+ _step4;
4424
+ try {
4425
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
4426
+ var node = _step4.value;
4427
+ if (node.nodeName == "BR" && node.parentNode) {
4428
+ var after = node.nextSibling;
4429
+ if (after && after.nodeType == 1 && after.contentEditable == "false") node.parentNode.removeChild(node);
4430
+ }
4431
+ }
4432
+ } catch (err) {
4433
+ _iterator4.e(err);
4434
+ } finally {
4435
+ _iterator4.f();
4436
+ }
4402
4437
  }
4403
4438
  var readSel = null;
4404
4439
  if (from < 0 && newSel && view.input.lastFocus > Date.now() - 200 && Math.max(view.input.lastTouch, view.input.lastClick.time) < Date.now() - 300 && selectionCollapsed(sel) && (readSel = selectionFromDOM(view)) && readSel.eq(prosemirrorState.Selection.near(view.state.doc.resolve(0), 1))) {
package/dist/index.js CHANGED
@@ -367,17 +367,20 @@ function findOffsetInNode(node, coords) {
367
367
  }
368
368
  function findOffsetInText(node, coords) {
369
369
  let len = node.nodeValue.length;
370
- let range = document.createRange();
370
+ let range = document.createRange(), result;
371
371
  for (let i = 0; i < len; i++) {
372
372
  range.setEnd(node, i + 1);
373
373
  range.setStart(node, i);
374
374
  let rect = singleRect(range, 1);
375
375
  if (rect.top == rect.bottom)
376
376
  continue;
377
- if (inRect(coords, rect))
378
- return { node, offset: i + (coords.left >= (rect.left + rect.right) / 2 ? 1 : 0) };
377
+ if (inRect(coords, rect)) {
378
+ result = { node, offset: i + (coords.left >= (rect.left + rect.right) / 2 ? 1 : 0) };
379
+ break;
380
+ }
379
381
  }
380
- return { node, offset: 0 };
382
+ range.detach();
383
+ return result || { node, offset: 0 };
381
384
  }
382
385
  function inRect(coords, rect) {
383
386
  return coords.left >= rect.left - 1 && coords.left <= rect.right + 1 &&
@@ -3471,7 +3474,8 @@ editHandlers.compositionstart = editHandlers.compositionupdate = view => {
3471
3474
  let { state } = view, $pos = state.selection.$to;
3472
3475
  if (state.selection instanceof TextSelection &&
3473
3476
  (state.storedMarks ||
3474
- (!$pos.textOffset && $pos.parentOffset && $pos.nodeBefore.marks.some(m => m.type.spec.inclusive === false)))) {
3477
+ (!$pos.textOffset && $pos.parentOffset && $pos.nodeBefore.marks.some(m => m.type.spec.inclusive === false)) ||
3478
+ chrome && windows && selectionBeforeUneditable(view))) { // Issue #1500
3475
3479
  // Need to wrap the cursor in mark nodes different from the ones in the DOM context
3476
3480
  view.markCursor = view.state.storedMarks || $pos.marks();
3477
3481
  endComposition(view, true);
@@ -3505,6 +3509,13 @@ editHandlers.compositionstart = editHandlers.compositionupdate = view => {
3505
3509
  }
3506
3510
  scheduleComposeEnd(view, timeoutComposition);
3507
3511
  };
3512
+ function selectionBeforeUneditable(view) {
3513
+ let { focusNode, focusOffset } = view.domSelectionRange();
3514
+ if (!focusNode || focusNode.nodeType != 1 || focusOffset >= focusNode.childNodes.length)
3515
+ return false;
3516
+ let next = focusNode.childNodes[focusOffset];
3517
+ return next.nodeType == 1 && next.contentEditable == "false";
3518
+ }
3508
3519
  editHandlers.compositionend = (view, event) => {
3509
3520
  if (view.composing) {
3510
3521
  view.input.composing = false;
@@ -3732,10 +3743,15 @@ handlers.dragend = view => {
3732
3743
  }, 50);
3733
3744
  };
3734
3745
  editHandlers.dragover = editHandlers.dragenter = (_, e) => e.preventDefault();
3735
- editHandlers.drop = (view, _event) => {
3736
- let event = _event;
3737
- let dragging = view.dragging;
3738
- view.dragging = null;
3746
+ editHandlers.drop = (view, event) => {
3747
+ try {
3748
+ handleDrop(view, event, view.dragging);
3749
+ }
3750
+ finally {
3751
+ view.dragging = null;
3752
+ }
3753
+ };
3754
+ function handleDrop(view, event, dragging) {
3739
3755
  if (!event.dataTransfer)
3740
3756
  return;
3741
3757
  let eventPos = view.posAtCoords(eventCoords(event));
@@ -3789,7 +3805,7 @@ editHandlers.drop = (view, _event) => {
3789
3805
  }
3790
3806
  view.focus();
3791
3807
  view.dispatch(tr.setMeta("uiEvent", "drop"));
3792
- };
3808
+ }
3793
3809
  handlers.focus = view => {
3794
3810
  view.input.lastFocus = Date.now();
3795
3811
  if (!view.focused) {
@@ -4715,6 +4731,17 @@ class DOMObserver {
4715
4731
  }
4716
4732
  }
4717
4733
  }
4734
+ else if ((chrome || safari) && added.some(n => n.nodeName == "BR") &&
4735
+ (view.input.lastKeyCode == 8 || view.input.lastKeyCode == 46)) {
4736
+ // Chrome/Safari sometimes insert a bogus break node if you
4737
+ // backspace out the last bit of text before an inline-flex node (#1552)
4738
+ for (let node of added)
4739
+ if (node.nodeName == "BR" && node.parentNode) {
4740
+ let after = node.nextSibling;
4741
+ if (after && after.nodeType == 1 && after.contentEditable == "false")
4742
+ node.parentNode.removeChild(node);
4743
+ }
4744
+ }
4718
4745
  let readSel = null;
4719
4746
  // If it looks like the browser has reset the selection to the
4720
4747
  // start of the document after focus, restore the selection from
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prosemirror-view",
3
- "version": "1.41.3",
3
+ "version": "1.41.4",
4
4
  "description": "ProseMirror's view component",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
package/src/dom.ts CHANGED
@@ -30,7 +30,7 @@ export const textRange = function(node: Text, from?: number, to?: number) {
30
30
  }
31
31
 
32
32
  export const clearReusedRange = function() {
33
- reusedRange = null;
33
+ reusedRange = null
34
34
  }
35
35
 
36
36
  // Scans forward and backward through DOM positions equivalent to the
package/src/domchange.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import {Fragment, DOMParser, TagParseRule, Node, Mark, ResolvedPos} from "prosemirror-model"
2
- import {Selection, TextSelection, Transaction} from "prosemirror-state"
2
+ import {TextSelection, Transaction} from "prosemirror-state"
3
3
 
4
4
  import {selectionBetween, selectionFromDOM, selectionToDOM} from "./selection"
5
5
  import {selectionCollapsed, keyEvent, DOMNode} from "./dom"
package/src/domcoords.ts CHANGED
@@ -184,16 +184,19 @@ function findOffsetInNode(node: HTMLElement, coords: {top: number, left: number}
184
184
 
185
185
  function findOffsetInText(node: Text, coords: {top: number, left: number}) {
186
186
  let len = node.nodeValue!.length
187
- let range = document.createRange()
187
+ let range = document.createRange(), result: {node: Node, offset: number} | undefined
188
188
  for (let i = 0; i < len; i++) {
189
189
  range.setEnd(node, i + 1)
190
190
  range.setStart(node, i)
191
191
  let rect = singleRect(range, 1)
192
192
  if (rect.top == rect.bottom) continue
193
- if (inRect(coords, rect))
194
- return {node, offset: i + (coords.left >= (rect.left + rect.right) / 2 ? 1 : 0)}
193
+ if (inRect(coords, rect)) {
194
+ result = {node, offset: i + (coords.left >= (rect.left + rect.right) / 2 ? 1 : 0)}
195
+ break
196
+ }
195
197
  }
196
- return {node, offset: 0}
198
+ range.detach()
199
+ return result || {node, offset: 0}
197
200
  }
198
201
 
199
202
  function inRect(coords: {top: number, left: number}, rect: Rect) {
@@ -198,6 +198,15 @@ export class DOMObserver {
198
198
  br.remove()
199
199
  }
200
200
  }
201
+ } else if ((browser.chrome || browser.safari) && added.some(n => n.nodeName == "BR") &&
202
+ (view.input.lastKeyCode == 8 || view.input.lastKeyCode == 46)) {
203
+ // Chrome/Safari sometimes insert a bogus break node if you
204
+ // backspace out the last bit of text before an inline-flex node (#1552)
205
+ for (let node of added) if (node.nodeName == "BR" && node.parentNode) {
206
+ let after = node.nextSibling
207
+ if (after && after.nodeType == 1 && (after as HTMLElement).contentEditable == "false")
208
+ node.parentNode.removeChild(node)
209
+ }
201
210
  }
202
211
 
203
212
  let readSel: Selection | null = null
package/src/index.ts CHANGED
@@ -469,7 +469,7 @@ export class EditorView {
469
469
  }
470
470
  this.docView.destroy()
471
471
  ;(this as any).docView = null
472
- clearReusedRange();
472
+ clearReusedRange()
473
473
  }
474
474
 
475
475
  /// This is true when the view has been
package/src/input.ts CHANGED
@@ -459,7 +459,8 @@ editHandlers.compositionstart = editHandlers.compositionupdate = view => {
459
459
  let {state} = view, $pos = state.selection.$to
460
460
  if (state.selection instanceof TextSelection &&
461
461
  (state.storedMarks ||
462
- (!$pos.textOffset && $pos.parentOffset && $pos.nodeBefore!.marks.some(m => m.type.spec.inclusive === false)))) {
462
+ (!$pos.textOffset && $pos.parentOffset && $pos.nodeBefore!.marks.some(m => m.type.spec.inclusive === false)) ||
463
+ browser.chrome && browser.windows && selectionBeforeUneditable(view))) { // Issue #1500
463
464
  // Need to wrap the cursor in mark nodes different from the ones in the DOM context
464
465
  view.markCursor = view.state.storedMarks || $pos.marks()
465
466
  endComposition(view, true)
@@ -490,6 +491,13 @@ editHandlers.compositionstart = editHandlers.compositionupdate = view => {
490
491
  scheduleComposeEnd(view, timeoutComposition)
491
492
  }
492
493
 
494
+ function selectionBeforeUneditable(view: EditorView) {
495
+ let {focusNode, focusOffset} = view.domSelectionRange()
496
+ if (!focusNode || focusNode.nodeType != 1 || focusOffset >= focusNode.childNodes.length) return false
497
+ let next = focusNode.childNodes[focusOffset]
498
+ return next.nodeType == 1 && (next as HTMLElement).contentEditable == "false"
499
+ }
500
+
493
501
  editHandlers.compositionend = (view, event) => {
494
502
  if (view.composing) {
495
503
  view.input.composing = false
@@ -706,11 +714,15 @@ handlers.dragend = view => {
706
714
 
707
715
  editHandlers.dragover = editHandlers.dragenter = (_, e) => e.preventDefault()
708
716
 
709
- editHandlers.drop = (view, _event) => {
710
- let event = _event as DragEvent
711
- let dragging = view.dragging
712
- view.dragging = null
717
+ editHandlers.drop = (view, event) => {
718
+ try {
719
+ handleDrop(view, event as DragEvent, view.dragging)
720
+ } finally {
721
+ view.dragging = null
722
+ }
723
+ }
713
724
 
725
+ function handleDrop(view: EditorView, event: DragEvent, dragging: Dragging | null) {
714
726
  if (!event.dataTransfer) return
715
727
 
716
728
  let eventPos = view.posAtCoords(eventCoords(event))