prosemirror-view 1.26.4 → 1.27.1

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/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
- let startDOM = this.domSelection().focusNode!
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
- export interface EditorProps {
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 content before it is applied to
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
- let editHandlers: {[event: string]: (view: EditorView, event: Event) => void} = {}
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
@@ -403,11 +406,17 @@ class MouseDown {
403
406
  }
404
407
  }
405
408
 
406
- handlers.touchdown = view => {
409
+ handlers.touchstart = view => {
410
+ view.input.lastTouch = Date.now()
407
411
  forceDOMFlush(view)
408
412
  setSelectionOrigin(view, "pointer")
409
413
  }
410
414
 
415
+ handlers.touchmove = view => {
416
+ view.input.lastTouch = Date.now()
417
+ setSelectionOrigin(view, "pointer")
418
+ }
419
+
411
420
  handlers.contextmenu = view => forceDOMFlush(view)
412
421
 
413
422
  function inOrNearComposition(view: EditorView, event: Event) {
@@ -650,7 +659,6 @@ editHandlers.drop = (view, _event) => {
650
659
  let eventPos = view.posAtCoords(eventCoords(event))
651
660
  if (!eventPos) return
652
661
  let $mouse = view.state.doc.resolve(eventPos.pos)
653
- if (!$mouse) return
654
662
  let slice = dragging && dragging.slice
655
663
  if (slice) {
656
664
  view.someProp("transformPasted", f => { slice = f(slice!) })
@@ -695,6 +703,7 @@ editHandlers.drop = (view, _event) => {
695
703
  }
696
704
 
697
705
  handlers.focus = view => {
706
+ view.input.lastFocus = Date.now()
698
707
  if (!view.focused) {
699
708
  view.domObserver.stop()
700
709
  view.dom.classList.add("ProseMirror-focused")
package/src/viewdesc.ts CHANGED
@@ -262,8 +262,8 @@ export class ViewDesc {
262
262
  }
263
263
  }
264
264
 
265
- domFromPos(pos: number, side: number): {node: DOMNode, offset: number} {
266
- if (!this.contentDOM) return {node: this.dom, offset: 0}
265
+ domFromPos(pos: number, side: number): {node: DOMNode, offset: number, atom?: number} {
266
+ if (!this.contentDOM) return {node: this.dom, offset: 0, atom: pos + 1}
267
267
  // First find the position in the child array
268
268
  let i = 0, offset = 0
269
269
  for (let curPos = 0; i < this.children.length; i++) {
@@ -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
  }
@@ -1196,7 +1196,7 @@ class ViewTreeUpdater {
1196
1196
  if (child.dirty == NODE_DIRTY && child.dom == child.contentDOM) child.dirty = CONTENT_DIRTY
1197
1197
  if (!child.update(node, outerDeco, innerDeco, view)) return false
1198
1198
  this.destroyBetween(this.index, index)
1199
- this.index = index + 1
1199
+ this.index++
1200
1200
  return true
1201
1201
  }
1202
1202
 
@@ -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)