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,25 +1,28 @@
1
- import browser from "./browser"
1
+ import * as browser from "./browser"
2
2
 
3
- export const domIndex = function(node) {
3
+ export type DOMNode = InstanceType<typeof window.Node>
4
+ export type DOMSelection = InstanceType<typeof window.Selection>
5
+
6
+ export const domIndex = function(node: Node) {
4
7
  for (var index = 0;; index++) {
5
- node = node.previousSibling
8
+ node = node.previousSibling!
6
9
  if (!node) return index
7
10
  }
8
11
  }
9
12
 
10
- export const parentNode = function(node) {
11
- let parent = node.assignedSlot || node.parentNode
12
- return parent && parent.nodeType == 11 ? parent.host : parent
13
+ export const parentNode = function(node: Node) {
14
+ let parent = (node as HTMLSlotElement).assignedSlot || node.parentNode
15
+ return parent && parent.nodeType == 11 ? (parent as ShadowRoot).host : parent
13
16
  }
14
17
 
15
- let reusedRange = null
18
+ let reusedRange: Range | null = null
16
19
 
17
20
  // Note that this will always return the same range, because DOM range
18
21
  // objects are every expensive, and keep slowing down subsequent DOM
19
22
  // updates, for some reason.
20
- export const textRange = function(node, from, to) {
23
+ export const textRange = function(node: Text, from?: number, to?: number) {
21
24
  let range = reusedRange || (reusedRange = document.createRange())
22
- range.setEnd(node, to == null ? node.nodeValue.length : to)
25
+ range.setEnd(node, to == null ? node.nodeValue!.length : to)
23
26
  range.setStart(node, from || 0)
24
27
  return range
25
28
  }
@@ -27,26 +30,26 @@ export const textRange = function(node, from, to) {
27
30
  // Scans forward and backward through DOM positions equivalent to the
28
31
  // given one to see if the two are in the same place (i.e. after a
29
32
  // text node vs at the end of that text node)
30
- export const isEquivalentPosition = function(node, off, targetNode, targetOff) {
33
+ export const isEquivalentPosition = function(node: Node, off: number, targetNode: Node, targetOff: number) {
31
34
  return targetNode && (scanFor(node, off, targetNode, targetOff, -1) ||
32
35
  scanFor(node, off, targetNode, targetOff, 1))
33
36
  }
34
37
 
35
38
  const atomElements = /^(img|br|input|textarea|hr)$/i
36
39
 
37
- function scanFor(node, off, targetNode, targetOff, dir) {
40
+ function scanFor(node: Node, off: number, targetNode: Node, targetOff: number, dir: number) {
38
41
  for (;;) {
39
42
  if (node == targetNode && off == targetOff) return true
40
43
  if (off == (dir < 0 ? 0 : nodeSize(node))) {
41
44
  let parent = node.parentNode
42
45
  if (!parent || parent.nodeType != 1 || hasBlockDesc(node) || atomElements.test(node.nodeName) ||
43
- node.contentEditable == "false")
46
+ (node as HTMLElement).contentEditable == "false")
44
47
  return false
45
48
  off = domIndex(node) + (dir < 0 ? 0 : 1)
46
49
  node = parent
47
50
  } else if (node.nodeType == 1) {
48
51
  node = node.childNodes[off + (dir < 0 ? -1 : 0)]
49
- if (node.contentEditable == "false") return false
52
+ if ((node as HTMLElement).contentEditable == "false") return false
50
53
  off = dir < 0 ? nodeSize(node) : 0
51
54
  } else {
52
55
  return false
@@ -54,40 +57,40 @@ function scanFor(node, off, targetNode, targetOff, dir) {
54
57
  }
55
58
  }
56
59
 
57
- export function nodeSize(node) {
58
- return node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length
60
+ export function nodeSize(node: Node) {
61
+ return node.nodeType == 3 ? node.nodeValue!.length : node.childNodes.length
59
62
  }
60
63
 
61
- export function isOnEdge(node, offset, parent) {
64
+ export function isOnEdge(node: Node, offset: number, parent: Node) {
62
65
  for (let atStart = offset == 0, atEnd = offset == nodeSize(node); atStart || atEnd;) {
63
66
  if (node == parent) return true
64
67
  let index = domIndex(node)
65
- node = node.parentNode
68
+ node = node.parentNode!
66
69
  if (!node) return false
67
70
  atStart = atStart && index == 0
68
71
  atEnd = atEnd && index == nodeSize(node)
69
72
  }
70
73
  }
71
74
 
72
- function hasBlockDesc(dom) {
75
+ function hasBlockDesc(dom: Node) {
73
76
  let desc
74
- for (let cur = dom; cur; cur = cur.parentNode) if (desc = cur.pmViewDesc) break
77
+ for (let cur: Node | null = dom; cur; cur = cur.parentNode) if (desc = cur.pmViewDesc) break
75
78
  return desc && desc.node && desc.node.isBlock && (desc.dom == dom || desc.contentDOM == dom)
76
79
  }
77
80
 
78
81
  // Work around Chrome issue https://bugs.chromium.org/p/chromium/issues/detail?id=447523
79
82
  // (isCollapsed inappropriately returns true in shadow dom)
80
- export const selectionCollapsed = function(domSel) {
83
+ export const selectionCollapsed = function(domSel: Selection) {
81
84
  let collapsed = domSel.isCollapsed
82
85
  if (collapsed && browser.chrome && domSel.rangeCount && !domSel.getRangeAt(0).collapsed)
83
86
  collapsed = false
84
87
  return collapsed
85
88
  }
86
89
 
87
- export function keyEvent(keyCode, key) {
88
- let event = document.createEvent("Event")
90
+ export function keyEvent(keyCode: number, key: string) {
91
+ let event = document.createEvent("Event") as KeyboardEvent
89
92
  event.initEvent("keydown", true, true)
90
- event.keyCode = keyCode
91
- event.key = event.code = key
93
+ ;(event as any).keyCode = keyCode
94
+ ;(event as any).key = (event as any).code = key
92
95
  return event
93
96
  }
@@ -1,9 +1,10 @@
1
- import {Fragment, DOMParser} from "prosemirror-model"
1
+ import {Fragment, DOMParser, ParseRule, Node, Mark, ResolvedPos} from "prosemirror-model"
2
2
  import {Selection, TextSelection} from "prosemirror-state"
3
3
 
4
4
  import {selectionBetween, selectionFromDOM, selectionToDOM} from "./selection"
5
- import {selectionCollapsed, keyEvent} from "./dom"
6
- import browser from "./browser"
5
+ import {selectionCollapsed, keyEvent, DOMNode} from "./dom"
6
+ import * as browser from "./browser"
7
+ import {EditorView} from "./index"
7
8
 
8
9
  // Note that all referencing and parsing is done with the
9
10
  // start-of-operation selection and document, since that's the one
@@ -11,18 +12,20 @@ import browser from "./browser"
11
12
  // the modification is mapped over those before it is applied, in
12
13
  // readDOMChange.
13
14
 
14
- function parseBetween(view, from_, to_) {
15
+ function parseBetween(view: EditorView, from_: number, to_: number) {
15
16
  let {node: parent, fromOffset, toOffset, from, to} = view.docView.parseRange(from_, to_)
16
17
 
17
- let domSel = view.root.getSelection(), find = null, anchor = domSel.anchorNode
18
+ let domSel = view.domSelection()
19
+ let find: {node: DOMNode, offset: number, pos?: number}[] | undefined
20
+ let anchor = domSel.anchorNode
18
21
  if (anchor && view.dom.contains(anchor.nodeType == 1 ? anchor : anchor.parentNode)) {
19
22
  find = [{node: anchor, offset: domSel.anchorOffset}]
20
23
  if (!selectionCollapsed(domSel))
21
- find.push({node: domSel.focusNode, offset: domSel.focusOffset})
24
+ find.push({node: domSel.focusNode!, offset: domSel.focusOffset})
22
25
  }
23
26
  // Work around issue in Chrome where backspacing sometimes replaces
24
27
  // the deleted content with a random BR node (issues #799, #831)
25
- if (browser.chrome && view.lastKeyCode === 8) {
28
+ if (browser.chrome && view.input.lastKeyCode === 8) {
26
29
  for (let off = toOffset; off > fromOffset; off--) {
27
30
  let node = parent.childNodes[off - 1], desc = node.pmViewDesc
28
31
  if (node.nodeName == "BR" && !desc) { toOffset = off; break }
@@ -40,7 +43,6 @@ function parseBetween(view, from_, to_) {
40
43
  from: fromOffset,
41
44
  to: toOffset,
42
45
  preserveWhitespace: $from.parent.type.whitespace == "pre" ? "full" : true,
43
- editableContent: true,
44
46
  findPositions: find,
45
47
  ruleFromNode,
46
48
  context: $from
@@ -53,7 +55,7 @@ function parseBetween(view, from_, to_) {
53
55
  return {doc, sel, from, to}
54
56
  }
55
57
 
56
- function ruleFromNode(dom) {
58
+ function ruleFromNode(dom: DOMNode): ParseRule | null {
57
59
  let desc = dom.pmViewDesc
58
60
  if (desc) {
59
61
  return desc.parseRule()
@@ -64,18 +66,19 @@ function ruleFromNode(dom) {
64
66
  if (browser.safari && /^(ul|ol)$/i.test(dom.parentNode.nodeName)) {
65
67
  let skip = document.createElement("div")
66
68
  skip.appendChild(document.createElement("li"))
67
- return {skip}
69
+ return {skip} as any
68
70
  } else if (dom.parentNode.lastChild == dom || browser.safari && /^(tr|table)$/i.test(dom.parentNode.nodeName)) {
69
71
  return {ignore: true}
70
72
  }
71
- } else if (dom.nodeName == "IMG" && dom.getAttribute("mark-placeholder")) {
73
+ } else if (dom.nodeName == "IMG" && (dom as HTMLElement).getAttribute("mark-placeholder")) {
72
74
  return {ignore: true}
73
75
  }
76
+ return null
74
77
  }
75
78
 
76
- export function readDOMChange(view, from, to, typeOver, addedNodes) {
79
+ export function readDOMChange(view: EditorView, from: number, to: number, typeOver: boolean, addedNodes: readonly DOMNode[]) {
77
80
  if (from < 0) {
78
- let origin = view.lastSelectionTime > Date.now() - 50 ? view.lastSelectionOrigin : null
81
+ let origin = view.input.lastSelectionTime > Date.now() - 50 ? view.input.lastSelectionOrigin : null
79
82
  let newSel = selectionFromDOM(view, origin)
80
83
  if (newSel && !view.state.selection.eq(newSel)) {
81
84
  let tr = view.state.tr.setSelection(newSel)
@@ -97,29 +100,29 @@ export function readDOMChange(view, from, to, typeOver, addedNodes) {
97
100
  // Chrome sometimes leaves the cursor before the inserted text when
98
101
  // composing after a cursor wrapper. This moves it forward.
99
102
  if (browser.chrome && view.cursorWrapper && parse.sel && parse.sel.anchor == view.cursorWrapper.deco.from) {
100
- let text = view.cursorWrapper.deco.type.toDOM.nextSibling
103
+ let text = (view.cursorWrapper.deco.type as any).toDOM.nextSibling as DOMNode
101
104
  let size = text && text.nodeValue ? text.nodeValue.length : 1
102
105
  parse.sel = {anchor: parse.sel.anchor + size, head: parse.sel.anchor + size}
103
106
  }
104
107
 
105
108
  let doc = view.state.doc, compare = doc.slice(parse.from, parse.to)
106
- let preferredPos, preferredSide
109
+ let preferredPos, preferredSide: "start" | "end"
107
110
  // Prefer anchoring to end when Backspace is pressed
108
- if (view.lastKeyCode === 8 && Date.now() - 100 < view.lastKeyCodeTime) {
111
+ if (view.input.lastKeyCode === 8 && Date.now() - 100 < view.input.lastKeyCodeTime) {
109
112
  preferredPos = view.state.selection.to
110
113
  preferredSide = "end"
111
114
  } else {
112
115
  preferredPos = view.state.selection.from
113
116
  preferredSide = "start"
114
117
  }
115
- view.lastKeyCode = null
118
+ view.input.lastKeyCode = null
116
119
 
117
120
  let change = findDiff(compare.content, parse.doc.content, parse.from, preferredPos, preferredSide)
118
- if ((browser.ios && view.lastIOSEnter > Date.now() - 225 || browser.android) &&
121
+ if ((browser.ios && view.input.lastIOSEnter > Date.now() - 225 || browser.android) &&
119
122
  addedNodes.some(n => n.nodeName == "DIV" || n.nodeName == "P") &&
120
123
  (!change || change.endA >= change.endB) &&
121
124
  view.someProp("handleKeyDown", f => f(view, keyEvent(13, "Enter")))) {
122
- view.lastIOSEnter = 0
125
+ view.input.lastIOSEnter = 0
123
126
  return
124
127
  }
125
128
  if (!change) {
@@ -134,7 +137,7 @@ export function readDOMChange(view, from, to, typeOver, addedNodes) {
134
137
  return
135
138
  }
136
139
  }
137
- view.domChangeCount++
140
+ view.input.domChangeCount++
138
141
  // Handle the case where overwriting a selection by typing matches
139
142
  // the start or end of the selected content, creating a change
140
143
  // that's smaller than what was actually overwritten.
@@ -168,13 +171,13 @@ export function readDOMChange(view, from, to, typeOver, addedNodes) {
168
171
  let nextSel
169
172
  // If this looks like the effect of pressing Enter (or was recorded
170
173
  // as being an iOS enter press), just dispatch an Enter key instead.
171
- if (((browser.ios && view.lastIOSEnter > Date.now() - 225 &&
174
+ if (((browser.ios && view.input.lastIOSEnter > Date.now() - 225 &&
172
175
  (!inlineChange || addedNodes.some(n => n.nodeName == "DIV" || n.nodeName == "P"))) ||
173
176
  (!inlineChange && $from.pos < parse.doc.content.size &&
174
177
  (nextSel = Selection.findFrom(parse.doc.resolve($from.pos + 1), 1, true)) &&
175
178
  nextSel.head == $to.pos)) &&
176
179
  view.someProp("handleKeyDown", f => f(view, keyEvent(13, "Enter")))) {
177
- view.lastIOSEnter = 0
180
+ view.input.lastIOSEnter = 0
178
181
  return
179
182
  }
180
183
  // Same for backspace
@@ -188,8 +191,8 @@ export function readDOMChange(view, from, to, typeOver, addedNodes) {
188
191
  // Chrome Android will occasionally, during composition, delete the
189
192
  // entire composition and then immediately insert it again. This is
190
193
  // used to detect that situation.
191
- if (browser.chrome && browser.android && change.toB == change.from)
192
- view.lastAndroidDelete = Date.now()
194
+ if (browser.chrome && browser.android && change.endB == change.start)
195
+ view.input.lastAndroidDelete = Date.now()
193
196
 
194
197
  // This tries to detect Android virtual keyboard
195
198
  // enter-and-pick-suggestion action. That sometimes (see issue
@@ -247,7 +250,7 @@ export function readDOMChange(view, from, to, typeOver, addedNodes) {
247
250
  // Edge just doesn't move the cursor forward when you start typing
248
251
  // in an empty block or between br nodes.
249
252
  if (sel && !(browser.chrome && browser.android && view.composing && sel.empty &&
250
- (change.start != change.endB || view.lastAndroidDelete < Date.now() - 100) &&
253
+ (change.start != change.endB || view.input.lastAndroidDelete < Date.now() - 100) &&
251
254
  (sel.head == chFrom || sel.head == tr.mapping.map(chTo) - 1) ||
252
255
  browser.ie && sel.empty && sel.head == chFrom))
253
256
  tr.setSelection(sel)
@@ -256,28 +259,27 @@ export function readDOMChange(view, from, to, typeOver, addedNodes) {
256
259
  view.dispatch(tr.scrollIntoView())
257
260
  }
258
261
 
259
- function resolveSelection(view, doc, parsedSel) {
262
+ function resolveSelection(view: EditorView, doc: Node, parsedSel: {anchor: number, head: number}) {
260
263
  if (Math.max(parsedSel.anchor, parsedSel.head) > doc.content.size) return null
261
264
  return selectionBetween(view, doc.resolve(parsedSel.anchor), doc.resolve(parsedSel.head))
262
265
  }
263
266
 
264
- // : (Fragment, Fragment) → ?{mark: Mark, type: string}
265
267
  // Given two same-length, non-empty fragments of inline content,
266
268
  // determine whether the first could be created from the second by
267
269
  // removing or adding a single mark type.
268
- function isMarkChange(cur, prev) {
269
- let curMarks = cur.firstChild.marks, prevMarks = prev.firstChild.marks
270
- let added = curMarks, removed = prevMarks, type, mark, update
270
+ function isMarkChange(cur: Fragment, prev: Fragment) {
271
+ let curMarks = cur.firstChild!.marks, prevMarks = prev.firstChild!.marks
272
+ let added = curMarks, removed = prevMarks, type, mark: Mark | undefined, update
271
273
  for (let i = 0; i < prevMarks.length; i++) added = prevMarks[i].removeFromSet(added)
272
274
  for (let i = 0; i < curMarks.length; i++) removed = curMarks[i].removeFromSet(removed)
273
275
  if (added.length == 1 && removed.length == 0) {
274
276
  mark = added[0]
275
277
  type = "add"
276
- update = node => node.mark(mark.addToSet(node.marks))
278
+ update = (node: Node) => node.mark(mark!.addToSet(node.marks))
277
279
  } else if (added.length == 0 && removed.length == 1) {
278
280
  mark = removed[0]
279
281
  type = "remove"
280
- update = node => node.mark(mark.removeFromSet(node.marks))
282
+ update = (node: Node) => node.mark(mark!.removeFromSet(node.marks))
281
283
  } else {
282
284
  return null
283
285
  }
@@ -286,7 +288,7 @@ function isMarkChange(cur, prev) {
286
288
  if (Fragment.from(updated).eq(cur)) return {mark, type}
287
289
  }
288
290
 
289
- function looksLikeJoin(old, start, end, $newStart, $newEnd) {
291
+ function looksLikeJoin(old: Node, start: number, end: number, $newStart: ResolvedPos, $newEnd: ResolvedPos) {
290
292
  if (!$newStart.parent.isTextblock ||
291
293
  // The content must have shrunk
292
294
  end - start <= $newEnd.pos - $newStart.pos ||
@@ -308,7 +310,7 @@ function looksLikeJoin(old, start, end, $newStart, $newEnd) {
308
310
  return $newStart.parent.content.cut($newStart.parentOffset).eq($next.parent.content)
309
311
  }
310
312
 
311
- function skipClosingAndOpening($pos, fromEnd, mayOpen) {
313
+ function skipClosingAndOpening($pos: ResolvedPos, fromEnd: boolean, mayOpen: boolean) {
312
314
  let depth = $pos.depth, end = fromEnd ? $pos.end() : $pos.pos
313
315
  while (depth > 0 && (fromEnd || $pos.indexAfter(depth) == $pos.node(depth).childCount)) {
314
316
  depth--
@@ -325,10 +327,10 @@ function skipClosingAndOpening($pos, fromEnd, mayOpen) {
325
327
  return end
326
328
  }
327
329
 
328
- function findDiff(a, b, pos, preferredPos, preferredSide) {
330
+ function findDiff(a: Fragment, b: Fragment, pos: number, preferredPos: number, preferredSide: "start" | "end") {
329
331
  let start = a.findDiffStart(b, pos)
330
332
  if (start == null) return null
331
- let {a: endA, b: endB} = a.findDiffEnd(b, pos + a.size, pos + b.size)
333
+ let {a: endA, b: endB} = a.findDiffEnd(b, pos + a.size, pos + b.size)!
332
334
  if (preferredSide == "end") {
333
335
  let adjust = Math.max(0, start - Math.min(endA, endB))
334
336
  preferredPos -= endA + adjust - start