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/CHANGELOG.md +18 -2
- package/CONTRIBUTING.md +19 -19
- package/README.md +3 -3
- package/dist/index.cjs +164 -96
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +105 -64
- package/package.json +3 -3
- package/src/domchange.ts +3 -13
- package/src/domobserver.ts +4 -1
- package/src/index.ts +4 -3
- package/src/input.ts +94 -39
- package/src/selection.ts +6 -6
- package/src/viewdesc.ts +8 -8
- package/style/prosemirror.css +1 -1
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
|
-
|
|
264
|
-
else if (NodeSelection.isSelectable(node))
|
|
265
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(() =>
|
|
371
|
-
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
|
677
|
-
|
|
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://
|
|
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
|
-
//
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
if (!force &&
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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,
|
|
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:
|
|
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
|
}
|
package/style/prosemirror.css
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
.ProseMirror-hideselection *::-moz-selection { background: transparent; }
|
|
24
24
|
.ProseMirror-hideselection { caret-color: transparent; }
|
|
25
25
|
|
|
26
|
-
/* See https://
|
|
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 {
|