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,16 +1,21 @@
1
+ import {EditorState} from "prosemirror-state"
1
2
  import {nodeSize, textRange, parentNode} from "./dom"
2
- import browser from "./browser"
3
+ import * as browser from "./browser"
4
+ import {EditorView} from "./index"
5
+ import {NodeViewDesc} from "./viewdesc"
3
6
 
4
- function windowRect(doc) {
7
+ export type Rect = {left: number, right: number, top: number, bottom: number}
8
+
9
+ function windowRect(doc: Document): Rect {
5
10
  return {left: 0, right: doc.documentElement.clientWidth,
6
11
  top: 0, bottom: doc.documentElement.clientHeight}
7
12
  }
8
13
 
9
- function getSide(value, side) {
14
+ function getSide(value: number | Rect, side: keyof Rect): number {
10
15
  return typeof value == "number" ? value : value[side]
11
16
  }
12
17
 
13
- function clientRect(node) {
18
+ function clientRect(node: HTMLElement): Rect {
14
19
  let rect = node.getBoundingClientRect()
15
20
  // Adjust for elements with style "transform: scale()"
16
21
  let scaleX = (rect.width / node.offsetWidth) || 1
@@ -20,14 +25,15 @@ function clientRect(node) {
20
25
  top: rect.top, bottom: rect.top + node.clientHeight * scaleY}
21
26
  }
22
27
 
23
- export function scrollRectIntoView(view, rect, startDOM) {
28
+ export function scrollRectIntoView(view: EditorView, rect: Rect, startDOM: Node) {
24
29
  let scrollThreshold = view.someProp("scrollThreshold") || 0, scrollMargin = view.someProp("scrollMargin") || 5
25
30
  let doc = view.dom.ownerDocument
26
- for (let parent = startDOM || view.dom;; parent = parentNode(parent)) {
31
+ for (let parent: Node | null = startDOM || view.dom;; parent = parentNode(parent)) {
27
32
  if (!parent) break
28
33
  if (parent.nodeType != 1) continue
29
- let atTop = parent == doc.body || parent.nodeType != 1
30
- let bounding = atTop ? windowRect(doc) : clientRect(parent)
34
+ let elt = parent as HTMLElement
35
+ let atTop = elt == doc.body
36
+ let bounding = atTop ? windowRect(doc) : clientRect(elt as HTMLElement)
31
37
  let moveX = 0, moveY = 0
32
38
  if (rect.top < bounding.top + getSide(scrollThreshold, "top"))
33
39
  moveY = -(bounding.top - rect.top + getSide(scrollMargin, "top"))
@@ -39,12 +45,12 @@ export function scrollRectIntoView(view, rect, startDOM) {
39
45
  moveX = rect.right - bounding.right + getSide(scrollMargin, "right")
40
46
  if (moveX || moveY) {
41
47
  if (atTop) {
42
- doc.defaultView.scrollBy(moveX, moveY)
48
+ doc.defaultView!.scrollBy(moveX, moveY)
43
49
  } else {
44
- let startX = parent.scrollLeft, startY = parent.scrollTop
45
- if (moveY) parent.scrollTop += moveY
46
- if (moveX) parent.scrollLeft += moveX
47
- let dX = parent.scrollLeft - startX, dY = parent.scrollTop - startY
50
+ let startX = elt.scrollLeft, startY = elt.scrollTop
51
+ if (moveY) elt.scrollTop += moveY
52
+ if (moveX) elt.scrollLeft += moveX
53
+ let dX = elt.scrollLeft - startX, dY = elt.scrollTop - startY
48
54
  rect = {left: rect.left - dX, top: rect.top - dY, right: rect.right - dX, bottom: rect.bottom - dY}
49
55
  }
50
56
  }
@@ -56,27 +62,31 @@ export function scrollRectIntoView(view, rect, startDOM) {
56
62
  // the top position of an element near the top of the editor, which
57
63
  // will be used to make sure the visible viewport remains stable even
58
64
  // when the size of the content above changes.
59
- export function storeScrollPos(view) {
65
+ export function storeScrollPos(view: EditorView): {
66
+ refDOM: HTMLElement,
67
+ refTop: number,
68
+ stack: {dom: HTMLElement, top: number, left: number}[]
69
+ } {
60
70
  let rect = view.dom.getBoundingClientRect(), startY = Math.max(0, rect.top)
61
- let refDOM, refTop
71
+ let refDOM: HTMLElement, refTop: number
62
72
  for (let x = (rect.left + rect.right) / 2, y = startY + 1;
63
73
  y < Math.min(innerHeight, rect.bottom); y += 5) {
64
74
  let dom = view.root.elementFromPoint(x, y)
65
- if (dom == view.dom || !view.dom.contains(dom)) continue
66
- let localRect = dom.getBoundingClientRect()
75
+ if (!dom || dom == view.dom || !view.dom.contains(dom)) continue
76
+ let localRect = (dom as HTMLElement).getBoundingClientRect()
67
77
  if (localRect.top >= startY - 20) {
68
- refDOM = dom
78
+ refDOM = dom as HTMLElement
69
79
  refTop = localRect.top
70
80
  break
71
81
  }
72
82
  }
73
- return {refDOM, refTop, stack: scrollStack(view.dom)}
83
+ return {refDOM: refDOM!, refTop: refTop!, stack: scrollStack(view.dom)}
74
84
  }
75
85
 
76
- function scrollStack(dom) {
86
+ function scrollStack(dom: Node): {dom: HTMLElement, top: number, left: number}[] {
77
87
  let stack = [], doc = dom.ownerDocument
78
- for (; dom; dom = parentNode(dom)) {
79
- stack.push({dom, top: dom.scrollTop, left: dom.scrollLeft})
88
+ for (let cur: Node | null = dom; cur; cur = parentNode(cur)) {
89
+ stack.push({dom: cur as HTMLElement, top: (cur as HTMLElement).scrollTop, left: (cur as HTMLElement).scrollLeft})
80
90
  if (dom == doc) break
81
91
  }
82
92
  return stack
@@ -84,12 +94,16 @@ function scrollStack(dom) {
84
94
 
85
95
  // Reset the scroll position of the editor's parent nodes to that what
86
96
  // it was before, when storeScrollPos was called.
87
- export function resetScrollPos({refDOM, refTop, stack}) {
97
+ export function resetScrollPos({refDOM, refTop, stack}: {
98
+ refDOM: HTMLElement,
99
+ refTop: number,
100
+ stack: {dom: HTMLElement, top: number, left: number}[]
101
+ }) {
88
102
  let newRefTop = refDOM ? refDOM.getBoundingClientRect().top : 0
89
103
  restoreScrollStack(stack, newRefTop == 0 ? 0 : newRefTop - refTop)
90
104
  }
91
105
 
92
- function restoreScrollStack(stack, dTop) {
106
+ function restoreScrollStack(stack: {dom: HTMLElement, top: number, left: number}[], dTop: number) {
93
107
  for (let i = 0; i < stack.length; i++) {
94
108
  let {dom, top, left} = stack[i]
95
109
  if (dom.scrollTop != top + dTop) dom.scrollTop = top + dTop
@@ -97,11 +111,11 @@ function restoreScrollStack(stack, dTop) {
97
111
  }
98
112
  }
99
113
 
100
- let preventScrollSupported = null
114
+ let preventScrollSupported: false | null | {preventScroll: boolean} = null
101
115
  // Feature-detects support for .focus({preventScroll: true}), and uses
102
116
  // a fallback kludge when not supported.
103
- export function focusPreventScroll(dom) {
104
- if (dom.setActive) return dom.setActive() // in IE
117
+ export function focusPreventScroll(dom: HTMLElement) {
118
+ if ((dom as any).setActive) return (dom as any).setActive() // in IE
105
119
  if (preventScrollSupported) return dom.focus(preventScrollSupported)
106
120
 
107
121
  let stored = scrollStack(dom)
@@ -117,13 +131,13 @@ export function focusPreventScroll(dom) {
117
131
  }
118
132
  }
119
133
 
120
- function findOffsetInNode(node, coords) {
121
- let closest, dxClosest = 2e8, coordsClosest, offset = 0
134
+ function findOffsetInNode(node: HTMLElement, coords: {top: number, left: number}): {node: Node, offset: number} {
135
+ let closest, dxClosest = 2e8, coordsClosest: {left: number, top: number} | undefined, offset = 0
122
136
  let rowBot = coords.top, rowTop = coords.top
123
137
  for (let child = node.firstChild, childIndex = 0; child; child = child.nextSibling, childIndex++) {
124
138
  let rects
125
- if (child.nodeType == 1) rects = child.getClientRects()
126
- else if (child.nodeType == 3) rects = textRange(child).getClientRects()
139
+ if (child.nodeType == 1) rects = (child as HTMLElement).getClientRects()
140
+ else if (child.nodeType == 3) rects = textRange(child as Text).getClientRects()
127
141
  else continue
128
142
 
129
143
  for (let i = 0; i < rects.length; i++) {
@@ -136,7 +150,10 @@ function findOffsetInNode(node, coords) {
136
150
  if (dx < dxClosest) {
137
151
  closest = child
138
152
  dxClosest = dx
139
- coordsClosest = dx && closest.nodeType == 3 ? {left: rect.right < coords.left ? rect.right : rect.left, top: coords.top} : coords
153
+ coordsClosest = dx && closest.nodeType == 3 ? {
154
+ left: rect.right < coords.left ? rect.right : rect.left,
155
+ top: coords.top
156
+ } : coords
140
157
  if (child.nodeType == 1 && dx)
141
158
  offset = childIndex + (coords.left >= (rect.left + rect.right) / 2 ? 1 : 0)
142
159
  continue
@@ -147,13 +164,13 @@ function findOffsetInNode(node, coords) {
147
164
  offset = childIndex + 1
148
165
  }
149
166
  }
150
- if (closest && closest.nodeType == 3) return findOffsetInText(closest, coordsClosest)
167
+ if (closest && closest.nodeType == 3) return findOffsetInText(closest as Text, coordsClosest!)
151
168
  if (!closest || (dxClosest && closest.nodeType == 1)) return {node, offset}
152
- return findOffsetInNode(closest, coordsClosest)
169
+ return findOffsetInNode(closest as HTMLElement, coordsClosest!)
153
170
  }
154
171
 
155
- function findOffsetInText(node, coords) {
156
- let len = node.nodeValue.length
172
+ function findOffsetInText(node: Text, coords: {top: number, left: number}) {
173
+ let len = node.nodeValue!.length
157
174
  let range = document.createRange()
158
175
  for (let i = 0; i < len; i++) {
159
176
  range.setEnd(node, i + 1)
@@ -166,28 +183,28 @@ function findOffsetInText(node, coords) {
166
183
  return {node, offset: 0}
167
184
  }
168
185
 
169
- function inRect(coords, rect) {
186
+ function inRect(coords: {top: number, left: number}, rect: Rect) {
170
187
  return coords.left >= rect.left - 1 && coords.left <= rect.right + 1&&
171
188
  coords.top >= rect.top - 1 && coords.top <= rect.bottom + 1
172
189
  }
173
190
 
174
- function targetKludge(dom, coords) {
191
+ function targetKludge(dom: HTMLElement, coords: {top: number, left: number}) {
175
192
  let parent = dom.parentNode
176
193
  if (parent && /^li$/i.test(parent.nodeName) && coords.left < dom.getBoundingClientRect().left)
177
- return parent
194
+ return parent as HTMLElement
178
195
  return dom
179
196
  }
180
197
 
181
- function posFromElement(view, elt, coords) {
198
+ function posFromElement(view: EditorView, elt: HTMLElement, coords: {top: number, left: number}) {
182
199
  let {node, offset} = findOffsetInNode(elt, coords), bias = -1
183
200
  if (node.nodeType == 1 && !node.firstChild) {
184
- let rect = node.getBoundingClientRect()
201
+ let rect = (node as HTMLElement).getBoundingClientRect()
185
202
  bias = rect.left != rect.right && coords.left > (rect.left + rect.right) / 2 ? 1 : -1
186
203
  }
187
204
  return view.docView.posFromDOM(node, offset, bias)
188
205
  }
189
206
 
190
- function posFromCaret(view, node, offset, coords) {
207
+ function posFromCaret(view: EditorView, node: Node, offset: number, coords: {top: number, left: number}) {
191
208
  // Browser (in caretPosition/RangeFromPoint) will agressively
192
209
  // normalize towards nearby inline nodes. Since we are interested in
193
210
  // positions between block nodes too, we first walk up the hierarchy
@@ -199,27 +216,27 @@ function posFromCaret(view, node, offset, coords) {
199
216
  if (cur == view.dom) break
200
217
  let desc = view.docView.nearestDesc(cur, true)
201
218
  if (!desc) return null
202
- if (desc.node.isBlock && desc.parent) {
203
- let rect = desc.dom.getBoundingClientRect()
219
+ if ((desc as NodeViewDesc).node.isBlock && desc.parent) {
220
+ let rect = (desc.dom as HTMLElement).getBoundingClientRect()
204
221
  if (rect.left > coords.left || rect.top > coords.top) outside = desc.posBefore
205
222
  else if (rect.right < coords.left || rect.bottom < coords.top) outside = desc.posAfter
206
223
  else break
207
224
  }
208
- cur = desc.dom.parentNode
225
+ cur = desc.dom.parentNode!
209
226
  }
210
- return outside > -1 ? outside : view.docView.posFromDOM(node, offset)
227
+ return outside > -1 ? outside : view.docView.posFromDOM(node, offset, 1)
211
228
  }
212
229
 
213
- function elementFromPoint(element, coords, box) {
230
+ function elementFromPoint(element: HTMLElement, coords: {top: number, left: number}, box: Rect): HTMLElement {
214
231
  let len = element.childNodes.length
215
232
  if (len && box.top < box.bottom) {
216
233
  for (let startI = Math.max(0, Math.min(len - 1, Math.floor(len * (coords.top - box.top) / (box.bottom - box.top)) - 2)), i = startI;;) {
217
234
  let child = element.childNodes[i]
218
235
  if (child.nodeType == 1) {
219
- let rects = child.getClientRects()
236
+ let rects = (child as HTMLElement).getClientRects()
220
237
  for (let j = 0; j < rects.length; j++) {
221
238
  let rect = rects[j]
222
- if (inRect(coords, rect)) return elementFromPoint(child, coords, rect)
239
+ if (inRect(coords, rect)) return elementFromPoint(child as HTMLElement, coords, rect)
223
240
  }
224
241
  }
225
242
  if ((i = (i + 1) % len) == startI) break
@@ -229,11 +246,11 @@ function elementFromPoint(element, coords, box) {
229
246
  }
230
247
 
231
248
  // Given an x,y position on the editor, get the position in the document.
232
- export function posAtCoords(view, coords) {
233
- let doc = view.dom.ownerDocument, node, offset
234
- if (doc.caretPositionFromPoint) {
249
+ export function posAtCoords(view: EditorView, coords: {top: number, left: number}) {
250
+ let doc = view.dom.ownerDocument, node: Node | undefined, offset = 0
251
+ if ((doc as any).caretPositionFromPoint) {
235
252
  try { // Firefox throws for this call in hard-to-predict circumstances (#994)
236
- let pos = doc.caretPositionFromPoint(coords.left, coords.top)
253
+ let pos = (doc as any).caretPositionFromPoint(coords.left, coords.top)
237
254
  if (pos) ({offsetNode: node, offset} = pos)
238
255
  } catch (_) {}
239
256
  }
@@ -242,7 +259,9 @@ export function posAtCoords(view, coords) {
242
259
  if (range) ({startContainer: node, startOffset: offset} = range)
243
260
  }
244
261
 
245
- let elt = (view.root.elementFromPoint ? view.root : doc).elementFromPoint(coords.left, coords.top + 1), pos
262
+ let elt = ((view.root as any).elementFromPoint ? view.root : doc)
263
+ .elementFromPoint(coords.left, coords.top + 1) as HTMLElement
264
+ let pos
246
265
  if (!elt || !view.dom.contains(elt.nodeType != 1 ? elt.parentNode : elt)) {
247
266
  let box = view.dom.getBoundingClientRect()
248
267
  if (!inRect(coords, box)) return null
@@ -251,8 +270,8 @@ export function posAtCoords(view, coords) {
251
270
  }
252
271
  // Safari's caretRangeFromPoint returns nonsense when on a draggable element
253
272
  if (browser.safari) {
254
- for (let p = elt; node && p; p = parentNode(p))
255
- if (p.draggable) node = offset = null
273
+ for (let p: Node | null = elt; node && p; p = parentNode(p))
274
+ if ((p as HTMLElement).draggable) node = undefined
256
275
  }
257
276
  elt = targetKludge(elt, coords)
258
277
  if (node) {
@@ -264,15 +283,15 @@ export function posAtCoords(view, coords) {
264
283
  // even if those are behind it.
265
284
  if (offset < node.childNodes.length) {
266
285
  let next = node.childNodes[offset], box
267
- if (next.nodeName == "IMG" && (box = next.getBoundingClientRect()).right <= coords.left &&
286
+ if (next.nodeName == "IMG" && (box = (next as HTMLElement).getBoundingClientRect()).right <= coords.left &&
268
287
  box.bottom > coords.top)
269
288
  offset++
270
289
  }
271
290
  }
272
291
  // Suspiciously specific kludge to work around caret*FromPoint
273
292
  // never returning a position at the end of the document
274
- if (node == view.dom && offset == node.childNodes.length - 1 && node.lastChild.nodeType == 1 &&
275
- coords.top > node.lastChild.getBoundingClientRect().bottom)
293
+ if (node == view.dom && offset == node.childNodes.length - 1 && node.lastChild!.nodeType == 1 &&
294
+ coords.top > (node.lastChild as HTMLElement).getBoundingClientRect().bottom)
276
295
  pos = view.state.doc.content.size
277
296
  // Ignore positions directly after a BR, since caret*FromPoint
278
297
  // 'round up' positions that would be more accurately placed
@@ -286,32 +305,31 @@ export function posAtCoords(view, coords) {
286
305
  return {pos, inside: desc ? desc.posAtStart - desc.border : -1}
287
306
  }
288
307
 
289
- function singleRect(object, bias) {
290
- let rects = object.getClientRects()
291
- return !rects.length ? object.getBoundingClientRect() : rects[bias < 0 ? 0 : rects.length - 1]
308
+ function singleRect(target: HTMLElement | Range, bias: number): DOMRect {
309
+ let rects = target.getClientRects()
310
+ return !rects.length ? target.getBoundingClientRect() : rects[bias < 0 ? 0 : rects.length - 1]
292
311
  }
293
312
 
294
313
  const BIDI = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/
295
314
 
296
- // : (EditorView, number, number) → {left: number, top: number, right: number, bottom: number}
297
315
  // Given a position in the document model, get a bounding box of the
298
316
  // character at that position, relative to the window.
299
- export function coordsAtPos(view, pos, side) {
317
+ export function coordsAtPos(view: EditorView, pos: number, side: number): Rect {
300
318
  let {node, offset} = view.docView.domFromPos(pos, side < 0 ? -1 : 1)
301
319
 
302
320
  let supportEmptyRange = browser.webkit || browser.gecko
303
321
  if (node.nodeType == 3) {
304
322
  // These browsers support querying empty text ranges. Prefer that in
305
323
  // bidi context or when at the end of a node.
306
- if (supportEmptyRange && (BIDI.test(node.nodeValue) || (side < 0 ? !offset : offset == node.nodeValue.length))) {
307
- let rect = singleRect(textRange(node, offset, offset), side)
324
+ if (supportEmptyRange && (BIDI.test(node.nodeValue!) || (side < 0 ? !offset : offset == node.nodeValue!.length))) {
325
+ let rect = singleRect(textRange(node as Text, offset, offset), side)
308
326
  // Firefox returns bad results (the position before the space)
309
327
  // when querying a position directly after line-broken
310
328
  // whitespace. Detect this situation and and kludge around it
311
- if (browser.gecko && offset && /\s/.test(node.nodeValue[offset - 1]) && offset < node.nodeValue.length) {
312
- let rectBefore = singleRect(textRange(node, offset - 1, offset - 1), -1)
329
+ if (browser.gecko && offset && /\s/.test(node.nodeValue![offset - 1]) && offset < node.nodeValue!.length) {
330
+ let rectBefore = singleRect(textRange(node as Text, offset - 1, offset - 1), -1)
313
331
  if (rectBefore.top == rect.top) {
314
- let rectAfter = singleRect(textRange(node, offset, offset + 1), -1)
332
+ let rectAfter = singleRect(textRange(node as Text, offset, offset + 1), -1)
315
333
  if (rectAfter.top != rect.top)
316
334
  return flattenV(rectAfter, rectAfter.left < rectBefore.left)
317
335
  }
@@ -320,10 +338,10 @@ export function coordsAtPos(view, pos, side) {
320
338
  } else {
321
339
  let from = offset, to = offset, takeSide = side < 0 ? 1 : -1
322
340
  if (side < 0 && !offset) { to++; takeSide = -1 }
323
- else if (side >= 0 && offset == node.nodeValue.length) { from--; takeSide = 1 }
341
+ else if (side >= 0 && offset == node.nodeValue!.length) { from--; takeSide = 1 }
324
342
  else if (side < 0) { from-- }
325
343
  else { to ++ }
326
- return flattenV(singleRect(textRange(node, from, to), takeSide), takeSide < 0)
344
+ return flattenV(singleRect(textRange(node as Text, from, to), takeSide), takeSide < 0)
327
345
  }
328
346
  }
329
347
 
@@ -331,49 +349,49 @@ export function coordsAtPos(view, pos, side) {
331
349
  if (!view.state.doc.resolve(pos).parent.inlineContent) {
332
350
  if (offset && (side < 0 || offset == nodeSize(node))) {
333
351
  let before = node.childNodes[offset - 1]
334
- if (before.nodeType == 1) return flattenH(before.getBoundingClientRect(), false)
352
+ if (before.nodeType == 1) return flattenH((before as HTMLElement).getBoundingClientRect(), false)
335
353
  }
336
354
  if (offset < nodeSize(node)) {
337
355
  let after = node.childNodes[offset]
338
- if (after.nodeType == 1) return flattenH(after.getBoundingClientRect(), true)
356
+ if (after.nodeType == 1) return flattenH((after as HTMLElement).getBoundingClientRect(), true)
339
357
  }
340
- return flattenH(node.getBoundingClientRect(), side >= 0)
358
+ return flattenH((node as HTMLElement).getBoundingClientRect(), side >= 0)
341
359
  }
342
360
 
343
361
  // Inline, not in text node (this is not Bidi-safe)
344
362
  if (offset && (side < 0 || offset == nodeSize(node))) {
345
363
  let before = node.childNodes[offset - 1]
346
- let target = before.nodeType == 3 ? textRange(before, nodeSize(before) - (supportEmptyRange ? 0 : 1))
364
+ let target = before.nodeType == 3 ? textRange(before as Text, nodeSize(before) - (supportEmptyRange ? 0 : 1))
347
365
  // BR nodes tend to only return the rectangle before them.
348
366
  // Only use them if they are the last element in their parent
349
367
  : before.nodeType == 1 && (before.nodeName != "BR" || !before.nextSibling) ? before : null
350
- if (target) return flattenV(singleRect(target, 1), false)
368
+ if (target) return flattenV(singleRect(target as Range | HTMLElement, 1), false)
351
369
  }
352
370
  if (offset < nodeSize(node)) {
353
371
  let after = node.childNodes[offset]
354
- while (after.pmViewDesc && after.pmViewDesc.ignoreForCoords) after = after.nextSibling
355
- let target = !after ? null : after.nodeType == 3 ? textRange(after, 0, (supportEmptyRange ? 0 : 1))
372
+ while (after.pmViewDesc && after.pmViewDesc.ignoreForCoords) after = after.nextSibling!
373
+ let target = !after ? null : after.nodeType == 3 ? textRange(after as Text, 0, (supportEmptyRange ? 0 : 1))
356
374
  : after.nodeType == 1 ? after : null
357
- if (target) return flattenV(singleRect(target, -1), true)
375
+ if (target) return flattenV(singleRect(target as Range | HTMLElement, -1), true)
358
376
  }
359
377
  // All else failed, just try to get a rectangle for the target node
360
- return flattenV(singleRect(node.nodeType == 3 ? textRange(node) : node, -side), side >= 0)
378
+ return flattenV(singleRect(node.nodeType == 3 ? textRange(node as Text) : node as HTMLElement, -side), side >= 0)
361
379
  }
362
380
 
363
- function flattenV(rect, left) {
381
+ function flattenV(rect: DOMRect, left: boolean) {
364
382
  if (rect.width == 0) return rect
365
383
  let x = left ? rect.left : rect.right
366
384
  return {top: rect.top, bottom: rect.bottom, left: x, right: x}
367
385
  }
368
386
 
369
- function flattenH(rect, top) {
387
+ function flattenH(rect: DOMRect, top: boolean) {
370
388
  if (rect.height == 0) return rect
371
389
  let y = top ? rect.top : rect.bottom
372
390
  return {top: y, bottom: y, left: rect.left, right: rect.right}
373
391
  }
374
392
 
375
- function withFlushedState(view, state, f) {
376
- let viewState = view.state, active = view.root.activeElement
393
+ function withFlushedState<T>(view: EditorView, state: EditorState, f: () => T): T {
394
+ let viewState = view.state, active = view.root.activeElement as HTMLElement
377
395
  if (viewState != state) view.updateState(state)
378
396
  if (active != view.dom) view.focus()
379
397
  try {
@@ -384,10 +402,9 @@ function withFlushedState(view, state, f) {
384
402
  }
385
403
  }
386
404
 
387
- // : (EditorView, number, number)
388
405
  // Whether vertical position motion in a given direction
389
406
  // from a position would leave a text block.
390
- function endOfTextblockVertical(view, state, dir) {
407
+ function endOfTextblockVertical(view: EditorView, state: EditorState, dir: "up" | "down") {
391
408
  let sel = state.selection
392
409
  let $pos = dir == "up" ? sel.$from : sel.$to
393
410
  return withFlushedState(view, state, () => {
@@ -395,14 +412,14 @@ function endOfTextblockVertical(view, state, dir) {
395
412
  for (;;) {
396
413
  let nearest = view.docView.nearestDesc(dom, true)
397
414
  if (!nearest) break
398
- if (nearest.node.isBlock) { dom = nearest.dom; break }
399
- dom = nearest.dom.parentNode
415
+ if (nearest.node!.isBlock) { dom = nearest.dom; break }
416
+ dom = nearest.dom.parentNode!
400
417
  }
401
418
  let coords = coordsAtPos(view, $pos.pos, 1)
402
419
  for (let child = dom.firstChild; child; child = child.nextSibling) {
403
420
  let boxes
404
- if (child.nodeType == 1) boxes = child.getClientRects()
405
- else if (child.nodeType == 3) boxes = textRange(child, 0, child.nodeValue.length).getClientRects()
421
+ if (child.nodeType == 1) boxes = (child as HTMLElement).getClientRects()
422
+ else if (child.nodeType == 3) boxes = textRange(child as Text, 0, child.nodeValue!.length).getClientRects()
406
423
  else continue
407
424
  for (let i = 0; i < boxes.length; i++) {
408
425
  let box = boxes[i]
@@ -418,14 +435,14 @@ function endOfTextblockVertical(view, state, dir) {
418
435
 
419
436
  const maybeRTL = /[\u0590-\u08ac]/
420
437
 
421
- function endOfTextblockHorizontal(view, state, dir) {
438
+ function endOfTextblockHorizontal(view: EditorView, state: EditorState, dir: "left" | "right" | "forward" | "backward") {
422
439
  let {$head} = state.selection
423
440
  if (!$head.parent.isTextblock) return false
424
441
  let offset = $head.parentOffset, atStart = !offset, atEnd = offset == $head.parent.content.size
425
- let sel = view.root.getSelection()
442
+ let sel = view.domSelection()
426
443
  // If the textblock is all LTR, or the browser doesn't support
427
444
  // Selection.modify (Edge), fall back to a primitive approach
428
- if (!maybeRTL.test($head.parent.textContent) || !sel.modify)
445
+ if (!maybeRTL.test($head.parent.textContent) || !(sel as any).modify)
429
446
  return dir == "left" || dir == "backward" ? atStart : atEnd
430
447
 
431
448
  return withFlushedState(view, state, () => {
@@ -435,21 +452,25 @@ function endOfTextblockHorizontal(view, state, dir) {
435
452
  // textblock (or doesn't move it at all, when at the start/end of
436
453
  // the document).
437
454
  let oldRange = sel.getRangeAt(0), oldNode = sel.focusNode, oldOff = sel.focusOffset
438
- let oldBidiLevel = sel.caretBidiLevel // Only for Firefox
439
- sel.modify("move", dir, "character")
455
+ let oldBidiLevel = (sel as any).caretBidiLevel // Only for Firefox
456
+ ;(sel as any).modify("move", dir, "character")
440
457
  let parentDOM = $head.depth ? view.docView.domAfterPos($head.before()) : view.dom
441
- let result = !parentDOM.contains(sel.focusNode.nodeType == 1 ? sel.focusNode : sel.focusNode.parentNode) ||
458
+ let result = !parentDOM.contains(sel.focusNode!.nodeType == 1 ? sel.focusNode : sel.focusNode!.parentNode) ||
442
459
  (oldNode == sel.focusNode && oldOff == sel.focusOffset)
443
460
  // Restore the previous selection
444
461
  sel.removeAllRanges()
445
462
  sel.addRange(oldRange)
446
- if (oldBidiLevel != null) sel.caretBidiLevel = oldBidiLevel
463
+ if (oldBidiLevel != null) (sel as any).caretBidiLevel = oldBidiLevel
447
464
  return result
448
465
  })
449
466
  }
450
467
 
451
- let cachedState = null, cachedDir = null, cachedResult = false
452
- export function endOfTextblock(view, state, dir) {
468
+ export type TextblockDir = "up" | "down" | "left" | "right" | "forward" | "backward"
469
+
470
+ let cachedState: EditorState | null = null
471
+ let cachedDir: TextblockDir | null = null
472
+ let cachedResult: boolean = false
473
+ export function endOfTextblock(view: EditorView, state: EditorState, dir: TextblockDir) {
453
474
  if (cachedState == state && cachedDir == dir) return cachedResult
454
475
  cachedState = state; cachedDir = dir
455
476
  return cachedResult = dir == "up" || dir == "down"