prosemirror-view 1.27.0 → 1.28.0

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()
@@ -283,6 +292,21 @@ export class EditorView {
283
292
 
284
293
  /// Query whether the view has focus.
285
294
  hasFocus() {
295
+ // Work around IE not handling focus correctly if resize handles are shown.
296
+ // If the cursor is inside an element with resize handles, activeElement
297
+ // will be that element instead of this.dom.
298
+ if (browser.ie) {
299
+ // If activeElement is within this.dom, and there are no other elements
300
+ // setting `contenteditable` to false in between, treat it as focused.
301
+ let node = this.root.activeElement
302
+ if (node == this.dom) return true
303
+ if (!node || !this.dom.contains(node)) return false
304
+ while (node && this.dom != node && this.dom.contains(node)) {
305
+ if ((node as HTMLElement).contentEditable == 'false') return false
306
+ node = node.parentElement
307
+ }
308
+ return true
309
+ }
286
310
  return this.root.activeElement == this.dom
287
311
  }
288
312
 
@@ -623,10 +647,14 @@ export interface EditorProps<P = any> {
623
647
  /// The `plain` flag will be true when the text is pasted as plain text.
624
648
  clipboardTextParser?: (this: P, text: string, $context: ResolvedPos, plain: boolean) => Slice
625
649
 
626
- /// Can be used to transform pasted content before it is applied to
627
- /// the document.
650
+ /// Can be used to transform pasted or dragged-and-dropped content
651
+ /// before it is applied to the document.
628
652
  transformPasted?: (this: P, slice: Slice) => Slice
629
653
 
654
+ /// Can be used to transform copied or cut content before it is
655
+ /// serialized to the clipboard.
656
+ transformCopied?: (this: P, slice: Slice) => Slice
657
+
630
658
  /// Allows you to pass custom rendering and behavior logic for
631
659
  /// nodes. Should map node names to constructor functions that
632
660
  /// produce a [`NodeView`](#view.NodeView) object implementing the
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
@@ -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)) {
@@ -384,7 +388,7 @@ class MouseDown {
384
388
  // (hidden) cursor is doesn't change the selection, and
385
389
  // thus doesn't get a reaction from ProseMirror. This
386
390
  // works around that.
387
- (browser.chrome && !(this.view.state.selection instanceof TextSelection) &&
391
+ (browser.chrome && !this.view.state.selection.visible &&
388
392
  Math.min(Math.abs(pos.pos - this.view.state.selection.from),
389
393
  Math.abs(pos.pos - this.view.state.selection.to)) <= 2))) {
390
394
  updateSelection(this.view, Selection.near(this.view.state.doc.resolve(pos.pos)), "pointer")
@@ -395,19 +399,29 @@ class MouseDown {
395
399
  }
396
400
 
397
401
  move(event: MouseEvent) {
398
- if (!this.allowDefault && (Math.abs(this.event.x - event.clientX) > 4 ||
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.touchdown = view => {
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/selection.ts CHANGED
@@ -181,7 +181,7 @@ export function selectionBetween(view: EditorView, $anchor: ResolvedPos, $head:
181
181
  }
182
182
 
183
183
  export function hasFocusAndSelection(view: EditorView) {
184
- if (view.editable && view.root.activeElement != view.dom) return false
184
+ if (view.editable && !view.hasFocus()) return false
185
185
  return hasSelection(view)
186
186
  }
187
187
 
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)