prosemirror-view 1.42.4 → 1.42.6
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/CHANGELOG.md +22 -0
- package/dist/index.cjs +150 -128
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +165 -140
- package/package.json +2 -2
- package/src/capturekeys.ts +31 -35
- package/src/clipboard.ts +10 -7
- package/src/dom.ts +15 -8
- package/src/domchange.ts +15 -6
- package/src/domcoords.ts +52 -53
- package/src/domobserver.ts +6 -7
- package/src/index.ts +4 -4
- package/src/input.ts +7 -7
- package/src/selection.ts +19 -6
- package/src/viewdesc.ts +37 -33
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prosemirror-view",
|
|
3
|
-
"version": "1.42.
|
|
3
|
+
"version": "1.42.6",
|
|
4
4
|
"description": "ProseMirror's view component",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"url": "git+https://code.haverbeke.berlin/prosemirror/prosemirror-view.git"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"prosemirror-model": "^1.25.
|
|
31
|
+
"prosemirror-model": "^1.25.12",
|
|
32
32
|
"prosemirror-state": "^1.0.0",
|
|
33
33
|
"prosemirror-transform": "^1.1.0"
|
|
34
34
|
},
|
package/src/capturekeys.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {Selection, NodeSelection, TextSelection, AllSelection, EditorState} from "prosemirror-state"
|
|
2
2
|
import {EditorView} from "./index"
|
|
3
3
|
import * as browser from "./browser"
|
|
4
|
-
import {domIndex, selectionCollapsed, hasBlockDesc} from "./dom"
|
|
4
|
+
import {domIndex, selectionCollapsed, hasBlockDesc, isElt, isText, nodeSize} from "./dom"
|
|
5
5
|
import {selectionToDOM} from "./selection"
|
|
6
6
|
|
|
7
7
|
function moveSelectionBlock(state: EditorState, dir: number) {
|
|
@@ -37,13 +37,13 @@ function selectHorizontally(view: EditorView, dir: number, mods: string) {
|
|
|
37
37
|
if (!(node.isAtom || (desc = view.docView.descAt(nodePos)) && !desc.contentDOM)) return false
|
|
38
38
|
if (NodeSelection.isSelectable(node)) {
|
|
39
39
|
return apply(view, new NodeSelection(dir < 0 ? view.state.doc.resolve($head.pos - node.nodeSize) : $head))
|
|
40
|
-
} else
|
|
40
|
+
} else {
|
|
41
41
|
// Chrome and Safari will introduce extra pointless cursor
|
|
42
|
-
// positions around inline uneditable nodes,
|
|
43
|
-
//
|
|
42
|
+
// positions around inline uneditable nodes (#937), and
|
|
43
|
+
// Firefox will sometimes just plain refuse to move across
|
|
44
|
+
// them (#1592), so in this situation we move the cursor
|
|
45
|
+
// ourselves.
|
|
44
46
|
return apply(view, new TextSelection(view.state.doc.resolve(dir < 0 ? nodePos : nodePos + node.nodeSize)))
|
|
45
|
-
} else {
|
|
46
|
-
return false
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
} else if (sel instanceof NodeSelection && sel.node.isInline) {
|
|
@@ -55,14 +55,10 @@ function selectHorizontally(view: EditorView, dir: number, mods: string) {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
function nodeLen(node: Node) {
|
|
59
|
-
return node.nodeType == 3 ? node.nodeValue!.length : node.childNodes.length
|
|
60
|
-
}
|
|
61
|
-
|
|
62
58
|
function isIgnorable(dom: Node, dir: number) {
|
|
63
59
|
let desc = dom.pmViewDesc
|
|
64
60
|
return desc ? desc.size == 0 && (dir < 0 || dom.nextSibling || dom.nodeName != "BR")
|
|
65
|
-
: dom
|
|
61
|
+
: isElt(dom) && dom.contentEditable == "false"
|
|
66
62
|
}
|
|
67
63
|
|
|
68
64
|
function skipIgnoredNodes(view: EditorView, dir: number) {
|
|
@@ -79,20 +75,19 @@ function skipIgnoredNodesBefore(view: EditorView) {
|
|
|
79
75
|
// Gecko will do odd things when the selection is directly in front
|
|
80
76
|
// of a non-editable node, so in that case, move it into the next
|
|
81
77
|
// node if possible. Issue prosemirror/prosemirror#832.
|
|
82
|
-
if (browser.gecko && node
|
|
78
|
+
if (browser.gecko && isElt(node) && offset < nodeSize(node) && isIgnorable(node.childNodes[offset], -1)) force = true
|
|
83
79
|
for (;;) {
|
|
84
80
|
if (offset > 0) {
|
|
85
|
-
if (node
|
|
86
|
-
|
|
81
|
+
if (!isElt(node)) break
|
|
82
|
+
let before = node.childNodes[offset - 1]
|
|
83
|
+
if (isIgnorable(before, -1)) {
|
|
84
|
+
moveNode = node
|
|
85
|
+
moveOffset = --offset
|
|
86
|
+
} else if (isText(before)) {
|
|
87
|
+
node = before
|
|
88
|
+
offset = node.nodeValue!.length
|
|
87
89
|
} else {
|
|
88
|
-
|
|
89
|
-
if (isIgnorable(before, -1)) {
|
|
90
|
-
moveNode = node
|
|
91
|
-
moveOffset = --offset
|
|
92
|
-
} else if (before.nodeType == 3) {
|
|
93
|
-
node = before
|
|
94
|
-
offset = node.nodeValue!.length
|
|
95
|
-
} else break
|
|
90
|
+
break
|
|
96
91
|
}
|
|
97
92
|
} else if (isBlockNode(node)) {
|
|
98
93
|
break
|
|
@@ -109,7 +104,7 @@ function skipIgnoredNodesBefore(view: EditorView) {
|
|
|
109
104
|
offset = 0
|
|
110
105
|
} else {
|
|
111
106
|
node = prev
|
|
112
|
-
offset =
|
|
107
|
+
offset = nodeSize(node)
|
|
113
108
|
}
|
|
114
109
|
}
|
|
115
110
|
}
|
|
@@ -123,17 +118,18 @@ function skipIgnoredNodesAfter(view: EditorView) {
|
|
|
123
118
|
let sel = view.domSelectionRange()
|
|
124
119
|
let node = sel.focusNode!, offset = sel.focusOffset
|
|
125
120
|
if (!node) return
|
|
126
|
-
let len =
|
|
121
|
+
let len = nodeSize(node)
|
|
127
122
|
let moveNode, moveOffset: number | undefined
|
|
128
123
|
for (;;) {
|
|
129
124
|
if (offset < len) {
|
|
130
|
-
if (node
|
|
125
|
+
if (!isElt(node)) break
|
|
131
126
|
let after = node.childNodes[offset]
|
|
132
127
|
if (isIgnorable(after, 1)) {
|
|
133
128
|
moveNode = node
|
|
134
129
|
moveOffset = ++offset
|
|
130
|
+
} else {
|
|
131
|
+
break
|
|
135
132
|
}
|
|
136
|
-
else break
|
|
137
133
|
} else if (isBlockNode(node)) {
|
|
138
134
|
break
|
|
139
135
|
} else {
|
|
@@ -150,7 +146,7 @@ function skipIgnoredNodesAfter(view: EditorView) {
|
|
|
150
146
|
} else {
|
|
151
147
|
node = next
|
|
152
148
|
offset = 0
|
|
153
|
-
len =
|
|
149
|
+
len = nodeSize(node)
|
|
154
150
|
}
|
|
155
151
|
}
|
|
156
152
|
}
|
|
@@ -169,8 +165,8 @@ function textNodeAfter(node: Node | null, offset: number): Text | undefined {
|
|
|
169
165
|
}
|
|
170
166
|
while (node && offset < node.childNodes.length) {
|
|
171
167
|
let next = node.childNodes[offset]
|
|
172
|
-
if (next
|
|
173
|
-
if (next
|
|
168
|
+
if (isText(next)) return next
|
|
169
|
+
if (isElt(next) && next.contentEditable == "false") break
|
|
174
170
|
node = next
|
|
175
171
|
offset = 0
|
|
176
172
|
}
|
|
@@ -183,15 +179,15 @@ function textNodeBefore(node: Node | null, offset: number): Text | undefined {
|
|
|
183
179
|
}
|
|
184
180
|
while (node && offset) {
|
|
185
181
|
let next = node.childNodes[offset - 1]
|
|
186
|
-
if (next
|
|
187
|
-
if (next
|
|
182
|
+
if (isText(next)) return next
|
|
183
|
+
if (isElt(next) && next.contentEditable == "false") break
|
|
188
184
|
node = next
|
|
189
185
|
offset = node.childNodes.length
|
|
190
186
|
}
|
|
191
187
|
}
|
|
192
188
|
|
|
193
189
|
function setSelFocus(view: EditorView, node: Node, offset: number) {
|
|
194
|
-
if (node
|
|
190
|
+
if (!isText(node)) {
|
|
195
191
|
let before, after
|
|
196
192
|
if (after = textNodeAfter(node, offset)) {
|
|
197
193
|
node = after
|
|
@@ -295,9 +291,9 @@ function switchEditable(view: EditorView, node: HTMLElement, state: string) {
|
|
|
295
291
|
function safariDownArrowBug(view: EditorView) {
|
|
296
292
|
if (!browser.safari || view.state.selection.$head.parentOffset > 0) return false
|
|
297
293
|
let {focusNode, focusOffset} = view.domSelectionRange()
|
|
298
|
-
if (focusNode && focusNode
|
|
299
|
-
focusNode.firstChild && (focusNode.firstChild
|
|
300
|
-
let child = focusNode.firstChild
|
|
294
|
+
if (focusNode && isElt(focusNode) && focusOffset == 0 &&
|
|
295
|
+
focusNode.firstChild && isElt(focusNode.firstChild) && focusNode.firstChild.contentEditable == "false") {
|
|
296
|
+
let child = focusNode.firstChild
|
|
301
297
|
switchEditable(view, child, "true")
|
|
302
298
|
setTimeout(() => switchEditable(view, child, "false"), 20)
|
|
303
299
|
}
|
package/src/clipboard.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {Slice, Fragment, DOMParser, DOMSerializer, ResolvedPos, NodeType, Node} from "prosemirror-model"
|
|
2
2
|
import * as browser from "./browser"
|
|
3
3
|
import {EditorView} from "./index"
|
|
4
|
+
import {isElt} from "./dom"
|
|
4
5
|
|
|
5
6
|
export function serializeForClipboard(view: EditorView, slice: Slice) {
|
|
6
7
|
view.someProp("transformCopied", f => { slice = f(slice!, view) })
|
|
@@ -19,7 +20,7 @@ export function serializeForClipboard(view: EditorView, slice: Slice) {
|
|
|
19
20
|
wrap.appendChild(serializer.serializeFragment(content, {document: doc}))
|
|
20
21
|
|
|
21
22
|
let firstChild = wrap.firstChild, needsWrap, wrappers = 0
|
|
22
|
-
while (firstChild && firstChild
|
|
23
|
+
while (firstChild && isElt(firstChild) && (needsWrap = wrapMap[firstChild.nodeName.toLowerCase()])) {
|
|
23
24
|
for (let i = needsWrap.length - 1; i >= 0; i--) {
|
|
24
25
|
let wrapper = doc.createElement(needsWrap[i])
|
|
25
26
|
while (wrap.firstChild) wrapper.appendChild(wrap.firstChild)
|
|
@@ -29,8 +30,8 @@ export function serializeForClipboard(view: EditorView, slice: Slice) {
|
|
|
29
30
|
firstChild = wrap.firstChild
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
if (firstChild && firstChild
|
|
33
|
-
|
|
33
|
+
if (firstChild && isElt(firstChild))
|
|
34
|
+
firstChild.setAttribute(
|
|
34
35
|
"data-pm-slice", `${openStart} ${openEnd}${wrappers ? ` -${wrappers}` : ""} ${JSON.stringify(context)}`)
|
|
35
36
|
|
|
36
37
|
let text = view.someProp("clipboardTextSerializer", f => f(slice, view)) ||
|
|
@@ -74,7 +75,7 @@ export function parseFromClipboard(view: EditorView, text: string, html: string
|
|
|
74
75
|
let sliceData = contextNode && /^(\d+) (\d+)(?: -(\d+))? (.*)/.exec(contextNode.getAttribute("data-pm-slice") || "")
|
|
75
76
|
if (sliceData && sliceData[3]) for (let i = +sliceData[3]; i > 0; i--) {
|
|
76
77
|
let child = dom!.firstChild
|
|
77
|
-
while (child && child
|
|
78
|
+
while (child && !isElt(child)) child = child.nextSibling
|
|
78
79
|
if (!child) break
|
|
79
80
|
dom = child as HTMLElement
|
|
80
81
|
}
|
|
@@ -270,9 +271,11 @@ function addContext(slice: Slice, context: string) {
|
|
|
270
271
|
for (let i = array.length - 2; i >= 0; i -= 2) {
|
|
271
272
|
let type = schema.nodes[array[i]]
|
|
272
273
|
if (!type || type.hasRequiredAttrs()) break
|
|
273
|
-
try {
|
|
274
|
-
|
|
275
|
-
|
|
274
|
+
try {
|
|
275
|
+
content = Fragment.from(type.create(array[i + 1], content))
|
|
276
|
+
} catch(e) {
|
|
277
|
+
break
|
|
278
|
+
}
|
|
276
279
|
openStart++; openEnd++
|
|
277
280
|
}
|
|
278
281
|
return new Slice(content, openStart, openEnd)
|
package/src/dom.ts
CHANGED
|
@@ -48,14 +48,14 @@ function scanFor(node: Node, off: number, targetNode: Node, targetOff: number, d
|
|
|
48
48
|
if (node == targetNode && off == targetOff) return true
|
|
49
49
|
if (off == (dir < 0 ? 0 : nodeSize(node))) {
|
|
50
50
|
let parent = node.parentNode
|
|
51
|
-
if (!parent || parent
|
|
51
|
+
if (!parent || !isElt(parent) || hasBlockDesc(node) || atomElements.test(node.nodeName) ||
|
|
52
52
|
(node as HTMLElement).contentEditable == "false")
|
|
53
53
|
return false
|
|
54
54
|
off = domIndex(node) + (dir < 0 ? 0 : 1)
|
|
55
55
|
node = parent
|
|
56
|
-
} else if (node
|
|
56
|
+
} else if (isElt(node)) {
|
|
57
57
|
let child = node.childNodes[off + (dir < 0 ? -1 : 0)]
|
|
58
|
-
if (child
|
|
58
|
+
if (isElt(child) && child.contentEditable == "false") {
|
|
59
59
|
if (child.pmViewDesc?.ignoreForSelection) off += dir
|
|
60
60
|
else return false
|
|
61
61
|
} else {
|
|
@@ -72,10 +72,17 @@ export function nodeSize(node: Node) {
|
|
|
72
72
|
return node.nodeType == 3 ? node.nodeValue!.length : node.childNodes.length
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
export function isElt(node: Node): node is HTMLElement { return node.nodeType == 1 }
|
|
76
|
+
export function isText(node: Node): node is Text { return node.nodeType == 3 }
|
|
77
|
+
|
|
78
|
+
export function contains(parent: Node, child: Node | null) {
|
|
79
|
+
return child ? parent.contains(isElt(child) ? child : child.parentNode) : false
|
|
80
|
+
}
|
|
81
|
+
|
|
75
82
|
export function textNodeBefore(node: Node, offset: number) {
|
|
76
83
|
for (;;) {
|
|
77
|
-
if (node
|
|
78
|
-
if (node
|
|
84
|
+
if (isText(node) && offset) return node
|
|
85
|
+
if (isElt(node) && offset > 0) {
|
|
79
86
|
if ((node as HTMLElement).contentEditable == "false") return null
|
|
80
87
|
node = node.childNodes[offset - 1]
|
|
81
88
|
offset = nodeSize(node)
|
|
@@ -90,9 +97,9 @@ export function textNodeBefore(node: Node, offset: number) {
|
|
|
90
97
|
|
|
91
98
|
export function textNodeAfter(node: Node, offset: number) {
|
|
92
99
|
for (;;) {
|
|
93
|
-
if (node
|
|
94
|
-
if (node
|
|
95
|
-
if (
|
|
100
|
+
if (isText(node) && offset < node.nodeValue!.length) return node as Text
|
|
101
|
+
if (isElt(node) && offset < node.childNodes.length) {
|
|
102
|
+
if (node.contentEditable == "false") return null
|
|
96
103
|
node = node.childNodes[offset]
|
|
97
104
|
offset = 0
|
|
98
105
|
} else if (node.parentNode && !hasBlockDesc(node)) {
|
package/src/domchange.ts
CHANGED
|
@@ -2,7 +2,7 @@ import {Fragment, DOMParser, TagParseRule, Node, Mark, ResolvedPos} from "prosem
|
|
|
2
2
|
import {TextSelection, Transaction} from "prosemirror-state"
|
|
3
3
|
|
|
4
4
|
import {selectionBetween, selectionFromDOM, selectionToDOM} from "./selection"
|
|
5
|
-
import {selectionCollapsed, keyEvent, DOMNode} from "./dom"
|
|
5
|
+
import {selectionCollapsed, keyEvent, DOMNode, isElt, contains} from "./dom"
|
|
6
6
|
import * as browser from "./browser"
|
|
7
7
|
import {EditorView} from "./index"
|
|
8
8
|
|
|
@@ -18,7 +18,7 @@ function parseBetween(view: EditorView, from_: number, to_: number, addedNodes:
|
|
|
18
18
|
let domSel = view.domSelectionRange()
|
|
19
19
|
let find: {node: DOMNode, offset: number, pos?: number}[] | undefined
|
|
20
20
|
let anchor = domSel.anchorNode
|
|
21
|
-
if (anchor && view.dom
|
|
21
|
+
if (anchor && contains(view.dom, anchor)) {
|
|
22
22
|
find = [{node: anchor, offset: domSel.anchorOffset}]
|
|
23
23
|
if (!selectionCollapsed(domSel))
|
|
24
24
|
find.push({node: domSel.focusNode!, offset: domSel.focusOffset})
|
|
@@ -122,7 +122,7 @@ export function readDOMChange(view: EditorView, from: number, to: number, typeOv
|
|
|
122
122
|
let change = findDiff(compare.content, parse.doc.content, parse.from, preferredPos, preferredSide)
|
|
123
123
|
if (change) view.input.domChangeCount++
|
|
124
124
|
if ((browser.ios && view.input.lastIOSEnter > Date.now() - 225 || browser.android) &&
|
|
125
|
-
addedNodes.some(n => n
|
|
125
|
+
addedNodes.some(n => isElt(n) && !isInline.test(n.nodeName)) &&
|
|
126
126
|
(!change || change.endA >= change.endB) &&
|
|
127
127
|
view.someProp("handleKeyDown", f => f(view, keyEvent(13, "Enter")))) {
|
|
128
128
|
view.input.lastIOSEnter = 0
|
|
@@ -180,9 +180,7 @@ export function readDOMChange(view: EditorView, from: number, to: number, typeOv
|
|
|
180
180
|
// as being an iOS enter press), just dispatch an Enter key instead.
|
|
181
181
|
if (((browser.ios && view.input.lastIOSEnter > Date.now() - 225 &&
|
|
182
182
|
(!inlineChange || addedNodes.some(n => n.nodeName == "DIV" || n.nodeName == "P"))) ||
|
|
183
|
-
(!inlineChange && $from.pos
|
|
184
|
-
(!$from.sameParent($to) || !$from.parent.inlineContent) &&
|
|
185
|
-
$from.pos < $to.pos && !/\S/.test(parse.doc.textBetween($from.pos, $to.pos, "", "")))) &&
|
|
183
|
+
(!inlineChange && looksLikeEnter(parse.doc, $from.pos, $to.pos))) &&
|
|
186
184
|
view.someProp("handleKeyDown", f => f(view, keyEvent(13, "Enter")))) {
|
|
187
185
|
view.input.lastIOSEnter = 0
|
|
188
186
|
return
|
|
@@ -333,6 +331,17 @@ function looksLikeBackspace(old: Node, start: number, end: number, $newStart: Re
|
|
|
333
331
|
return $newStart.parent.content.cut($newStart.parentOffset).eq($next.parent.content)
|
|
334
332
|
}
|
|
335
333
|
|
|
334
|
+
function looksLikeEnter(content: Node, from: number, to: number) {
|
|
335
|
+
let newTextlines = 0, newLeaves = 0
|
|
336
|
+
content.nodesBetween(from, to, (node, pos) => {
|
|
337
|
+
if (node.isTextblock && pos >= from && pos < to)
|
|
338
|
+
newTextlines++
|
|
339
|
+
else if (node.isText ? /\S/.test(node.text!.slice(Math.max(0, from - pos), Math.min(node.nodeSize, to - pos))) : node.isLeaf)
|
|
340
|
+
newLeaves++
|
|
341
|
+
})
|
|
342
|
+
return newLeaves == 0 && newTextlines == 1
|
|
343
|
+
}
|
|
344
|
+
|
|
336
345
|
function skipClosingAndOpening($pos: ResolvedPos, fromEnd: boolean, mayOpen: boolean) {
|
|
337
346
|
let depth = $pos.depth, end = fromEnd ? $pos.end() : $pos.pos
|
|
338
347
|
while (depth > 0 && (fromEnd || $pos.indexAfter(depth) == $pos.node(depth).childCount)) {
|
package/src/domcoords.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {EditorState} from "prosemirror-state"
|
|
2
|
-
import {nodeSize, textRange, parentNode, caretFromPoint} from "./dom"
|
|
2
|
+
import {nodeSize, textRange, parentNode, caretFromPoint, isElt, isText, contains} from "./dom"
|
|
3
3
|
import * as browser from "./browser"
|
|
4
4
|
import {EditorView} from "./index"
|
|
5
5
|
|
|
@@ -37,10 +37,9 @@ export function scrollRectIntoView(view: EditorView, rect: Rect, startDOM: Node)
|
|
|
37
37
|
let doc = view.dom.ownerDocument
|
|
38
38
|
for (let parent: Node | null = startDOM || view.dom;;) {
|
|
39
39
|
if (!parent) break
|
|
40
|
-
if (parent
|
|
41
|
-
let
|
|
42
|
-
let
|
|
43
|
-
let bounding = atTop ? windowRect(doc) : clientRect(elt as HTMLElement)
|
|
40
|
+
if (!isElt(parent)) { parent = parentNode(parent); continue }
|
|
41
|
+
let atTop = parent == doc.body
|
|
42
|
+
let bounding = atTop ? windowRect(doc) : clientRect(parent)
|
|
44
43
|
let moveX = 0, moveY = 0
|
|
45
44
|
if (rect.top < bounding.top + getSide(scrollThreshold, "top"))
|
|
46
45
|
moveY = -(bounding.top - rect.top + getSide(scrollMargin, "top"))
|
|
@@ -56,16 +55,16 @@ export function scrollRectIntoView(view: EditorView, rect: Rect, startDOM: Node)
|
|
|
56
55
|
if (atTop) {
|
|
57
56
|
doc.defaultView!.scrollBy(moveX, moveY)
|
|
58
57
|
} else {
|
|
59
|
-
let startX =
|
|
60
|
-
if (moveY)
|
|
61
|
-
if (moveX)
|
|
62
|
-
let dX =
|
|
58
|
+
let startX = parent.scrollLeft, startY = parent.scrollTop
|
|
59
|
+
if (moveY) parent.scrollTop += moveY
|
|
60
|
+
if (moveX) parent.scrollLeft += moveX
|
|
61
|
+
let dX = parent.scrollLeft - startX, dY = parent.scrollTop - startY
|
|
63
62
|
rect = {left: rect.left - dX, top: rect.top - dY, right: rect.right - dX, bottom: rect.bottom - dY}
|
|
64
63
|
}
|
|
65
64
|
}
|
|
66
|
-
let pos: string = atTop ? "fixed" : getComputedStyle(parent
|
|
65
|
+
let pos: string = atTop ? "fixed" : getComputedStyle(parent).position
|
|
67
66
|
if (/^(fixed|sticky)$/.test(pos)) break
|
|
68
|
-
parent = pos == "absolute" ?
|
|
67
|
+
parent = pos == "absolute" ? parent.offsetParent : parentNode(parent)
|
|
69
68
|
}
|
|
70
69
|
}
|
|
71
70
|
|
|
@@ -84,7 +83,7 @@ export function storeScrollPos(view: EditorView): {
|
|
|
84
83
|
y < Math.min(innerHeight, rect.bottom); y += 5) {
|
|
85
84
|
let dom = view.root.elementFromPoint(x, y)
|
|
86
85
|
if (!dom || dom == view.dom || !view.dom.contains(dom)) continue
|
|
87
|
-
let localRect =
|
|
86
|
+
let localRect = dom.getBoundingClientRect()
|
|
88
87
|
if (localRect.top >= startY - 20) {
|
|
89
88
|
refDOM = dom as HTMLElement
|
|
90
89
|
refTop = localRect.top
|
|
@@ -143,13 +142,14 @@ export function focusPreventScroll(dom: HTMLElement) {
|
|
|
143
142
|
}
|
|
144
143
|
|
|
145
144
|
function findOffsetInNode(node: HTMLElement, coords: {top: number, left: number}): {node: Node, offset: number} {
|
|
146
|
-
let closest
|
|
145
|
+
let closest: Text | HTMLElement | undefined
|
|
146
|
+
let dxClosest = 2e8, coordsClosest: {left: number, top: number} | undefined, offset = 0
|
|
147
147
|
let rowBot = coords.top, rowTop = coords.top
|
|
148
|
-
let firstBelow:
|
|
148
|
+
let firstBelow: Text | HTMLElement | undefined, coordsBelow: {left: number, top: number} | undefined
|
|
149
149
|
for (let child = node.firstChild, childIndex = 0; child; child = child.nextSibling, childIndex++) {
|
|
150
150
|
let rects
|
|
151
|
-
if (child
|
|
152
|
-
else if (child
|
|
151
|
+
if (isElt(child)) rects = child.getClientRects()
|
|
152
|
+
else if (isText(child)) rects = textRange(child).getClientRects()
|
|
153
153
|
else continue
|
|
154
154
|
|
|
155
155
|
for (let i = 0; i < rects.length; i++) {
|
|
@@ -162,11 +162,11 @@ function findOffsetInNode(node: HTMLElement, coords: {top: number, left: number}
|
|
|
162
162
|
if (dx < dxClosest) {
|
|
163
163
|
closest = child
|
|
164
164
|
dxClosest = dx
|
|
165
|
-
coordsClosest = dx && closest
|
|
165
|
+
coordsClosest = dx && isText(closest) ? {
|
|
166
166
|
left: rect.right < coords.left ? rect.right : rect.left,
|
|
167
167
|
top: coords.top
|
|
168
168
|
} : coords
|
|
169
|
-
if (child
|
|
169
|
+
if (isElt(child) && dx)
|
|
170
170
|
offset = childIndex + (coords.left >= (rect.left + rect.right) / 2 ? 1 : 0)
|
|
171
171
|
continue
|
|
172
172
|
}
|
|
@@ -180,9 +180,9 @@ function findOffsetInNode(node: HTMLElement, coords: {top: number, left: number}
|
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
182
|
if (!closest && firstBelow) { closest = firstBelow; coordsClosest = coordsBelow; dxClosest = 0 }
|
|
183
|
-
if (closest && closest
|
|
184
|
-
if (!closest ||
|
|
185
|
-
return findOffsetInNode(closest
|
|
183
|
+
if (closest && isText(closest)) return findOffsetInText(closest, coordsClosest!)
|
|
184
|
+
if (!closest || dxClosest) return {node, offset}
|
|
185
|
+
return findOffsetInNode(closest, coordsClosest!)
|
|
186
186
|
}
|
|
187
187
|
|
|
188
188
|
function findOffsetInText(node: Text, coords: {top: number, left: number}) {
|
|
@@ -216,8 +216,8 @@ function targetKludge(dom: HTMLElement, coords: {top: number, left: number}) {
|
|
|
216
216
|
|
|
217
217
|
function posFromElement(view: EditorView, elt: HTMLElement, coords: {top: number, left: number}) {
|
|
218
218
|
let {node, offset} = findOffsetInNode(elt, coords), bias = -1
|
|
219
|
-
if (node
|
|
220
|
-
let rect =
|
|
219
|
+
if (isElt(node) && !node.firstChild) {
|
|
220
|
+
let rect = node.getBoundingClientRect()
|
|
221
221
|
bias = rect.left != rect.right && coords.left > (rect.left + rect.right) / 2 ? 1 : -1
|
|
222
222
|
}
|
|
223
223
|
return view.docView.posFromDOM(node, offset, bias)
|
|
@@ -235,9 +235,9 @@ function posFromCaret(view: EditorView, node: Node, offset: number, coords: {top
|
|
|
235
235
|
if (cur == view.dom) break
|
|
236
236
|
let desc = view.docView.nearestDesc(cur, true), rect
|
|
237
237
|
if (!desc) return null
|
|
238
|
-
if (desc.dom
|
|
238
|
+
if (isElt(desc.dom) && (desc.node.isBlock && desc.parent || !desc.contentDOM) &&
|
|
239
239
|
// Ignore elements with zero-size bounding rectangles
|
|
240
|
-
((rect =
|
|
240
|
+
((rect = desc.dom.getBoundingClientRect()).width || rect.height)) {
|
|
241
241
|
if (desc.node.isBlock && desc.parent && !/^T(R|BODY|HEAD|FOOT)$/.test(desc.dom!.nodeName)) {
|
|
242
242
|
// Only apply the horizontal test to the innermost block. Vertical for any parent.
|
|
243
243
|
if (!sawBlock && rect.left > coords.left || rect.top > coords.top) outsideBlock = desc.posBefore
|
|
@@ -261,11 +261,11 @@ function elementFromPoint(element: HTMLElement, coords: {top: number, left: numb
|
|
|
261
261
|
if (len && box.top < box.bottom) {
|
|
262
262
|
for (let startI = Math.max(0, Math.min(len - 1, Math.floor(len * (coords.top - box.top) / (box.bottom - box.top)) - 2)), i = startI;;) {
|
|
263
263
|
let child = element.childNodes[i]
|
|
264
|
-
if (child
|
|
265
|
-
let rects =
|
|
264
|
+
if (isElt(child)) {
|
|
265
|
+
let rects = child.getClientRects()
|
|
266
266
|
for (let j = 0; j < rects.length; j++) {
|
|
267
267
|
let rect = rects[j]
|
|
268
|
-
if (inRect(coords, rect)) return elementFromPoint(child
|
|
268
|
+
if (inRect(coords, rect)) return elementFromPoint(child, coords, rect)
|
|
269
269
|
}
|
|
270
270
|
}
|
|
271
271
|
if ((i = (i + 1) % len) == startI) break
|
|
@@ -283,7 +283,7 @@ export function posAtCoords(view: EditorView, coords: {top: number, left: number
|
|
|
283
283
|
let elt = ((view.root as any).elementFromPoint ? view.root : doc)
|
|
284
284
|
.elementFromPoint(coords.left, coords.top) as HTMLElement
|
|
285
285
|
let pos
|
|
286
|
-
if (!elt || !view.dom.contains(elt
|
|
286
|
+
if (!elt || !view.dom.contains(elt)) {
|
|
287
287
|
let box = view.dom.getBoundingClientRect()
|
|
288
288
|
if (!inRect(coords, box)) return null
|
|
289
289
|
elt = elementFromPoint(view.dom, coords, box)
|
|
@@ -296,7 +296,7 @@ export function posAtCoords(view: EditorView, coords: {top: number, left: number
|
|
|
296
296
|
}
|
|
297
297
|
elt = targetKludge(elt, coords)
|
|
298
298
|
if (node) {
|
|
299
|
-
if (browser.gecko && node
|
|
299
|
+
if (browser.gecko && isElt(node)) {
|
|
300
300
|
// Firefox will sometimes return offsets into <input> nodes, which
|
|
301
301
|
// have no actual children, from caretPositionFromPoint (#953)
|
|
302
302
|
offset = Math.min(offset, node.childNodes.length)
|
|
@@ -311,18 +311,18 @@ export function posAtCoords(view: EditorView, coords: {top: number, left: number
|
|
|
311
311
|
}
|
|
312
312
|
let prev
|
|
313
313
|
// When clicking above the right side of an uneditable node, Chrome will report a cursor position after that node.
|
|
314
|
-
if (browser.webkit && offset && node
|
|
315
|
-
|
|
314
|
+
if (browser.webkit && offset && isElt(node) && isElt(prev = node.childNodes[offset - 1]) &&
|
|
315
|
+
prev.contentEditable == "false" && prev.getBoundingClientRect().top >= coords.top)
|
|
316
316
|
offset--
|
|
317
317
|
// Suspiciously specific kludge to work around caret*FromPoint
|
|
318
318
|
// never returning a position at the end of the document
|
|
319
|
-
if (node == view.dom && offset == node.childNodes.length - 1 && node.lastChild
|
|
320
|
-
coords.top >
|
|
319
|
+
if (node == view.dom && offset == node.childNodes.length - 1 && isElt(node.lastChild!) &&
|
|
320
|
+
coords.top > node.lastChild.getBoundingClientRect().bottom)
|
|
321
321
|
pos = view.state.doc.content.size
|
|
322
322
|
// Ignore positions directly after a BR, since caret*FromPoint
|
|
323
323
|
// 'round up' positions that would be more accurately placed
|
|
324
324
|
// before the BR node.
|
|
325
|
-
else if (offset == 0 || node
|
|
325
|
+
else if (offset == 0 || !isElt(node) || node.childNodes[offset - 1].nodeName != "BR")
|
|
326
326
|
pos = posFromCaret(view, node, offset, coords)
|
|
327
327
|
}
|
|
328
328
|
if (pos == null) pos = posFromElement(view, elt, coords)
|
|
@@ -352,18 +352,18 @@ export function coordsAtPos(view: EditorView, pos: number, side: number): Rect {
|
|
|
352
352
|
let {node, offset, atom} = view.docView.domFromPos(pos, side < 0 ? -1 : 1)
|
|
353
353
|
|
|
354
354
|
let supportEmptyRange = browser.webkit || browser.gecko
|
|
355
|
-
if (node
|
|
355
|
+
if (isText(node)) {
|
|
356
356
|
// These browsers support querying empty text ranges. Prefer that in
|
|
357
357
|
// bidi context or when at the end of a node.
|
|
358
358
|
if (supportEmptyRange && (BIDI.test(node.nodeValue!) || (side < 0 ? !offset : offset == node.nodeValue!.length))) {
|
|
359
|
-
let rect = singleRect(textRange(node
|
|
359
|
+
let rect = singleRect(textRange(node, offset, offset), side)
|
|
360
360
|
// Firefox returns bad results (the position before the space)
|
|
361
361
|
// when querying a position directly after line-broken
|
|
362
362
|
// whitespace. Detect this situation and and kludge around it
|
|
363
363
|
if (browser.gecko && offset && /\s/.test(node.nodeValue![offset - 1]) && offset < node.nodeValue!.length) {
|
|
364
|
-
let rectBefore = singleRect(textRange(node
|
|
364
|
+
let rectBefore = singleRect(textRange(node, offset - 1, offset - 1), -1)
|
|
365
365
|
if (rectBefore.top == rect.top) {
|
|
366
|
-
let rectAfter = singleRect(textRange(node
|
|
366
|
+
let rectAfter = singleRect(textRange(node, offset, offset + 1), -1)
|
|
367
367
|
if (rectAfter.top != rect.top)
|
|
368
368
|
return flattenV(rectAfter, rectAfter.left < rectBefore.left)
|
|
369
369
|
}
|
|
@@ -375,7 +375,7 @@ export function coordsAtPos(view: EditorView, pos: number, side: number): Rect {
|
|
|
375
375
|
else if (side >= 0 && offset == node.nodeValue!.length) { from--; takeSide = 1 }
|
|
376
376
|
else if (side < 0) { from-- }
|
|
377
377
|
else { to ++ }
|
|
378
|
-
return flattenV(singleRect(textRange(node
|
|
378
|
+
return flattenV(singleRect(textRange(node, from, to), takeSide), takeSide < 0)
|
|
379
379
|
}
|
|
380
380
|
}
|
|
381
381
|
|
|
@@ -384,11 +384,11 @@ export function coordsAtPos(view: EditorView, pos: number, side: number): Rect {
|
|
|
384
384
|
if (!$dom.parent.inlineContent) {
|
|
385
385
|
if (atom == null && offset && (side < 0 || offset == nodeSize(node))) {
|
|
386
386
|
let before = node.childNodes[offset - 1]
|
|
387
|
-
if (before
|
|
387
|
+
if (isElt(before)) return flattenH(before.getBoundingClientRect(), false)
|
|
388
388
|
}
|
|
389
389
|
if (atom == null && offset < nodeSize(node)) {
|
|
390
390
|
let after = node.childNodes[offset]
|
|
391
|
-
if (after
|
|
391
|
+
if (isElt(after)) return flattenH(after.getBoundingClientRect(), true)
|
|
392
392
|
}
|
|
393
393
|
return flattenH((node as HTMLElement).getBoundingClientRect(), side >= 0)
|
|
394
394
|
}
|
|
@@ -396,21 +396,21 @@ export function coordsAtPos(view: EditorView, pos: number, side: number): Rect {
|
|
|
396
396
|
// Inline, not in text node (this is not Bidi-safe)
|
|
397
397
|
if (atom == null && offset && (side < 0 || offset == nodeSize(node))) {
|
|
398
398
|
let before = node.childNodes[offset - 1]
|
|
399
|
-
let target = before
|
|
399
|
+
let target = isText(before) ? textRange(before, nodeSize(before) - (supportEmptyRange ? 0 : 1))
|
|
400
400
|
// BR nodes tend to only return the rectangle before them.
|
|
401
401
|
// Only use them if they are the last element in their parent
|
|
402
|
-
: before
|
|
403
|
-
if (target) return flattenV(singleRect(target
|
|
402
|
+
: isElt(before) && (before.nodeName != "BR" || !before.nextSibling) ? before : null
|
|
403
|
+
if (target) return flattenV(singleRect(target, 1), false)
|
|
404
404
|
}
|
|
405
405
|
if (atom == null && offset < nodeSize(node)) {
|
|
406
406
|
let after = node.childNodes[offset]
|
|
407
407
|
while (after.pmViewDesc && after.pmViewDesc.ignoreForCoords) after = after.nextSibling!
|
|
408
|
-
let target = !after ? null : after
|
|
409
|
-
: after
|
|
410
|
-
if (target) return flattenV(singleRect(target
|
|
408
|
+
let target = !after ? null : isText(after) ? textRange(after, 0, (supportEmptyRange ? 0 : 1))
|
|
409
|
+
: isElt(after) ? after : null
|
|
410
|
+
if (target) return flattenV(singleRect(target, -1), true)
|
|
411
411
|
}
|
|
412
412
|
// All else failed, just try to get a rectangle for the target node
|
|
413
|
-
return flattenV(singleRect(node
|
|
413
|
+
return flattenV(singleRect(isText(node) ? textRange(node) : node as HTMLElement, -side), side >= 0)
|
|
414
414
|
}
|
|
415
415
|
|
|
416
416
|
function flattenV(rect: DOMRect, left: boolean) {
|
|
@@ -453,8 +453,8 @@ function endOfTextblockVertical(view: EditorView, state: EditorState, dir: "up"
|
|
|
453
453
|
let coords = coordsAtPos(view, $pos.pos, 1)
|
|
454
454
|
for (let child = dom.firstChild; child; child = child.nextSibling) {
|
|
455
455
|
let boxes
|
|
456
|
-
if (child
|
|
457
|
-
else if (child
|
|
456
|
+
if (isElt(child)) boxes = child.getClientRects()
|
|
457
|
+
else if (isText(child)) boxes = textRange(child, 0, child.nodeValue!.length).getClientRects()
|
|
458
458
|
else continue
|
|
459
459
|
for (let i = 0; i < boxes.length; i++) {
|
|
460
460
|
let box = boxes[i]
|
|
@@ -492,8 +492,7 @@ function endOfTextblockHorizontal(view: EditorView, state: EditorState, dir: "le
|
|
|
492
492
|
;(sel as any).modify("move", dir, "character")
|
|
493
493
|
let parentDOM = $head.depth ? view.docView.domAfterPos($head.before()) : view.dom
|
|
494
494
|
let {focusNode: newNode, focusOffset: newOff} = view.domSelectionRange()
|
|
495
|
-
let result = newNode && !
|
|
496
|
-
(oldNode == newNode && oldOff == newOff)
|
|
495
|
+
let result = newNode && !contains(parentDOM, newNode) || (oldNode == newNode && oldOff == newOff)
|
|
497
496
|
// Restore the previous selection
|
|
498
497
|
try {
|
|
499
498
|
sel.collapse(anchorNode, anchorOffset)
|
package/src/domobserver.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {Selection} from "prosemirror-state"
|
|
2
2
|
import * as browser from "./browser"
|
|
3
|
-
import {domIndex, isEquivalentPosition, selectionCollapsed, parentNode, DOMSelectionRange, DOMNode, DOMSelection} from "./dom"
|
|
3
|
+
import {domIndex, isEquivalentPosition, selectionCollapsed, parentNode, DOMSelectionRange, DOMNode, DOMSelection, isText, isElt} from "./dom"
|
|
4
4
|
import {hasFocusAndSelection, selectionToDOM, selectionFromDOM} from "./selection"
|
|
5
5
|
import {EditorView} from "./index"
|
|
6
6
|
|
|
@@ -159,7 +159,7 @@ export class DOMObserver {
|
|
|
159
159
|
let desc = container && this.view.docView.nearestDesc(container)
|
|
160
160
|
if (desc && desc.ignoreMutation({
|
|
161
161
|
type: "selection",
|
|
162
|
-
target: container
|
|
162
|
+
target: isText(container!) ? container!.parentNode! : container!
|
|
163
163
|
})) {
|
|
164
164
|
this.setCurSelection()
|
|
165
165
|
return true
|
|
@@ -200,8 +200,8 @@ export class DOMObserver {
|
|
|
200
200
|
// backspace out the last bit of text before an inline-flex node (#1552)
|
|
201
201
|
for (let node of added) if (node.nodeName == "BR" && node.parentNode) {
|
|
202
202
|
let after = node.nextSibling
|
|
203
|
-
while (after && after
|
|
204
|
-
if (
|
|
203
|
+
while (after && isElt(after)) {
|
|
204
|
+
if (after.contentEditable == "false") {
|
|
205
205
|
node.parentNode.removeChild(node)
|
|
206
206
|
break
|
|
207
207
|
}
|
|
@@ -267,7 +267,7 @@ export class DOMObserver {
|
|
|
267
267
|
for (let i = 0; i < mut.addedNodes.length; i++) {
|
|
268
268
|
let node = mut.addedNodes[i]
|
|
269
269
|
added.push(node)
|
|
270
|
-
if (node
|
|
270
|
+
if (isText(node)) this.lastChangedTextNode = node
|
|
271
271
|
}
|
|
272
272
|
if (desc.contentDOM && desc.contentDOM != desc.dom && !desc.contentDOM.contains(mut.target))
|
|
273
273
|
return {from: desc.posBefore, to: desc.posAfter}
|
|
@@ -381,8 +381,7 @@ function fixUpBadSafariComposition(view: EditorView, addedNodes: readonly DOMNod
|
|
|
381
381
|
let parent = nextCell
|
|
382
382
|
for (;;) {
|
|
383
383
|
let first = parent.firstChild
|
|
384
|
-
if (!first || first
|
|
385
|
-
/^(BR|IMG)$/.test(first.nodeName)) break
|
|
384
|
+
if (!first || !isElt(first) || first.contentEditable == "false" || /^(BR|IMG)$/.test(first.nodeName)) break
|
|
386
385
|
parent = first
|
|
387
386
|
}
|
|
388
387
|
parent.insertBefore(node, parent.firstChild)
|