prosemirror-view 1.26.5 → 1.27.2
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 +28 -0
- package/dist/index.cjs +124 -76
- package/dist/index.d.ts +28 -24
- package/dist/index.js +108 -59
- package/package.json +1 -1
- package/src/decoration.ts +17 -12
- package/src/domchange.ts +8 -8
- package/src/domcoords.ts +1 -1
- package/src/domobserver.ts +37 -18
- package/src/index.ts +46 -33
- package/src/input.ts +21 -7
- package/src/viewdesc.ts +4 -3
- package/dist/index.es.js +0 -5558
- package/dist/index.es.js.map +0 -1
- package/dist/index.js.map +0 -1
package/src/index.ts
CHANGED
|
@@ -53,6 +53,10 @@ export class EditorView {
|
|
|
53
53
|
private pluginViews: PluginView[] = []
|
|
54
54
|
/// @internal
|
|
55
55
|
domObserver!: DOMObserver
|
|
56
|
+
/// Holds `true` when a hack node is needed in Firefox to prevent the
|
|
57
|
+
/// [space is eaten issue](https://github.com/ProseMirror/prosemirror/issues/651)
|
|
58
|
+
/// @internal
|
|
59
|
+
requiresGeckoHackNode: boolean = false
|
|
56
60
|
|
|
57
61
|
/// The view's current [state](#state.EditorState).
|
|
58
62
|
public state: EditorState
|
|
@@ -214,20 +218,25 @@ export class EditorView {
|
|
|
214
218
|
if (scroll == "reset") {
|
|
215
219
|
this.dom.scrollTop = 0
|
|
216
220
|
} else if (scroll == "to selection") {
|
|
217
|
-
|
|
218
|
-
if (this.someProp("handleScrollToSelection", f => f(this))) {
|
|
219
|
-
// Handled
|
|
220
|
-
} else if (state.selection instanceof NodeSelection) {
|
|
221
|
-
let target = this.docView.domAfterPos(state.selection.from)
|
|
222
|
-
if (target.nodeType == 1) scrollRectIntoView(this, (target as HTMLElement).getBoundingClientRect(), startDOM)
|
|
223
|
-
} else {
|
|
224
|
-
scrollRectIntoView(this, this.coordsAtPos(state.selection.head, 1), startDOM)
|
|
225
|
-
}
|
|
221
|
+
this.scrollToSelection()
|
|
226
222
|
} else if (oldScrollPos) {
|
|
227
223
|
resetScrollPos(oldScrollPos)
|
|
228
224
|
}
|
|
229
225
|
}
|
|
230
226
|
|
|
227
|
+
/// @internal
|
|
228
|
+
scrollToSelection() {
|
|
229
|
+
let startDOM = this.domSelection().focusNode!
|
|
230
|
+
if (this.someProp("handleScrollToSelection", f => f(this))) {
|
|
231
|
+
// Handled
|
|
232
|
+
} else if (this.state.selection instanceof NodeSelection) {
|
|
233
|
+
let target = this.docView.domAfterPos(this.state.selection.from)
|
|
234
|
+
if (target.nodeType == 1) scrollRectIntoView(this, (target as HTMLElement).getBoundingClientRect(), startDOM)
|
|
235
|
+
} else {
|
|
236
|
+
scrollRectIntoView(this, this.coordsAtPos(this.state.selection.head, 1), startDOM)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
231
240
|
private destroyPluginViews() {
|
|
232
241
|
let view
|
|
233
242
|
while (view = this.pluginViews.pop()) if (view.destroy) view.destroy()
|
|
@@ -528,7 +537,11 @@ export interface DOMEventMap extends HTMLElementEventMap {
|
|
|
528
537
|
/// searching through the plugins (in order of appearance) until one of
|
|
529
538
|
/// them returns true. For some props, the first plugin that yields a
|
|
530
539
|
/// value gets precedence.
|
|
531
|
-
|
|
540
|
+
///
|
|
541
|
+
/// The optional type parameter refers to the type of `this` in prop
|
|
542
|
+
/// functions, and is used to pass in the plugin type when defining a
|
|
543
|
+
/// [plugin](#state.Plugin).
|
|
544
|
+
export interface EditorProps<P = any> {
|
|
532
545
|
/// Can be an object mapping DOM event type names to functions that
|
|
533
546
|
/// handle them. Such functions will be called before any handling
|
|
534
547
|
/// ProseMirror does of events fired on the editable DOM element.
|
|
@@ -537,59 +550,59 @@ export interface EditorProps {
|
|
|
537
550
|
/// `preventDefault` yourself (or not, if you want to allow the
|
|
538
551
|
/// default behavior).
|
|
539
552
|
handleDOMEvents?: {
|
|
540
|
-
[event in keyof DOMEventMap]?: (view: EditorView, event: DOMEventMap[event]) => boolean | void
|
|
553
|
+
[event in keyof DOMEventMap]?: (this: P, view: EditorView, event: DOMEventMap[event]) => boolean | void
|
|
541
554
|
}
|
|
542
555
|
|
|
543
556
|
/// Called when the editor receives a `keydown` event.
|
|
544
|
-
handleKeyDown?: (view: EditorView, event: KeyboardEvent) => boolean | void
|
|
557
|
+
handleKeyDown?: (this: P, view: EditorView, event: KeyboardEvent) => boolean | void
|
|
545
558
|
|
|
546
559
|
/// Handler for `keypress` events.
|
|
547
|
-
handleKeyPress?: (view: EditorView, event: KeyboardEvent) => boolean | void
|
|
560
|
+
handleKeyPress?: (this: P, view: EditorView, event: KeyboardEvent) => boolean | void
|
|
548
561
|
|
|
549
562
|
/// Whenever the user directly input text, this handler is called
|
|
550
563
|
/// before the input is applied. If it returns `true`, the default
|
|
551
564
|
/// behavior of actually inserting the text is suppressed.
|
|
552
|
-
handleTextInput?: (view: EditorView, from: number, to: number, text: string) => boolean | void
|
|
565
|
+
handleTextInput?: (this: P, view: EditorView, from: number, to: number, text: string) => boolean | void
|
|
553
566
|
|
|
554
567
|
/// Called for each node around a click, from the inside out. The
|
|
555
568
|
/// `direct` flag will be true for the inner node.
|
|
556
|
-
handleClickOn?: (view: EditorView, pos: number, node: Node, nodePos: number, event: MouseEvent, direct: boolean) => boolean | void
|
|
569
|
+
handleClickOn?: (this: P, view: EditorView, pos: number, node: Node, nodePos: number, event: MouseEvent, direct: boolean) => boolean | void
|
|
557
570
|
|
|
558
571
|
/// Called when the editor is clicked, after `handleClickOn` handlers
|
|
559
572
|
/// have been called.
|
|
560
|
-
handleClick?: (view: EditorView, pos: number, event: MouseEvent) => boolean | void
|
|
573
|
+
handleClick?: (this: P, view: EditorView, pos: number, event: MouseEvent) => boolean | void
|
|
561
574
|
|
|
562
575
|
/// Called for each node around a double click.
|
|
563
|
-
handleDoubleClickOn?: (view: EditorView, pos: number, node: Node, nodePos: number, event: MouseEvent, direct: boolean) => boolean | void
|
|
576
|
+
handleDoubleClickOn?: (this: P, view: EditorView, pos: number, node: Node, nodePos: number, event: MouseEvent, direct: boolean) => boolean | void
|
|
564
577
|
|
|
565
578
|
/// Called when the editor is double-clicked, after `handleDoubleClickOn`.
|
|
566
|
-
handleDoubleClick?: (view: EditorView, pos: number, event: MouseEvent) => boolean | void
|
|
579
|
+
handleDoubleClick?: (this: P, view: EditorView, pos: number, event: MouseEvent) => boolean | void
|
|
567
580
|
|
|
568
581
|
/// Called for each node around a triple click.
|
|
569
|
-
handleTripleClickOn?: (view: EditorView, pos: number, node: Node, nodePos: number, event: MouseEvent, direct: boolean) => boolean | void
|
|
582
|
+
handleTripleClickOn?: (this: P, view: EditorView, pos: number, node: Node, nodePos: number, event: MouseEvent, direct: boolean) => boolean | void
|
|
570
583
|
|
|
571
584
|
/// Called when the editor is triple-clicked, after `handleTripleClickOn`.
|
|
572
|
-
handleTripleClick?: (view: EditorView, pos: number, event: MouseEvent) => boolean | void
|
|
585
|
+
handleTripleClick?: (this: P, view: EditorView, pos: number, event: MouseEvent) => boolean | void
|
|
573
586
|
|
|
574
587
|
/// Can be used to override the behavior of pasting. `slice` is the
|
|
575
588
|
/// pasted content parsed by the editor, but you can directly access
|
|
576
589
|
/// the event to get at the raw content.
|
|
577
|
-
handlePaste?: (view: EditorView, event: ClipboardEvent, slice: Slice) => boolean | void
|
|
590
|
+
handlePaste?: (this: P, view: EditorView, event: ClipboardEvent, slice: Slice) => boolean | void
|
|
578
591
|
|
|
579
592
|
/// Called when something is dropped on the editor. `moved` will be
|
|
580
593
|
/// true if this drop moves from the current selection (which should
|
|
581
594
|
/// thus be deleted).
|
|
582
|
-
handleDrop?: (view: EditorView, event: DragEvent, slice: Slice, moved: boolean) => boolean | void
|
|
595
|
+
handleDrop?: (this: P, view: EditorView, event: DragEvent, slice: Slice, moved: boolean) => boolean | void
|
|
583
596
|
|
|
584
597
|
/// Called when the view, after updating its state, tries to scroll
|
|
585
598
|
/// the selection into view. A handler function may return false to
|
|
586
599
|
/// indicate that it did not handle the scrolling and further
|
|
587
600
|
/// handlers or the default behavior should be tried.
|
|
588
|
-
handleScrollToSelection?: (view: EditorView) => boolean
|
|
601
|
+
handleScrollToSelection?: (this: P, view: EditorView) => boolean
|
|
589
602
|
|
|
590
603
|
/// Can be used to override the way a selection is created when
|
|
591
604
|
/// reading a DOM selection between the given anchor and head.
|
|
592
|
-
createSelectionBetween?: (view: EditorView, anchor: ResolvedPos, head: ResolvedPos) => Selection | null
|
|
605
|
+
createSelectionBetween?: (this: P, view: EditorView, anchor: ResolvedPos, head: ResolvedPos) => Selection | null
|
|
593
606
|
|
|
594
607
|
/// The [parser](#model.DOMParser) to use when reading editor changes
|
|
595
608
|
/// from the DOM. Defaults to calling
|
|
@@ -599,7 +612,7 @@ export interface EditorProps {
|
|
|
599
612
|
|
|
600
613
|
/// Can be used to transform pasted HTML text, _before_ it is parsed,
|
|
601
614
|
/// for example to clean it up.
|
|
602
|
-
transformPastedHTML?: (html: string) => string
|
|
615
|
+
transformPastedHTML?: (this: P, html: string) => string
|
|
603
616
|
|
|
604
617
|
/// The [parser](#model.DOMParser) to use when reading content from
|
|
605
618
|
/// the clipboard. When not given, the value of the
|
|
@@ -608,7 +621,7 @@ export interface EditorProps {
|
|
|
608
621
|
|
|
609
622
|
/// Transform pasted plain text. The `plain` flag will be true when
|
|
610
623
|
/// the text is pasted as plain text.
|
|
611
|
-
transformPastedText?: (text: string, plain: boolean) => string
|
|
624
|
+
transformPastedText?: (this: P, text: string, plain: boolean) => string
|
|
612
625
|
|
|
613
626
|
/// A function to parse text from the clipboard into a document
|
|
614
627
|
/// slice. Called after
|
|
@@ -617,11 +630,11 @@ export interface EditorProps {
|
|
|
617
630
|
/// in `<p>` tags, and call
|
|
618
631
|
/// [`clipboardParser`](#view.EditorProps.clipboardParser) on it.
|
|
619
632
|
/// The `plain` flag will be true when the text is pasted as plain text.
|
|
620
|
-
clipboardTextParser?: (text: string, $context: ResolvedPos, plain: boolean) => Slice
|
|
633
|
+
clipboardTextParser?: (this: P, text: string, $context: ResolvedPos, plain: boolean) => Slice
|
|
621
634
|
|
|
622
|
-
/// Can be used to transform pasted
|
|
623
|
-
/// the document.
|
|
624
|
-
transformPasted?: (slice: Slice) => Slice
|
|
635
|
+
/// Can be used to transform pasted or dragged-and-dropped content
|
|
636
|
+
/// before it is applied to the document.
|
|
637
|
+
transformPasted?: (this: P, slice: Slice) => Slice
|
|
625
638
|
|
|
626
639
|
/// Allows you to pass custom rendering and behavior logic for
|
|
627
640
|
/// nodes. Should map node names to constructor functions that
|
|
@@ -668,15 +681,15 @@ export interface EditorProps {
|
|
|
668
681
|
/// selection when copying text to the clipboard. By default, the
|
|
669
682
|
/// editor will use [`textBetween`](#model.Node.textBetween) on the
|
|
670
683
|
/// selected range.
|
|
671
|
-
clipboardTextSerializer?: (content: Slice) => string
|
|
684
|
+
clipboardTextSerializer?: (this: P, content: Slice) => string
|
|
672
685
|
|
|
673
686
|
/// A set of [document decorations](#view.Decoration) to show in the
|
|
674
687
|
/// view.
|
|
675
|
-
decorations?: (state: EditorState) => DecorationSource | null | undefined
|
|
688
|
+
decorations?: (this: P, state: EditorState) => DecorationSource | null | undefined
|
|
676
689
|
|
|
677
690
|
/// When this returns false, the content of the view is not directly
|
|
678
691
|
/// editable.
|
|
679
|
-
editable?: (state: EditorState) => boolean
|
|
692
|
+
editable?: (this: P, state: EditorState) => boolean
|
|
680
693
|
|
|
681
694
|
/// Control the DOM attributes of the editable element. May be either
|
|
682
695
|
/// an object or a function going from an editor state to an object.
|
package/src/input.ts
CHANGED
|
@@ -13,7 +13,8 @@ import {ViewDesc} from "./viewdesc"
|
|
|
13
13
|
// A collection of DOM events that occur within the editor, and callback functions
|
|
14
14
|
// to invoke when the event fires.
|
|
15
15
|
const handlers: {[event: string]: (view: EditorView, event: Event) => void} = {}
|
|
16
|
-
|
|
16
|
+
const editHandlers: {[event: string]: (view: EditorView, event: Event) => void} = {}
|
|
17
|
+
const passiveHandlers: Record<string, boolean> = {touchstart: true, touchmove: true}
|
|
17
18
|
|
|
18
19
|
export class InputState {
|
|
19
20
|
shiftKey = false
|
|
@@ -25,6 +26,8 @@ export class InputState {
|
|
|
25
26
|
lastSelectionTime = 0
|
|
26
27
|
lastIOSEnter = 0
|
|
27
28
|
lastIOSEnterFallbackTimeout = -1
|
|
29
|
+
lastFocus = 0
|
|
30
|
+
lastTouch = 0
|
|
28
31
|
lastAndroidDelete = 0
|
|
29
32
|
composing = false
|
|
30
33
|
composingTimeout = -1
|
|
@@ -42,7 +45,7 @@ export function initInput(view: EditorView) {
|
|
|
42
45
|
if (eventBelongsToView(view, event) && !runCustomHandler(view, event) &&
|
|
43
46
|
(view.editable || !(event.type in editHandlers)))
|
|
44
47
|
handler(view, event)
|
|
45
|
-
})
|
|
48
|
+
}, passiveHandlers[event] ? {passive: true} : undefined)
|
|
46
49
|
}
|
|
47
50
|
// On Safari, for reasons beyond my understanding, adding an input
|
|
48
51
|
// event handler makes an issue where the composition vanishes when
|
|
@@ -369,6 +372,7 @@ class MouseDown {
|
|
|
369
372
|
let pos: {pos: number, inside: number} | null = this.pos
|
|
370
373
|
if (this.view.state.doc != this.startDoc) pos = this.view.posAtCoords(eventCoords(event))
|
|
371
374
|
|
|
375
|
+
this.updateAllowDefault(event)
|
|
372
376
|
if (this.allowDefault || !pos) {
|
|
373
377
|
setSelectionOrigin(this.view, "pointer")
|
|
374
378
|
} else if (handleSingleClick(this.view, pos.pos, pos.inside, event, this.selectNode)) {
|
|
@@ -395,19 +399,29 @@ class MouseDown {
|
|
|
395
399
|
}
|
|
396
400
|
|
|
397
401
|
move(event: MouseEvent) {
|
|
398
|
-
|
|
399
|
-
Math.abs(this.event.y - event.clientY) > 4))
|
|
400
|
-
this.allowDefault = true
|
|
402
|
+
this.updateAllowDefault(event)
|
|
401
403
|
setSelectionOrigin(this.view, "pointer")
|
|
402
404
|
if (event.buttons == 0) this.done()
|
|
403
405
|
}
|
|
406
|
+
|
|
407
|
+
updateAllowDefault(event: MouseEvent) {
|
|
408
|
+
if (!this.allowDefault && (Math.abs(this.event.x - event.clientX) > 4 ||
|
|
409
|
+
Math.abs(this.event.y - event.clientY) > 4))
|
|
410
|
+
this.allowDefault = true
|
|
411
|
+
}
|
|
404
412
|
}
|
|
405
413
|
|
|
406
|
-
handlers.
|
|
414
|
+
handlers.touchstart = view => {
|
|
415
|
+
view.input.lastTouch = Date.now()
|
|
407
416
|
forceDOMFlush(view)
|
|
408
417
|
setSelectionOrigin(view, "pointer")
|
|
409
418
|
}
|
|
410
419
|
|
|
420
|
+
handlers.touchmove = view => {
|
|
421
|
+
view.input.lastTouch = Date.now()
|
|
422
|
+
setSelectionOrigin(view, "pointer")
|
|
423
|
+
}
|
|
424
|
+
|
|
411
425
|
handlers.contextmenu = view => forceDOMFlush(view)
|
|
412
426
|
|
|
413
427
|
function inOrNearComposition(view: EditorView, event: Event) {
|
|
@@ -650,7 +664,6 @@ editHandlers.drop = (view, _event) => {
|
|
|
650
664
|
let eventPos = view.posAtCoords(eventCoords(event))
|
|
651
665
|
if (!eventPos) return
|
|
652
666
|
let $mouse = view.state.doc.resolve(eventPos.pos)
|
|
653
|
-
if (!$mouse) return
|
|
654
667
|
let slice = dragging && dragging.slice
|
|
655
668
|
if (slice) {
|
|
656
669
|
view.someProp("transformPasted", f => { slice = f(slice!) })
|
|
@@ -695,6 +708,7 @@ editHandlers.drop = (view, _event) => {
|
|
|
695
708
|
}
|
|
696
709
|
|
|
697
710
|
handlers.focus = view => {
|
|
711
|
+
view.input.lastFocus = Date.now()
|
|
698
712
|
if (!view.focused) {
|
|
699
713
|
view.domObserver.stop()
|
|
700
714
|
view.dom.classList.add("ProseMirror-focused")
|
package/src/viewdesc.ts
CHANGED
|
@@ -709,7 +709,7 @@ export class NodeViewDesc extends ViewDesc {
|
|
|
709
709
|
let composition = view.composing ? this.localCompositionInfo(view, pos) : null
|
|
710
710
|
let localComposition = composition && composition.pos > -1 ? composition : null
|
|
711
711
|
let compositionInChild = composition && composition.pos < 0
|
|
712
|
-
let updater = new ViewTreeUpdater(this, localComposition && localComposition.node)
|
|
712
|
+
let updater = new ViewTreeUpdater(this, localComposition && localComposition.node, view)
|
|
713
713
|
iterDeco(this.node, this.innerDeco, (widget, i, insideNode) => {
|
|
714
714
|
if (widget.spec.marks)
|
|
715
715
|
updater.syncToMarks(widget.spec.marks, inline, view)
|
|
@@ -1109,7 +1109,7 @@ class ViewTreeUpdater {
|
|
|
1109
1109
|
preMatch: {index: number, matched: Map<ViewDesc, number>, matches: readonly ViewDesc[]}
|
|
1110
1110
|
top: ViewDesc
|
|
1111
1111
|
|
|
1112
|
-
constructor(top: NodeViewDesc, readonly lock: DOMNode | null) {
|
|
1112
|
+
constructor(top: NodeViewDesc, readonly lock: DOMNode | null, private readonly view: EditorView) {
|
|
1113
1113
|
this.top = top
|
|
1114
1114
|
this.preMatch = preMatch(top.node.content, top)
|
|
1115
1115
|
}
|
|
@@ -1273,7 +1273,8 @@ class ViewTreeUpdater {
|
|
|
1273
1273
|
|
|
1274
1274
|
if (!lastChild || // Empty textblock
|
|
1275
1275
|
!(lastChild instanceof TextViewDesc) ||
|
|
1276
|
-
/\n$/.test(lastChild.node.text!)
|
|
1276
|
+
/\n$/.test(lastChild.node.text!) ||
|
|
1277
|
+
(this.view.requiresGeckoHackNode && /\s$/.test(lastChild.node.text!))) {
|
|
1277
1278
|
// Avoid bugs in Safari's cursor drawing (#1165) and Chrome's mouse selection (#1152)
|
|
1278
1279
|
if ((browser.safari || browser.chrome) && lastChild && (lastChild.dom as HTMLElement).contentEditable == "false")
|
|
1279
1280
|
this.addHackNode("IMG", parent)
|