prosemirror-view 1.41.7 → 1.41.9

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/src/input.ts CHANGED
@@ -66,6 +66,7 @@ function setSelectionOrigin(view: EditorView, origin: string) {
66
66
  }
67
67
 
68
68
  export function destroyInput(view: EditorView) {
69
+ if (view.input.mouseDown) view.input.mouseDown.done()
69
70
  view.domObserver.stop()
70
71
  for (let type in view.input.eventHandlers)
71
72
  view.dom.removeEventListener(type, view.input.eventHandlers[type])
@@ -246,27 +247,28 @@ function handleTripleClick(view: EditorView, pos: number, inside: number, event:
246
247
 
247
248
  function defaultTripleClick(view: EditorView, inside: number, event: MouseEvent) {
248
249
  if (event.button != 0) return false
250
+ let selection = selectionForTripleClick(view, inside, true), doc = view.state.doc
251
+ if (!selection) return false
252
+ updateSelection(view, selection, "pointer")
253
+ if (selection instanceof TextSelection && doc.eq(view.state.doc))
254
+ view.input.mouseDown = new TripleClickDrag(view, selection)
255
+ return true
256
+ }
257
+
258
+ function selectionForTripleClick(view: EditorView, inside: number, selectNodes: boolean): Selection | null {
249
259
  let doc = view.state.doc
250
- if (inside == -1) {
251
- if (doc.inlineContent) {
252
- updateSelection(view, TextSelection.create(doc, 0, doc.content.size), "pointer")
253
- return true
254
- }
255
- return false
256
- }
260
+ if (inside == -1) return doc.inlineContent ? TextSelection.create(doc, 0, doc.content.size) : null
257
261
 
258
262
  let $pos = doc.resolve(inside)
259
263
  for (let i = $pos.depth + 1; i > 0; i--) {
260
264
  let node = i > $pos.depth ? $pos.nodeAfter! : $pos.node(i)
261
265
  let nodePos = $pos.before(i)
262
266
  if (node.inlineContent)
263
- updateSelection(view, TextSelection.create(doc, nodePos + 1, nodePos + 1 + node.content.size), "pointer")
264
- else if (NodeSelection.isSelectable(node))
265
- updateSelection(view, NodeSelection.create(doc, nodePos), "pointer")
266
- else
267
- continue
268
- return true
267
+ return TextSelection.create(doc, nodePos + 1, nodePos + 1 + node.content.size)
268
+ else if (selectNodes && NodeSelection.isSelectable(node))
269
+ return NodeSelection.create(doc, nodePos)
269
270
  }
271
+ return null
270
272
  }
271
273
 
272
274
  function forceDOMFlush(view: EditorView) {
@@ -287,12 +289,13 @@ handlers.mousedown = (view, _event) => {
287
289
  }
288
290
  view.input.lastClick = {time: now, x: event.clientX, y: event.clientY, type, button: event.button}
289
291
 
292
+ if (view.input.mouseDown) view.input.mouseDown.done()
293
+
290
294
  let pos = view.posAtCoords(eventCoords(event))
291
295
  if (!pos) return
292
296
 
293
297
  if (type == "singleClick") {
294
- if (view.input.mouseDown) view.input.mouseDown.done()
295
- view.input.mouseDown = new MouseDown(view, pos, event, !!flushed)
298
+ view.input.mouseDown = new LeftMouseDown(view, pos, event, !!flushed)
296
299
  } else if ((type == "doubleClick" ? handleDoubleClick : handleTripleClick)(view, pos.pos, pos.inside, event)) {
297
300
  event.preventDefault()
298
301
  } else {
@@ -300,20 +303,46 @@ handlers.mousedown = (view, _event) => {
300
303
  }
301
304
  }
302
305
 
303
- class MouseDown {
306
+
307
+ abstract class MouseDown {
308
+ mightDrag: {node: Node, pos: number, addAttr: boolean, setUneditable: boolean} | null = null
309
+
310
+ constructor(readonly view: EditorView) {
311
+ view.root.addEventListener("mouseup", this.up = this.up.bind(this) as any)
312
+ view.root.addEventListener("mousemove", this.move = this.move.bind(this) as any)
313
+ }
314
+
315
+ up(event: MouseEvent) {
316
+ this.done()
317
+ }
318
+
319
+ move(event: MouseEvent) {
320
+ if (event.buttons == 0) this.done()
321
+ }
322
+
323
+ done() {
324
+ this.view.root.removeEventListener("mouseup", this.up as any)
325
+ this.view.root.removeEventListener("mousemove", this.move as any)
326
+ if (this.view.input.mouseDown == this) this.view.input.mouseDown = null
327
+ }
328
+
329
+ delaySelUpdate() { return false }
330
+ }
331
+
332
+ class LeftMouseDown extends MouseDown {
304
333
  startDoc: Node
305
334
  selectNode: boolean
306
335
  allowDefault: boolean
307
336
  delayedSelectionSync = false
308
- mightDrag: {node: Node, pos: number, addAttr: boolean, setUneditable: boolean} | null = null
309
337
  target: HTMLElement | null
310
338
 
311
339
  constructor(
312
- readonly view: EditorView,
340
+ view: EditorView,
313
341
  readonly pos: {pos: number, inside: number},
314
342
  readonly event: MouseEvent,
315
343
  readonly flushed: boolean
316
344
  ) {
345
+ super(view)
317
346
  this.startDoc = view.state.doc
318
347
  this.selectNode = !!event[selectNodeModifier]
319
348
  this.allowDefault = event.shiftKey
@@ -334,8 +363,8 @@ class MouseDown {
334
363
 
335
364
  let {selection} = view.state
336
365
  if (event.button == 0 &&
337
- targetNode.type.spec.draggable && targetNode.type.spec.selectable !== false ||
338
- selection instanceof NodeSelection && selection.from <= targetPos && selection.to > targetPos)
366
+ (targetNode.type.spec.draggable && targetNode.type.spec.selectable !== false ||
367
+ selection instanceof NodeSelection && selection.from <= targetPos && selection.to > targetPos))
339
368
  this.mightDrag = {
340
369
  node: targetNode,
341
370
  pos: targetPos,
@@ -353,22 +382,20 @@ class MouseDown {
353
382
  this.view.domObserver.start()
354
383
  }
355
384
 
356
- view.root.addEventListener("mouseup", this.up = this.up.bind(this) as any)
357
- view.root.addEventListener("mousemove", this.move = this.move.bind(this) as any)
358
385
  setSelectionOrigin(view, "pointer")
359
386
  }
360
387
 
361
388
  done() {
362
- this.view.root.removeEventListener("mouseup", this.up as any)
363
- this.view.root.removeEventListener("mousemove", this.move as any)
389
+ super.done()
364
390
  if (this.mightDrag && this.target) {
365
391
  this.view.domObserver.stop()
366
392
  if (this.mightDrag.addAttr) this.target.removeAttribute("draggable")
367
393
  if (this.mightDrag.setUneditable) this.target.removeAttribute("contentEditable")
368
394
  this.view.domObserver.start()
369
395
  }
370
- if (this.delayedSelectionSync) setTimeout(() => selectionToDOM(this.view))
371
- this.view.input.mouseDown = null
396
+ if (this.delayedSelectionSync) setTimeout(() => {
397
+ if (!this.view.isDestroyed) selectionToDOM(this.view)
398
+ })
372
399
  }
373
400
 
374
401
  up(event: MouseEvent) {
@@ -409,7 +436,7 @@ class MouseDown {
409
436
  move(event: MouseEvent) {
410
437
  this.updateAllowDefault(event)
411
438
  setSelectionOrigin(this.view, "pointer")
412
- if (event.buttons == 0) this.done()
439
+ super.move(event)
413
440
  }
414
441
 
415
442
  updateAllowDefault(event: MouseEvent) {
@@ -417,6 +444,39 @@ class MouseDown {
417
444
  Math.abs(this.event.y - event.clientY) > 4))
418
445
  this.allowDefault = true
419
446
  }
447
+
448
+ delaySelUpdate() {
449
+ if (!this.allowDefault) return false
450
+ this.delayedSelectionSync = true
451
+ return true
452
+ }
453
+ }
454
+
455
+ class TripleClickDrag extends MouseDown {
456
+ startDoc: Node
457
+
458
+ constructor(view: EditorView, readonly startSelection: TextSelection) {
459
+ super(view)
460
+ this.startDoc = view.state.doc
461
+ }
462
+
463
+ move(event: MouseEvent) {
464
+ if (event.buttons == 0 || this.view.isDestroyed || !this.view.state.doc.eq(this.startDoc)) {
465
+ this.done()
466
+ return
467
+ }
468
+
469
+ event.preventDefault()
470
+ setSelectionOrigin(this.view, "pointer")
471
+
472
+ let pos = this.view.posAtCoords(eventCoords(event))
473
+ let target = pos && selectionForTripleClick(this.view, pos.inside, false)
474
+ if (!target) return
475
+
476
+ let {doc} = this.view.state, start = this.startSelection
477
+ let [anchor, head] = target.from < start.from ? [start.to, target.from] : [start.from, target.to]
478
+ updateSelection(this.view, TextSelection.create(doc, anchor, head), "pointer")
479
+ }
420
480
  }
421
481
 
422
482
  handlers.touchstart = view => {
@@ -444,7 +504,7 @@ function inOrNearComposition(view: EditorView, event: Event) {
444
504
  // This guards against the case where compositionend is triggered without the keyboard
445
505
  // (e.g. character confirmation may be done with the mouse), and keydown is triggered
446
506
  // afterwards- we wouldn't want to ignore the keydown event in this case.
447
- if (browser.safari && Math.abs(event.timeStamp - view.input.compositionEndedAt) < 500) {
507
+ if (browser.safari && Math.abs(Date.now() - view.input.compositionEndedAt) < 500) {
448
508
  view.input.compositionEndedAt = -2e8
449
509
  return true
450
510
  }
@@ -502,7 +562,7 @@ function selectionBeforeUneditable(view: EditorView) {
502
562
  editHandlers.compositionend = (view, event) => {
503
563
  if (view.composing) {
504
564
  view.input.composing = false
505
- view.input.compositionEndedAt = event.timeStamp
565
+ view.input.compositionEndedAt = Date.now()
506
566
  view.input.compositionPendingChanges = view.domObserver.pendingRecords().length ? view.input.compositionID : 0
507
567
  view.input.compositionNode = null
508
568
  if (view.input.badSafariComposition) view.domObserver.forceFlush()
@@ -520,7 +580,7 @@ function scheduleComposeEnd(view: EditorView, delay: number) {
520
580
  export function clearComposition(view: EditorView) {
521
581
  if (view.composing) {
522
582
  view.input.composing = false
523
- view.input.compositionEndedAt = timestampFromCustomEvent()
583
+ view.input.compositionEndedAt = Date.now()
524
584
  }
525
585
  while (view.input.compositionNodes.length > 0) view.input.compositionNodes.pop()!.markParentsDirty()
526
586
  }
@@ -544,12 +604,6 @@ export function findCompositionNode(view: EditorView) {
544
604
  return textBefore || textAfter
545
605
  }
546
606
 
547
- function timestampFromCustomEvent() {
548
- let event = document.createEvent("Event")
549
- event.initEvent("event", true, true)
550
- return event.timeStamp
551
- }
552
-
553
607
  /// @internal
554
608
  export function endComposition(view: EditorView, restarting = false) {
555
609
  if (browser.android && view.domObserver.flushingSoon >= 0) return
@@ -673,8 +727,9 @@ export class Dragging {
673
727
  const dragCopyModifier: keyof DragEvent = browser.mac ? "altKey" : "ctrlKey"
674
728
 
675
729
  function dragMoves(view: EditorView, event: DragEvent) {
676
- let moves = view.someProp("dragCopies", test => !test(event))
677
- return moves != null ? moves : !event[dragCopyModifier]
730
+ let copy: boolean | undefined
731
+ view.someProp("dragCopies", test => { copy = copy || test(event) })
732
+ return copy != null ? !copy : !event[dragCopyModifier]
678
733
  }
679
734
 
680
735
  handlers.dragstart = (view, _event) => {
@@ -701,7 +756,7 @@ handlers.dragstart = (view, _event) => {
701
756
  if (!event.dataTransfer.files.length || !browser.chrome || browser.chrome_version > 120)
702
757
  event.dataTransfer.clearData()
703
758
  event.dataTransfer.setData(brokenClipboardAPI ? "Text" : "text/html", dom.innerHTML)
704
- // See https://github.com/ProseMirror/prosemirror/issues/1156
759
+ // See https://code.haverbeke.berlin/prosemirror/prosemirror/issues/1156
705
760
  event.dataTransfer.effectAllowed = "copyMove"
706
761
  if (!brokenClipboardAPI) event.dataTransfer.setData("text/plain", text)
707
762
  view.dragging = new Dragging(slice, dragMoves(view, event), node)
package/src/selection.ts CHANGED
@@ -58,15 +58,15 @@ export function selectionToDOM(view: EditorView, force = false) {
58
58
 
59
59
  if (!editorOwnsSelection(view)) return
60
60
 
61
- // The delayed drag selection causes issues with Cell Selections
62
- // in Safari. And the drag selection delay is to workarond issues
63
- // which only present in Chrome.
64
- if (!force && view.input.mouseDown && view.input.mouseDown.allowDefault && browser.chrome) {
61
+ // Need to delay selection normalization during a native selection
62
+ // drag on Chrome, or it will cause further dragging to glitch.
63
+ let mouseDown = view.input.mouseDown
64
+ if (!force && browser.chrome && mouseDown) {
65
65
  let domSel = view.domSelectionRange(), curSel = view.domObserver.currentSelection
66
66
  if (domSel.anchorNode && curSel.anchorNode &&
67
67
  isEquivalentPosition(domSel.anchorNode, domSel.anchorOffset,
68
- curSel.anchorNode, curSel.anchorOffset)) {
69
- view.input.mouseDown.delayedSelectionSync = true
68
+ curSel.anchorNode, curSel.anchorOffset) &&
69
+ mouseDown.delaySelUpdate()) {
70
70
  view.domObserver.setCurSelection()
71
71
  return
72
72
  }
package/src/viewdesc.ts CHANGED
@@ -30,7 +30,7 @@ export type ViewMutationRecord = MutationRecord | { type: "selection", target: D
30
30
  /// Objects returned as node views must conform to this interface.
31
31
  export interface NodeView {
32
32
  /// The outer DOM node that represents the document node.
33
- dom: DOMNode
33
+ dom: HTMLElement
34
34
 
35
35
  /// The DOM node that should hold the node's content. Only meaningful
36
36
  /// if the node view also defines a `dom` property and if its node
@@ -99,7 +99,7 @@ export interface NodeView {
99
99
  /// Objects returned as mark views must conform to this interface.
100
100
  export interface MarkView {
101
101
  /// The outer DOM node that represents the document node.
102
- dom: DOMNode
102
+ dom: HTMLElement
103
103
 
104
104
  /// The DOM node that should hold the mark's content. When this is not
105
105
  /// present, the `dom` property is used as the content DOM.
@@ -610,7 +610,7 @@ class CompositionViewDesc extends ViewDesc {
610
610
  // some cases they will be split more often than would appear
611
611
  // necessary.
612
612
  class MarkViewDesc extends ViewDesc {
613
- constructor(parent: ViewDesc, readonly mark: Mark, dom: DOMNode, contentDOM: HTMLElement, readonly spec: MarkView) {
613
+ constructor(parent: ViewDesc, readonly mark: Mark, dom: HTMLElement, contentDOM: HTMLElement, readonly spec: MarkView) {
614
614
  super(parent, [], dom, contentDOM)
615
615
  }
616
616
 
@@ -697,13 +697,13 @@ export class NodeViewDesc extends ViewDesc {
697
697
  if (descObj.parent) return descObj.parent.posBeforeChild(descObj)
698
698
  }, outerDeco, innerDeco)
699
699
 
700
- let dom = spec && spec.dom, contentDOM = spec && spec.contentDOM
700
+ let dom: DOMNode | undefined = spec && spec.dom, contentDOM = spec && spec.contentDOM
701
701
  if (node.isText) {
702
702
  if (!dom) dom = document.createTextNode(node.text!)
703
703
  else if (dom.nodeType != 3) throw new RangeError("Text must be rendered as a DOM text node")
704
704
  } else if (!dom) {
705
705
  let spec = (DOMSerializer.renderSpec as any)(document, node.type.spec.toDOM!(node), null, node.attrs)
706
- ;({dom, contentDOM} = spec as {dom: DOMNode, contentDOM?: HTMLElement})
706
+ ;({dom, contentDOM} = spec as {dom: HTMLElement, contentDOM?: HTMLElement})
707
707
  }
708
708
  if (!contentDOM && !node.isText && dom.nodeName != "BR") { // Chrome gets confused by <br contenteditable=false>
709
709
  if (!(dom as HTMLElement).hasAttribute("contenteditable")) (dom as HTMLElement).contentEditable = "false"
@@ -714,8 +714,8 @@ export class NodeViewDesc extends ViewDesc {
714
714
  dom = applyOuterDeco(dom, outerDeco, node)
715
715
 
716
716
  if (spec)
717
- return descObj = new CustomNodeViewDesc(parent, node, outerDeco, innerDeco, dom, contentDOM || null, nodeDOM,
718
- spec, view, pos + 1)
717
+ return descObj = new CustomNodeViewDesc(parent, node, outerDeco, innerDeco, dom as HTMLElement, contentDOM || null,
718
+ nodeDOM as HTMLElement, spec, view, pos + 1)
719
719
  else if (node.isText)
720
720
  return new TextViewDesc(parent, node, outerDeco, innerDeco, dom, nodeDOM, view)
721
721
  else
@@ -985,7 +985,7 @@ class TrailingHackViewDesc extends ViewDesc {
985
985
  // customized.
986
986
  class CustomNodeViewDesc extends NodeViewDesc {
987
987
  constructor(parent: ViewDesc | undefined, node: Node, outerDeco: readonly Decoration[], innerDeco: DecorationSource,
988
- dom: DOMNode, contentDOM: HTMLElement | null, nodeDOM: DOMNode, readonly spec: NodeView,
988
+ dom: HTMLElement, contentDOM: HTMLElement | null, nodeDOM: HTMLElement, readonly spec: NodeView,
989
989
  view: EditorView, pos: number) {
990
990
  super(parent, node, outerDeco, innerDeco, dom, contentDOM, nodeDOM, view, pos)
991
991
  }
@@ -23,7 +23,7 @@
23
23
  .ProseMirror-hideselection *::-moz-selection { background: transparent; }
24
24
  .ProseMirror-hideselection { caret-color: transparent; }
25
25
 
26
- /* See https://github.com/ProseMirror/prosemirror/issues/1421#issuecomment-1759320191 */
26
+ /* See https://code.haverbeke.berlin/prosemirror/prosemirror/issues/1421#issuecomment-1759320191 */
27
27
  .ProseMirror [draggable][contenteditable=false] { user-select: text }
28
28
 
29
29
  .ProseMirror-selectednode {