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.
- package/dist/index.cjs +5733 -0
- package/dist/index.d.ts +799 -0
- package/dist/index.es.js +41 -41
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +4823 -5139
- package/dist/index.js.map +1 -1
- package/package.json +14 -14
- package/src/README.md +2 -0
- package/src/browser.ts +23 -0
- package/src/{capturekeys.js → capturekeys.ts} +38 -37
- package/src/{clipboard.js → clipboard.ts} +44 -41
- package/src/{decoration.js → decoration.ts} +313 -268
- package/src/{dom.js → dom.ts} +27 -24
- package/src/{domchange.js → domchange.ts} +38 -36
- package/src/{domcoords.js → domcoords.ts} +120 -99
- package/src/{domobserver.js → domobserver.ts} +41 -28
- package/src/index.ts +705 -0
- package/src/{input.js → input.ts} +203 -183
- package/src/{selection.js → selection.ts} +54 -48
- package/src/{viewdesc.js → viewdesc.ts} +342 -336
- package/rollup.config.js +0 -14
- package/src/browser.js +0 -23
- package/src/index.js +0 -668
|
@@ -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
|
-
|
|
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
|
|
30
|
-
let
|
|
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
|
|
48
|
+
doc.defaultView!.scrollBy(moveX, moveY)
|
|
43
49
|
} else {
|
|
44
|
-
let startX =
|
|
45
|
-
if (moveY)
|
|
46
|
-
if (moveX)
|
|
47
|
-
let dX =
|
|
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
|
|
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 (
|
|
79
|
-
stack.push({dom, top:
|
|
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 ? {
|
|
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
|
|
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)
|
|
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 =
|
|
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
|
|
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(
|
|
290
|
-
let rects =
|
|
291
|
-
return !rects.length ?
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
|
|
452
|
-
|
|
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"
|