prosemirror-view 1.23.13 → 1.24.0-beta.3

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.
@@ -1,46 +1,44 @@
1
1
  import {Selection, NodeSelection, TextSelection} from "prosemirror-state"
2
2
  import {dropPoint} from "prosemirror-transform"
3
- import {Slice} from "prosemirror-model"
3
+ import {Slice, Node} from "prosemirror-model"
4
4
 
5
- import browser from "./browser"
5
+ import * as browser from "./browser"
6
6
  import {captureKeyDown} from "./capturekeys"
7
- import {readDOMChange} from "./domchange"
8
7
  import {parseFromClipboard, serializeForClipboard} from "./clipboard"
9
- import {DOMObserver} from "./domobserver"
10
8
  import {selectionBetween, selectionToDOM, selectionFromDOM} from "./selection"
11
- import {keyEvent} from "./dom"
9
+ import {keyEvent, DOMNode} from "./dom"
10
+ import {EditorView} from "./index"
11
+ import {ViewDesc} from "./viewdesc"
12
12
 
13
13
  // A collection of DOM events that occur within the editor, and callback functions
14
14
  // to invoke when the event fires.
15
- const handlers = {}, editHandlers = {}
16
-
17
- export function initInput(view) {
18
- view.shiftKey = false
19
- view.mouseDown = null
20
- view.lastKeyCode = null
21
- view.lastKeyCodeTime = 0
22
- view.lastClick = {time: 0, x: 0, y: 0, type: ""}
23
- view.lastSelectionOrigin = null
24
- view.lastSelectionTime = 0
25
-
26
- view.lastIOSEnter = 0
27
- view.lastIOSEnterFallbackTimeout = null
28
- view.lastAndroidDelete = 0
29
-
30
- view.composing = false
31
- view.composingTimeout = null
32
- view.compositionNodes = []
33
- view.compositionEndedAt = -2e8
34
-
35
- view.domObserver = new DOMObserver(view, (from, to, typeOver, added) => readDOMChange(view, from, to, typeOver, added))
36
- view.domObserver.start()
37
- // Used by hacks like the beforeinput handler to check whether anything happened in the DOM
38
- view.domChangeCount = 0
39
-
40
- view.eventHandlers = Object.create(null)
15
+ const handlers: {[event: string]: (view: EditorView, event: Event) => void} = {}
16
+ let editHandlers: {[event: string]: (view: EditorView, event: Event) => void} = {}
17
+
18
+ export class InputState {
19
+ shiftKey = false
20
+ mouseDown: MouseDown | null = null
21
+ lastKeyCode: number | null = null
22
+ lastKeyCodeTime = 0
23
+ lastClick = {time: 0, x: 0, y: 0, type: ""}
24
+ lastSelectionOrigin: string | null = null
25
+ lastSelectionTime = 0
26
+ lastIOSEnter = 0
27
+ lastIOSEnterFallbackTimeout = -1
28
+ lastAndroidDelete = 0
29
+ composing = false
30
+ composingTimeout = -1
31
+ compositionNodes: ViewDesc[] = []
32
+ compositionEndedAt = -2e8
33
+ domChangeCount = 0
34
+ eventHandlers: {[event: string]: (event: Event) => void} = Object.create(null)
35
+ hideSelectionGuard: (() => void) | null = null
36
+ }
37
+
38
+ export function initInput(view: EditorView) {
41
39
  for (let event in handlers) {
42
40
  let handler = handlers[event]
43
- view.dom.addEventListener(event, view.eventHandlers[event] = event => {
41
+ view.dom.addEventListener(event, view.input.eventHandlers[event] = (event: Event) => {
44
42
  if (eventBelongsToView(view, event) && !runCustomHandler(view, event) &&
45
43
  (view.editable || !(event.type in editHandlers)))
46
44
  handler(view, event)
@@ -54,54 +52,55 @@ export function initInput(view) {
54
52
  ensureListeners(view)
55
53
  }
56
54
 
57
- function setSelectionOrigin(view, origin) {
58
- view.lastSelectionOrigin = origin
59
- view.lastSelectionTime = Date.now()
55
+ function setSelectionOrigin(view: EditorView, origin: string) {
56
+ view.input.lastSelectionOrigin = origin
57
+ view.input.lastSelectionTime = Date.now()
60
58
  }
61
59
 
62
- export function destroyInput(view) {
60
+ export function destroyInput(view: EditorView) {
63
61
  view.domObserver.stop()
64
- for (let type in view.eventHandlers)
65
- view.dom.removeEventListener(type, view.eventHandlers[type])
66
- clearTimeout(view.composingTimeout)
67
- clearTimeout(view.lastIOSEnterFallbackTimeout)
62
+ for (let type in view.input.eventHandlers)
63
+ view.dom.removeEventListener(type, view.input.eventHandlers[type])
64
+ clearTimeout(view.input.composingTimeout)
65
+ clearTimeout(view.input.lastIOSEnterFallbackTimeout)
68
66
  }
69
67
 
70
- export function ensureListeners(view) {
68
+ export function ensureListeners(view: EditorView) {
71
69
  view.someProp("handleDOMEvents", currentHandlers => {
72
- for (let type in currentHandlers) if (!view.eventHandlers[type])
73
- view.dom.addEventListener(type, view.eventHandlers[type] = event => runCustomHandler(view, event))
70
+ for (let type in currentHandlers) if (!view.input.eventHandlers[type])
71
+ view.dom.addEventListener(type, view.input.eventHandlers[type] = event => runCustomHandler(view, event))
74
72
  })
75
73
  }
76
74
 
77
- function runCustomHandler(view, event) {
75
+ function runCustomHandler(view: EditorView, event: Event) {
78
76
  return view.someProp("handleDOMEvents", handlers => {
79
77
  let handler = handlers[event.type]
80
78
  return handler ? handler(view, event) || event.defaultPrevented : false
81
79
  })
82
80
  }
83
81
 
84
- function eventBelongsToView(view, event) {
82
+ function eventBelongsToView(view: EditorView, event: Event) {
85
83
  if (!event.bubbles) return true
86
84
  if (event.defaultPrevented) return false
87
- for (let node = event.target; node != view.dom; node = node.parentNode)
85
+ for (let node = event.target as DOMNode; node != view.dom; node = node.parentNode!)
88
86
  if (!node || node.nodeType == 11 ||
89
87
  (node.pmViewDesc && node.pmViewDesc.stopEvent(event)))
90
88
  return false
91
89
  return true
92
90
  }
93
91
 
94
- export function dispatchEvent(view, event) {
92
+ export function dispatchEvent(view: EditorView, event: Event) {
95
93
  if (!runCustomHandler(view, event) && handlers[event.type] &&
96
94
  (view.editable || !(event.type in editHandlers)))
97
95
  handlers[event.type](view, event)
98
96
  }
99
97
 
100
- editHandlers.keydown = (view, event) => {
101
- view.shiftKey = event.keyCode == 16 || event.shiftKey
98
+ editHandlers.keydown = (view: EditorView, _event: Event) => {
99
+ let event = _event as KeyboardEvent
100
+ view.input.shiftKey = event.keyCode == 16 || event.shiftKey
102
101
  if (inOrNearComposition(view, event)) return
103
- view.lastKeyCode = event.keyCode
104
- view.lastKeyCodeTime = Date.now()
102
+ view.input.lastKeyCode = event.keyCode
103
+ view.input.lastKeyCodeTime = Date.now()
105
104
  // Suppress enter key events on Chrome Android, because those tend
106
105
  // to be part of a confused sequence of composition events fired,
107
106
  // and handling them eagerly tends to corrupt the input.
@@ -114,11 +113,11 @@ editHandlers.keydown = (view, event) => {
114
113
  // be replaced by whatever the Enter key handlers do.
115
114
  if (browser.ios && event.keyCode == 13 && !event.ctrlKey && !event.altKey && !event.metaKey) {
116
115
  let now = Date.now()
117
- view.lastIOSEnter = now
118
- view.lastIOSEnterFallbackTimeout = setTimeout(() => {
119
- if (view.lastIOSEnter == now) {
116
+ view.input.lastIOSEnter = now
117
+ view.input.lastIOSEnterFallbackTimeout = setTimeout(() => {
118
+ if (view.input.lastIOSEnter == now) {
120
119
  view.someProp("handleKeyDown", f => f(view, keyEvent(13, "Enter")))
121
- view.lastIOSEnter = 0
120
+ view.input.lastIOSEnter = 0
122
121
  }
123
122
  }, 200)
124
123
  } else if (view.someProp("handleKeyDown", f => f(view, event)) || captureKeyDown(view, event)) {
@@ -128,11 +127,12 @@ editHandlers.keydown = (view, event) => {
128
127
  }
129
128
  }
130
129
 
131
- editHandlers.keyup = (view, e) => {
132
- if (e.keyCode == 16) view.shiftKey = false
130
+ editHandlers.keyup = (view, event) => {
131
+ if ((event as KeyboardEvent).keyCode == 16) view.input.shiftKey = false
133
132
  }
134
133
 
135
- editHandlers.keypress = (view, event) => {
134
+ editHandlers.keypress = (view, _event) => {
135
+ let event = _event as KeyboardEvent
136
136
  if (inOrNearComposition(view, event) || !event.charCode ||
137
137
  event.ctrlKey && !event.altKey || browser.mac && event.metaKey) return
138
138
 
@@ -150,32 +150,38 @@ editHandlers.keypress = (view, event) => {
150
150
  }
151
151
  }
152
152
 
153
- function eventCoords(event) { return {left: event.clientX, top: event.clientY} }
153
+ function eventCoords(event: MouseEvent) { return {left: event.clientX, top: event.clientY} }
154
154
 
155
- function isNear(event, click) {
155
+ function isNear(event: MouseEvent, click: {x: number, y: number}) {
156
156
  let dx = click.x - event.clientX, dy = click.y - event.clientY
157
157
  return dx * dx + dy * dy < 100
158
158
  }
159
159
 
160
- function runHandlerOnContext(view, propName, pos, inside, event) {
160
+ function runHandlerOnContext(
161
+ view: EditorView,
162
+ propName: "handleClickOn" | "handleDoubleClickOn" | "handleTripleClickOn",
163
+ pos: number,
164
+ inside: number,
165
+ event: MouseEvent
166
+ ) {
161
167
  if (inside == -1) return false
162
168
  let $pos = view.state.doc.resolve(inside)
163
169
  for (let i = $pos.depth + 1; i > 0; i--) {
164
- if (view.someProp(propName, f => i > $pos.depth ? f(view, pos, $pos.nodeAfter, $pos.before(i), event, true)
170
+ if (view.someProp(propName, f => i > $pos.depth ? f(view, pos, $pos.nodeAfter!, $pos.before(i), event, true)
165
171
  : f(view, pos, $pos.node(i), $pos.before(i), event, false)))
166
172
  return true
167
173
  }
168
174
  return false
169
175
  }
170
176
 
171
- function updateSelection(view, selection, origin) {
177
+ function updateSelection(view: EditorView, selection: Selection, origin: string) {
172
178
  if (!view.focused) view.focus()
173
179
  let tr = view.state.tr.setSelection(selection)
174
180
  if (origin == "pointer") tr.setMeta("pointer", true)
175
181
  view.dispatch(tr)
176
182
  }
177
183
 
178
- function selectClickedLeaf(view, inside) {
184
+ function selectClickedLeaf(view: EditorView, inside: number) {
179
185
  if (inside == -1) return false
180
186
  let $pos = view.state.doc.resolve(inside), node = $pos.nodeAfter
181
187
  if (node && node.isAtom && NodeSelection.isSelectable(node)) {
@@ -185,14 +191,14 @@ function selectClickedLeaf(view, inside) {
185
191
  return false
186
192
  }
187
193
 
188
- function selectClickedNode(view, inside) {
194
+ function selectClickedNode(view: EditorView, inside: number) {
189
195
  if (inside == -1) return false
190
196
  let sel = view.state.selection, selectedNode, selectAt
191
197
  if (sel instanceof NodeSelection) selectedNode = sel.node
192
198
 
193
199
  let $pos = view.state.doc.resolve(inside)
194
200
  for (let i = $pos.depth + 1; i > 0; i--) {
195
- let node = i > $pos.depth ? $pos.nodeAfter : $pos.node(i)
201
+ let node = i > $pos.depth ? $pos.nodeAfter! : $pos.node(i)
196
202
  if (NodeSelection.isSelectable(node)) {
197
203
  if (selectedNode && sel.$from.depth > 0 &&
198
204
  i >= sel.$from.depth && $pos.before(sel.$from.depth + 1) == sel.$from.pos)
@@ -211,24 +217,24 @@ function selectClickedNode(view, inside) {
211
217
  }
212
218
  }
213
219
 
214
- function handleSingleClick(view, pos, inside, event, selectNode) {
220
+ function handleSingleClick(view: EditorView, pos: number, inside: number, event: MouseEvent, selectNode: boolean) {
215
221
  return runHandlerOnContext(view, "handleClickOn", pos, inside, event) ||
216
222
  view.someProp("handleClick", f => f(view, pos, event)) ||
217
223
  (selectNode ? selectClickedNode(view, inside) : selectClickedLeaf(view, inside))
218
224
  }
219
225
 
220
- function handleDoubleClick(view, pos, inside, event) {
226
+ function handleDoubleClick(view: EditorView, pos: number, inside: number, event: MouseEvent) {
221
227
  return runHandlerOnContext(view, "handleDoubleClickOn", pos, inside, event) ||
222
228
  view.someProp("handleDoubleClick", f => f(view, pos, event))
223
229
  }
224
230
 
225
- function handleTripleClick(view, pos, inside, event) {
231
+ function handleTripleClick(view: EditorView, pos: number, inside: number, event: MouseEvent) {
226
232
  return runHandlerOnContext(view, "handleTripleClickOn", pos, inside, event) ||
227
233
  view.someProp("handleTripleClick", f => f(view, pos, event)) ||
228
234
  defaultTripleClick(view, inside, event)
229
235
  }
230
236
 
231
- function defaultTripleClick(view, inside, event) {
237
+ function defaultTripleClick(view: EditorView, inside: number, event: MouseEvent) {
232
238
  if (event.button != 0) return false
233
239
  let doc = view.state.doc
234
240
  if (inside == -1) {
@@ -241,7 +247,7 @@ function defaultTripleClick(view, inside, event) {
241
247
 
242
248
  let $pos = doc.resolve(inside)
243
249
  for (let i = $pos.depth + 1; i > 0; i--) {
244
- let node = i > $pos.depth ? $pos.nodeAfter : $pos.node(i)
250
+ let node = i > $pos.depth ? $pos.nodeAfter! : $pos.node(i)
245
251
  let nodePos = $pos.before(i)
246
252
  if (node.inlineContent)
247
253
  updateSelection(view, TextSelection.create(doc, nodePos + 1, nodePos + 1 + node.content.size), "pointer")
@@ -253,28 +259,29 @@ function defaultTripleClick(view, inside, event) {
253
259
  }
254
260
  }
255
261
 
256
- function forceDOMFlush(view) {
262
+ function forceDOMFlush(view: EditorView) {
257
263
  return endComposition(view)
258
264
  }
259
265
 
260
- const selectNodeModifier = browser.mac ? "metaKey" : "ctrlKey"
266
+ const selectNodeModifier: keyof MouseEvent = browser.mac ? "metaKey" : "ctrlKey"
261
267
 
262
- handlers.mousedown = (view, event) => {
263
- view.shiftKey = event.shiftKey
268
+ handlers.mousedown = (view, _event) => {
269
+ let event = _event as MouseEvent
270
+ view.input.shiftKey = event.shiftKey
264
271
  let flushed = forceDOMFlush(view)
265
272
  let now = Date.now(), type = "singleClick"
266
- if (now - view.lastClick.time < 500 && isNear(event, view.lastClick) && !event[selectNodeModifier]) {
267
- if (view.lastClick.type == "singleClick") type = "doubleClick"
268
- else if (view.lastClick.type == "doubleClick") type = "tripleClick"
273
+ if (now - view.input.lastClick.time < 500 && isNear(event, view.input.lastClick) && !event[selectNodeModifier]) {
274
+ if (view.input.lastClick.type == "singleClick") type = "doubleClick"
275
+ else if (view.input.lastClick.type == "doubleClick") type = "tripleClick"
269
276
  }
270
- view.lastClick = {time: now, x: event.clientX, y: event.clientY, type}
277
+ view.input.lastClick = {time: now, x: event.clientX, y: event.clientY, type}
271
278
 
272
279
  let pos = view.posAtCoords(eventCoords(event))
273
280
  if (!pos) return
274
281
 
275
282
  if (type == "singleClick") {
276
- if (view.mouseDown) view.mouseDown.done()
277
- view.mouseDown = new MouseDown(view, pos, event, flushed)
283
+ if (view.input.mouseDown) view.input.mouseDown.done()
284
+ view.input.mouseDown = new MouseDown(view, pos, event, !!flushed)
278
285
  } else if ((type == "doubleClick" ? handleDoubleClick : handleTripleClick)(view, pos.pos, pos.inside, event)) {
279
286
  event.preventDefault()
280
287
  } else {
@@ -283,19 +290,26 @@ handlers.mousedown = (view, event) => {
283
290
  }
284
291
 
285
292
  class MouseDown {
286
- constructor(view, pos, event, flushed) {
287
- this.view = view
293
+ startDoc: Node
294
+ selectNode: boolean
295
+ allowDefault: boolean
296
+ delayedSelectionSync = false
297
+ mightDrag: {node: Node, pos: number, addAttr: boolean, setUneditable: boolean} | null = null
298
+ target: HTMLElement | null
299
+
300
+ constructor(
301
+ readonly view: EditorView,
302
+ readonly pos: {pos: number, inside: number},
303
+ readonly event: MouseEvent,
304
+ readonly flushed: boolean
305
+ ) {
288
306
  this.startDoc = view.state.doc
289
- this.pos = pos
290
- this.event = event
291
- this.flushed = flushed
292
- this.selectNode = event[selectNodeModifier]
307
+ this.selectNode = !!event[selectNodeModifier]
293
308
  this.allowDefault = event.shiftKey
294
- this.delayedSelectionSync = false
295
309
 
296
- let targetNode, targetPos
310
+ let targetNode: Node, targetPos
297
311
  if (pos.inside > -1) {
298
- targetNode = view.state.doc.nodeAt(pos.inside)
312
+ targetNode = view.state.doc.nodeAt(pos.inside)!
299
313
  targetPos = pos.inside
300
314
  } else {
301
315
  let $pos = view.state.doc.resolve(pos.pos)
@@ -303,39 +317,39 @@ class MouseDown {
303
317
  targetPos = $pos.depth ? $pos.before() : 0
304
318
  }
305
319
 
306
- this.mightDrag = null
307
-
308
- const target = flushed ? null : event.target
320
+ const target = flushed ? null : event.target as HTMLElement
309
321
  const targetDesc = target ? view.docView.nearestDesc(target, true) : null
310
- this.target = targetDesc ? targetDesc.dom : null
322
+ this.target = targetDesc ? targetDesc.dom as HTMLElement : null
311
323
 
312
324
  let {selection} = view.state
313
325
  if (event.button == 0 &&
314
326
  targetNode.type.spec.draggable && targetNode.type.spec.selectable !== false ||
315
327
  selection instanceof NodeSelection && selection.from <= targetPos && selection.to > targetPos)
316
- this.mightDrag = {node: targetNode,
317
- pos: targetPos,
318
- addAttr: this.target && !this.target.draggable,
319
- setUneditable: this.target && browser.gecko && !this.target.hasAttribute("contentEditable")}
328
+ this.mightDrag = {
329
+ node: targetNode,
330
+ pos: targetPos,
331
+ addAttr: !!(this.target && !this.target.draggable),
332
+ setUneditable: !!(this.target && browser.gecko && !this.target.hasAttribute("contentEditable"))
333
+ }
320
334
 
321
335
  if (this.target && this.mightDrag && (this.mightDrag.addAttr || this.mightDrag.setUneditable)) {
322
336
  this.view.domObserver.stop()
323
337
  if (this.mightDrag.addAttr) this.target.draggable = true
324
338
  if (this.mightDrag.setUneditable)
325
339
  setTimeout(() => {
326
- if (this.view.mouseDown == this) this.target.setAttribute("contentEditable", "false")
340
+ if (this.view.input.mouseDown == this) this.target!.setAttribute("contentEditable", "false")
327
341
  }, 20)
328
342
  this.view.domObserver.start()
329
343
  }
330
344
 
331
- view.root.addEventListener("mouseup", this.up = this.up.bind(this))
332
- view.root.addEventListener("mousemove", this.move = this.move.bind(this))
345
+ view.root.addEventListener("mouseup", this.up = this.up.bind(this) as any)
346
+ view.root.addEventListener("mousemove", this.move = this.move.bind(this) as any)
333
347
  setSelectionOrigin(view, "pointer")
334
348
  }
335
349
 
336
350
  done() {
337
- this.view.root.removeEventListener("mouseup", this.up)
338
- this.view.root.removeEventListener("mousemove", this.move)
351
+ this.view.root.removeEventListener("mouseup", this.up as any)
352
+ this.view.root.removeEventListener("mousemove", this.move as any)
339
353
  if (this.mightDrag && this.target) {
340
354
  this.view.domObserver.stop()
341
355
  if (this.mightDrag.addAttr) this.target.removeAttribute("draggable")
@@ -343,16 +357,16 @@ class MouseDown {
343
357
  this.view.domObserver.start()
344
358
  }
345
359
  if (this.delayedSelectionSync) setTimeout(() => selectionToDOM(this.view))
346
- this.view.mouseDown = null
360
+ this.view.input.mouseDown = null
347
361
  }
348
362
 
349
- up(event) {
363
+ up(event: MouseEvent) {
350
364
  this.done()
351
365
 
352
- if (!this.view.dom.contains(event.target.nodeType == 3 ? event.target.parentNode : event.target))
366
+ if (!this.view.dom.contains(event.target as HTMLElement))
353
367
  return
354
368
 
355
- let pos = this.pos
369
+ let pos: {pos: number, inside: number} | null = this.pos
356
370
  if (this.view.state.doc != this.startDoc) pos = this.view.posAtCoords(eventCoords(event))
357
371
 
358
372
  if (this.allowDefault || !pos) {
@@ -380,7 +394,7 @@ class MouseDown {
380
394
  }
381
395
  }
382
396
 
383
- move(event) {
397
+ move(event: MouseEvent) {
384
398
  if (!this.allowDefault && (Math.abs(this.event.x - event.clientX) > 4 ||
385
399
  Math.abs(this.event.y - event.clientY) > 4))
386
400
  this.allowDefault = true
@@ -396,7 +410,7 @@ handlers.touchdown = view => {
396
410
 
397
411
  handlers.contextmenu = view => forceDOMFlush(view)
398
412
 
399
- function inOrNearComposition(view, event) {
413
+ function inOrNearComposition(view: EditorView, event: Event) {
400
414
  if (view.composing) return true
401
415
  // See https://www.stum.de/2016/06/24/handling-ime-events-in-javascript/.
402
416
  // On Japanese input method editors (IMEs), the Enter key is used to confirm character
@@ -408,8 +422,8 @@ function inOrNearComposition(view, event) {
408
422
  // This guards against the case where compositionend is triggered without the keyboard
409
423
  // (e.g. character confirmation may be done with the mouse), and keydown is triggered
410
424
  // afterwards- we wouldn't want to ignore the keydown event in this case.
411
- if (browser.safari && Math.abs(event.timeStamp - view.compositionEndedAt) < 500) {
412
- view.compositionEndedAt = -2e8
425
+ if (browser.safari && Math.abs(event.timeStamp - view.input.compositionEndedAt) < 500) {
426
+ view.input.compositionEndedAt = -2e8
413
427
  return true
414
428
  }
415
429
  return false
@@ -424,7 +438,7 @@ editHandlers.compositionstart = editHandlers.compositionupdate = view => {
424
438
  let {state} = view, $pos = state.selection.$from
425
439
  if (state.selection.empty &&
426
440
  (state.storedMarks ||
427
- (!$pos.textOffset && $pos.parentOffset && $pos.nodeBefore.marks.some(m => m.type.spec.inclusive === false)))) {
441
+ (!$pos.textOffset && $pos.parentOffset && $pos.nodeBefore!.marks.some(m => m.type.spec.inclusive === false)))) {
428
442
  // Need to wrap the cursor in mark nodes different from the ones in the DOM context
429
443
  view.markCursor = view.state.storedMarks || $pos.marks()
430
444
  endComposition(view, true)
@@ -434,13 +448,13 @@ editHandlers.compositionstart = editHandlers.compositionupdate = view => {
434
448
  // In firefox, if the cursor is after but outside a marked node,
435
449
  // the inserted text won't inherit the marks. So this moves it
436
450
  // inside if necessary.
437
- if (browser.gecko && state.selection.empty && $pos.parentOffset && !$pos.textOffset && $pos.nodeBefore.marks.length) {
438
- let sel = view.root.getSelection()
451
+ if (browser.gecko && state.selection.empty && $pos.parentOffset && !$pos.textOffset && $pos.nodeBefore!.marks.length) {
452
+ let sel = view.domSelection()
439
453
  for (let node = sel.focusNode, offset = sel.focusOffset; node && node.nodeType == 1 && offset != 0;) {
440
454
  let before = offset < 0 ? node.lastChild : node.childNodes[offset - 1]
441
455
  if (!before) break
442
456
  if (before.nodeType == 3) {
443
- sel.collapse(before, before.nodeValue.length)
457
+ sel.collapse(before, before.nodeValue!.length)
444
458
  break
445
459
  } else {
446
460
  node = before
@@ -449,30 +463,30 @@ editHandlers.compositionstart = editHandlers.compositionupdate = view => {
449
463
  }
450
464
  }
451
465
  }
452
- view.composing = true
466
+ view.input.composing = true
453
467
  }
454
468
  scheduleComposeEnd(view, timeoutComposition)
455
469
  }
456
470
 
457
471
  editHandlers.compositionend = (view, event) => {
458
472
  if (view.composing) {
459
- view.composing = false
460
- view.compositionEndedAt = event.timeStamp
473
+ view.input.composing = false
474
+ view.input.compositionEndedAt = event.timeStamp
461
475
  scheduleComposeEnd(view, 20)
462
476
  }
463
477
  }
464
478
 
465
- function scheduleComposeEnd(view, delay) {
466
- clearTimeout(view.composingTimeout)
467
- if (delay > -1) view.composingTimeout = setTimeout(() => endComposition(view), delay)
479
+ function scheduleComposeEnd(view: EditorView, delay: number) {
480
+ clearTimeout(view.input.composingTimeout)
481
+ if (delay > -1) view.input.composingTimeout = setTimeout(() => endComposition(view), delay)
468
482
  }
469
483
 
470
- export function clearComposition(view) {
484
+ export function clearComposition(view: EditorView) {
471
485
  if (view.composing) {
472
- view.composing = false
473
- view.compositionEndedAt = timestampFromCustomEvent()
486
+ view.input.composing = false
487
+ view.input.compositionEndedAt = timestampFromCustomEvent()
474
488
  }
475
- while (view.compositionNodes.length > 0) view.compositionNodes.pop().markParentsDirty()
489
+ while (view.input.compositionNodes.length > 0) view.input.compositionNodes.pop()!.markParentsDirty()
476
490
  }
477
491
 
478
492
  function timestampFromCustomEvent() {
@@ -481,7 +495,8 @@ function timestampFromCustomEvent() {
481
495
  return event.timeStamp
482
496
  }
483
497
 
484
- export function endComposition(view, forceUpdate) {
498
+ /// @internal
499
+ export function endComposition(view: EditorView, forceUpdate = false) {
485
500
  if (browser.android && view.domObserver.flushingSoon >= 0) return
486
501
  view.domObserver.forceFlush()
487
502
  clearComposition(view)
@@ -494,14 +509,14 @@ export function endComposition(view, forceUpdate) {
494
509
  return false
495
510
  }
496
511
 
497
- function captureCopy(view, dom) {
512
+ function captureCopy(view: EditorView, dom: HTMLElement) {
498
513
  // The extra wrapper is somehow necessary on IE/Edge to prevent the
499
514
  // content from being mangled when it is put onto the clipboard
500
515
  if (!view.dom.parentNode) return
501
516
  let wrap = view.dom.parentNode.appendChild(document.createElement("div"))
502
517
  wrap.appendChild(dom)
503
518
  wrap.style.cssText = "position: fixed; left: -10000px; top: 10px"
504
- let sel = getSelection(), range = document.createRange()
519
+ let sel = getSelection()!, range = document.createRange()
505
520
  range.selectNodeContents(dom)
506
521
  // Done because IE will fire a selectionchange moving the selection
507
522
  // to its start when removeAllRanges is called and the editor still
@@ -521,15 +536,16 @@ function captureCopy(view, dom) {
521
536
  const brokenClipboardAPI = (browser.ie && browser.ie_version < 15) ||
522
537
  (browser.ios && browser.webkit_version < 604)
523
538
 
524
- handlers.copy = editHandlers.cut = (view, e) => {
525
- let sel = view.state.selection, cut = e.type == "cut"
539
+ handlers.copy = editHandlers.cut = (view, _event) => {
540
+ let event = _event as ClipboardEvent
541
+ let sel = view.state.selection, cut = event.type == "cut"
526
542
  if (sel.empty) return
527
543
 
528
544
  // IE and Edge's clipboard interface is completely broken
529
- let data = brokenClipboardAPI ? null : e.clipboardData
545
+ let data = brokenClipboardAPI ? null : event.clipboardData
530
546
  let slice = sel.content(), {dom, text} = serializeForClipboard(view, slice)
531
547
  if (data) {
532
- e.preventDefault()
548
+ event.preventDefault()
533
549
  data.clearData()
534
550
  data.setData("text/html", dom.innerHTML)
535
551
  data.setData("text/plain", text)
@@ -539,13 +555,13 @@ handlers.copy = editHandlers.cut = (view, e) => {
539
555
  if (cut) view.dispatch(view.state.tr.deleteSelection().scrollIntoView().setMeta("uiEvent", "cut"))
540
556
  }
541
557
 
542
- function sliceSingleNode(slice) {
558
+ function sliceSingleNode(slice: Slice) {
543
559
  return slice.openStart == 0 && slice.openEnd == 0 && slice.content.childCount == 1 ? slice.content.firstChild : null
544
560
  }
545
561
 
546
- function capturePaste(view, e) {
562
+ function capturePaste(view: EditorView, event: ClipboardEvent) {
547
563
  if (!view.dom.parentNode) return
548
- let plainText = view.shiftKey || view.state.selection.$from.parent.type.spec.code
564
+ let plainText = view.input.shiftKey || view.state.selection.$from.parent.type.spec.code
549
565
  let target = view.dom.parentNode.appendChild(document.createElement(plainText ? "textarea" : "div"))
550
566
  if (!plainText) target.contentEditable = "true"
551
567
  target.style.cssText = "position: fixed; left: -10000px; top: 10px"
@@ -553,65 +569,66 @@ function capturePaste(view, e) {
553
569
  setTimeout(() => {
554
570
  view.focus()
555
571
  if (target.parentNode) target.parentNode.removeChild(target)
556
- if (plainText) doPaste(view, target.value, null, e)
557
- else doPaste(view, target.textContent, target.innerHTML, e)
572
+ if (plainText) doPaste(view, (target as HTMLTextAreaElement).value, null, event)
573
+ else doPaste(view, target.textContent!, target.innerHTML, event)
558
574
  }, 50)
559
575
  }
560
576
 
561
- function doPaste(view, text, html, e) {
562
- let slice = parseFromClipboard(view, text, html, view.shiftKey, view.state.selection.$from)
563
- if (view.someProp("handlePaste", f => f(view, e, slice || Slice.empty))) return true
577
+ function doPaste(view: EditorView, text: string, html: string | null, event: ClipboardEvent) {
578
+ let slice = parseFromClipboard(view, text, html, view.input.shiftKey, view.state.selection.$from)
579
+ if (view.someProp("handlePaste", f => f(view, event, slice || Slice.empty))) return true
564
580
  if (!slice) return false
565
581
 
566
582
  let singleNode = sliceSingleNode(slice)
567
- let tr = singleNode ? view.state.tr.replaceSelectionWith(singleNode, view.shiftKey) : view.state.tr.replaceSelection(slice)
583
+ let tr = singleNode
584
+ ? view.state.tr.replaceSelectionWith(singleNode, view.input.shiftKey)
585
+ : view.state.tr.replaceSelection(slice)
568
586
  view.dispatch(tr.scrollIntoView().setMeta("paste", true).setMeta("uiEvent", "paste"))
569
587
  return true
570
588
  }
571
589
 
572
- editHandlers.paste = (view, e) => {
590
+ editHandlers.paste = (view, _event) => {
591
+ let event = _event as ClipboardEvent
573
592
  // Handling paste from JavaScript during composition is very poorly
574
593
  // handled by browsers, so as a dodgy but preferable kludge, we just
575
594
  // let the browser do its native thing there, except on Android,
576
595
  // where the editor is almost always composing.
577
596
  if (view.composing && !browser.android) return
578
- let data = brokenClipboardAPI ? null : e.clipboardData
579
- if (data && doPaste(view, data.getData("text/plain"), data.getData("text/html"), e)) e.preventDefault()
580
- else capturePaste(view, e)
597
+ let data = brokenClipboardAPI ? null : event.clipboardData
598
+ if (data && doPaste(view, data.getData("text/plain"), data.getData("text/html"), event)) event.preventDefault()
599
+ else capturePaste(view, event)
581
600
  }
582
601
 
583
602
  class Dragging {
584
- constructor(slice, move) {
585
- this.slice = slice
586
- this.move = move
587
- }
603
+ constructor(readonly slice: Slice, readonly move: boolean) {}
588
604
  }
589
605
 
590
- const dragCopyModifier = browser.mac ? "altKey" : "ctrlKey"
606
+ const dragCopyModifier: keyof DragEvent = browser.mac ? "altKey" : "ctrlKey"
591
607
 
592
- handlers.dragstart = (view, e) => {
593
- let mouseDown = view.mouseDown
608
+ handlers.dragstart = (view, _event) => {
609
+ let event = _event as DragEvent
610
+ let mouseDown = view.input.mouseDown
594
611
  if (mouseDown) mouseDown.done()
595
- if (!e.dataTransfer) return
612
+ if (!event.dataTransfer) return
596
613
 
597
614
  let sel = view.state.selection
598
- let pos = sel.empty ? null : view.posAtCoords(eventCoords(e))
615
+ let pos = sel.empty ? null : view.posAtCoords(eventCoords(event))
599
616
  if (pos && pos.pos >= sel.from && pos.pos <= (sel instanceof NodeSelection ? sel.to - 1: sel.to)) {
600
617
  // In selection
601
618
  } else if (mouseDown && mouseDown.mightDrag) {
602
619
  view.dispatch(view.state.tr.setSelection(NodeSelection.create(view.state.doc, mouseDown.mightDrag.pos)))
603
- } else if (e.target && e.target.nodeType == 1) {
604
- let desc = view.docView.nearestDesc(e.target, true)
605
- if (desc && desc.node.type.spec.draggable && desc != view.docView)
620
+ } else if (event.target && (event.target as HTMLElement).nodeType == 1) {
621
+ let desc = view.docView.nearestDesc(event.target as HTMLElement, true)
622
+ if (desc && desc.node!.type.spec.draggable && desc != view.docView)
606
623
  view.dispatch(view.state.tr.setSelection(NodeSelection.create(view.state.doc, desc.posBefore)))
607
624
  }
608
625
  let slice = view.state.selection.content(), {dom, text} = serializeForClipboard(view, slice)
609
- e.dataTransfer.clearData()
610
- e.dataTransfer.setData(brokenClipboardAPI ? "Text" : "text/html", dom.innerHTML)
626
+ event.dataTransfer.clearData()
627
+ event.dataTransfer.setData(brokenClipboardAPI ? "Text" : "text/html", dom.innerHTML)
611
628
  // See https://github.com/ProseMirror/prosemirror/issues/1156
612
- e.dataTransfer.effectAllowed = "copyMove"
613
- if (!brokenClipboardAPI) e.dataTransfer.setData("text/plain", text)
614
- view.dragging = new Dragging(slice, !e[dragCopyModifier])
629
+ event.dataTransfer.effectAllowed = "copyMove"
630
+ if (!brokenClipboardAPI) event.dataTransfer.setData("text/plain", text)
631
+ view.dragging = new Dragging(slice, !event[dragCopyModifier])
615
632
  }
616
633
 
617
634
  handlers.dragend = view => {
@@ -623,31 +640,32 @@ handlers.dragend = view => {
623
640
 
624
641
  editHandlers.dragover = editHandlers.dragenter = (_, e) => e.preventDefault()
625
642
 
626
- editHandlers.drop = (view, e) => {
643
+ editHandlers.drop = (view, _event) => {
644
+ let event = _event as DragEvent
627
645
  let dragging = view.dragging
628
646
  view.dragging = null
629
647
 
630
- if (!e.dataTransfer) return
648
+ if (!event.dataTransfer) return
631
649
 
632
- let eventPos = view.posAtCoords(eventCoords(e))
650
+ let eventPos = view.posAtCoords(eventCoords(event))
633
651
  if (!eventPos) return
634
652
  let $mouse = view.state.doc.resolve(eventPos.pos)
635
653
  if (!$mouse) return
636
654
  let slice = dragging && dragging.slice
637
655
  if (slice) {
638
- view.someProp("transformPasted", f => { slice = f(slice) })
656
+ view.someProp("transformPasted", f => { slice = f(slice!) })
639
657
  } else {
640
- slice = parseFromClipboard(view, e.dataTransfer.getData(brokenClipboardAPI ? "Text" : "text/plain"),
641
- brokenClipboardAPI ? null : e.dataTransfer.getData("text/html"), false, $mouse)
658
+ slice = parseFromClipboard(view, event.dataTransfer.getData(brokenClipboardAPI ? "Text" : "text/plain"),
659
+ brokenClipboardAPI ? null : event.dataTransfer.getData("text/html"), false, $mouse)
642
660
  }
643
- let move = dragging && !e[dragCopyModifier]
644
- if (view.someProp("handleDrop", f => f(view, e, slice || Slice.empty, move))) {
645
- e.preventDefault()
661
+ let move = !!(dragging && !event[dragCopyModifier])
662
+ if (view.someProp("handleDrop", f => f(view, event, slice || Slice.empty, move))) {
663
+ event.preventDefault()
646
664
  return
647
665
  }
648
666
  if (!slice) return
649
667
 
650
- e.preventDefault()
668
+ event.preventDefault()
651
669
  let insertPos = slice ? dropPoint(view.state.doc, $mouse.pos, slice) : $mouse.pos
652
670
  if (insertPos == null) insertPos = $mouse.pos
653
671
 
@@ -658,14 +676,14 @@ editHandlers.drop = (view, e) => {
658
676
  let isNode = slice.openStart == 0 && slice.openEnd == 0 && slice.content.childCount == 1
659
677
  let beforeInsert = tr.doc
660
678
  if (isNode)
661
- tr.replaceRangeWith(pos, pos, slice.content.firstChild)
679
+ tr.replaceRangeWith(pos, pos, slice.content.firstChild!)
662
680
  else
663
681
  tr.replaceRange(pos, pos, slice)
664
682
  if (tr.doc.eq(beforeInsert)) return
665
683
 
666
684
  let $pos = tr.doc.resolve(pos)
667
- if (isNode && NodeSelection.isSelectable(slice.content.firstChild) &&
668
- $pos.nodeAfter && $pos.nodeAfter.sameMarkup(slice.content.firstChild)) {
685
+ if (isNode && NodeSelection.isSelectable(slice.content.firstChild!) &&
686
+ $pos.nodeAfter && $pos.nodeAfter.sameMarkup(slice.content.firstChild!)) {
669
687
  tr.setSelection(new NodeSelection($pos))
670
688
  } else {
671
689
  let end = tr.mapping.map(insertPos)
@@ -683,24 +701,26 @@ handlers.focus = view => {
683
701
  view.domObserver.start()
684
702
  view.focused = true
685
703
  setTimeout(() => {
686
- if (view.docView && view.hasFocus() && !view.domObserver.currentSelection.eq(view.root.getSelection()))
704
+ if (view.docView && view.hasFocus() && !view.domObserver.currentSelection.eq(view.domSelection()))
687
705
  selectionToDOM(view)
688
706
  }, 20)
689
707
  }
690
708
  }
691
709
 
692
- handlers.blur = (view, e) => {
710
+ handlers.blur = (view, _event) => {
711
+ let event = _event as FocusEvent
693
712
  if (view.focused) {
694
713
  view.domObserver.stop()
695
714
  view.dom.classList.remove("ProseMirror-focused")
696
715
  view.domObserver.start()
697
- if (e.relatedTarget && view.dom.contains(e.relatedTarget))
698
- view.domObserver.currentSelection.set({})
716
+ if (event.relatedTarget && view.dom.contains(event.relatedTarget as HTMLElement))
717
+ view.domObserver.currentSelection.clear()
699
718
  view.focused = false
700
719
  }
701
720
  }
702
721
 
703
- handlers.beforeinput = (view, event) => {
722
+ handlers.beforeinput = (view, _event: Event) => {
723
+ let event = _event as InputEvent
704
724
  // We should probably do more with beforeinput events, but support
705
725
  // is so spotty that I'm still waiting to see where they are going.
706
726
 
@@ -708,14 +728,14 @@ handlers.beforeinput = (view, event) => {
708
728
  // Chrome Android when after an uneditable node.
709
729
  if (browser.chrome && browser.android && event.inputType == "deleteContentBackward") {
710
730
  view.domObserver.flushSoon()
711
- let {domChangeCount} = view
731
+ let {domChangeCount} = view.input
712
732
  setTimeout(() => {
713
- if (view.domChangeCount != domChangeCount) return // Event already had some effect
733
+ if (view.input.domChangeCount != domChangeCount) return // Event already had some effect
714
734
  // This bug tends to close the virtual keyboard, so we refocus
715
735
  view.dom.blur()
716
736
  view.focus()
717
737
  if (view.someProp("handleKeyDown", f => f(view, keyEvent(8, "Backspace")))) return
718
- let {$cursor} = view.state.selection
738
+ let {$cursor} = view.state.selection as TextSelection
719
739
  // Crude approximation of backspace behavior when no command handled it
720
740
  if ($cursor && $cursor.pos > 0) view.dispatch(view.state.tr.delete($cursor.pos - 1, $cursor.pos).scrollIntoView())
721
741
  }, 50)