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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prosemirror-view",
3
- "version": "1.42.4",
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.8",
31
+ "prosemirror-model": "^1.25.12",
32
32
  "prosemirror-state": "^1.0.0",
33
33
  "prosemirror-transform": "^1.1.0"
34
34
  },
@@ -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 if (browser.webkit) {
40
+ } else {
41
41
  // Chrome and Safari will introduce extra pointless cursor
42
- // positions around inline uneditable nodes, so we have to
43
- // take over and move the cursor past them (#937)
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.nodeType == 1 && (dom as HTMLElement).contentEditable == "false"
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.nodeType == 1 && offset < nodeLen(node) && isIgnorable(node.childNodes[offset], -1)) force = true
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.nodeType != 1) {
86
- break
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
- let before = node.childNodes[offset - 1]
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 = nodeLen(node)
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 = nodeLen(node)
121
+ let len = nodeSize(node)
127
122
  let moveNode, moveOffset: number | undefined
128
123
  for (;;) {
129
124
  if (offset < len) {
130
- if (node.nodeType != 1) break
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 = nodeLen(node)
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.nodeType == 3) return next as Text
173
- if (next.nodeType == 1 && (next as HTMLElement).contentEditable == "false") break
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.nodeType == 3) return next as Text
187
- if (next.nodeType == 1 && (next as HTMLElement).contentEditable == "false") break
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.nodeType != 3) {
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.nodeType == 1 && focusOffset == 0 &&
299
- focusNode.firstChild && (focusNode.firstChild as HTMLElement).contentEditable == "false") {
300
- let child = focusNode.firstChild as HTMLElement
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.nodeType == 1 && (needsWrap = wrapMap[firstChild.nodeName.toLowerCase()])) {
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.nodeType == 1)
33
- (firstChild as HTMLElement).setAttribute(
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.nodeType != 1) child = child.nextSibling
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 { type.checkAttrs(array[i + 1]) }
274
- catch(e) { break }
275
- content = Fragment.from(type.create(array[i + 1], content))
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.nodeType != 1 || hasBlockDesc(node) || atomElements.test(node.nodeName) ||
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.nodeType == 1) {
56
+ } else if (isElt(node)) {
57
57
  let child = node.childNodes[off + (dir < 0 ? -1 : 0)]
58
- if (child.nodeType == 1 && (child as HTMLElement).contentEditable == "false") {
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.nodeType == 3 && offset) return node as Text
78
- if (node.nodeType == 1 && offset > 0) {
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.nodeType == 3 && offset < node.nodeValue!.length) return node as Text
94
- if (node.nodeType == 1 && offset < node.childNodes.length) {
95
- if ((node as HTMLElement).contentEditable == "false") return null
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.contains(anchor.nodeType == 1 ? anchor : anchor.parentNode)) {
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.nodeType == 1 && !isInline.test(n.nodeName)) &&
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 < parse.doc.content.size &&
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.nodeType != 1) { parent = parentNode(parent); continue }
41
- let elt = parent as HTMLElement
42
- let atTop = elt == doc.body
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 = elt.scrollLeft, startY = elt.scrollTop
60
- if (moveY) elt.scrollTop += moveY
61
- if (moveX) elt.scrollLeft += moveX
62
- let dX = elt.scrollLeft - startX, dY = elt.scrollTop - startY
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 as HTMLElement).position
65
+ let pos: string = atTop ? "fixed" : getComputedStyle(parent).position
67
66
  if (/^(fixed|sticky)$/.test(pos)) break
68
- parent = pos == "absolute" ? (parent as HTMLElement).offsetParent : parentNode(parent)
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 = (dom as HTMLElement).getBoundingClientRect()
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, dxClosest = 2e8, coordsClosest: {left: number, top: number} | undefined, offset = 0
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: Node | undefined, coordsBelow: {left: number, top: number} | undefined
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.nodeType == 1) rects = (child as HTMLElement).getClientRects()
152
- else if (child.nodeType == 3) rects = textRange(child as Text).getClientRects()
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.nodeType == 3 ? {
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.nodeType == 1 && dx)
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.nodeType == 3) return findOffsetInText(closest as Text, coordsClosest!)
184
- if (!closest || (dxClosest && closest.nodeType == 1)) return {node, offset}
185
- return findOffsetInNode(closest as HTMLElement, coordsClosest!)
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.nodeType == 1 && !node.firstChild) {
220
- let rect = (node as HTMLElement).getBoundingClientRect()
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.nodeType == 1 && (desc.node.isBlock && desc.parent || !desc.contentDOM) &&
238
+ if (isElt(desc.dom) && (desc.node.isBlock && desc.parent || !desc.contentDOM) &&
239
239
  // Ignore elements with zero-size bounding rectangles
240
- ((rect = (desc.dom as HTMLElement).getBoundingClientRect()).width || rect.height)) {
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.nodeType == 1) {
265
- let rects = (child as HTMLElement).getClientRects()
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 as HTMLElement, coords, rect)
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.nodeType != 1 ? elt.parentNode : 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.nodeType == 1) {
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.nodeType == 1 && (prev = node.childNodes[offset - 1]).nodeType == 1 &&
315
- (prev as HTMLElement).contentEditable == "false" && (prev as HTMLElement).getBoundingClientRect().top >= coords.top)
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!.nodeType == 1 &&
320
- coords.top > (node.lastChild as HTMLElement).getBoundingClientRect().bottom)
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.nodeType != 1 || node.childNodes[offset - 1].nodeName != "BR")
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.nodeType == 3) {
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 as Text, offset, offset), side)
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 as Text, offset - 1, offset - 1), -1)
364
+ let rectBefore = singleRect(textRange(node, offset - 1, offset - 1), -1)
365
365
  if (rectBefore.top == rect.top) {
366
- let rectAfter = singleRect(textRange(node as Text, offset, offset + 1), -1)
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 as Text, from, to), takeSide), takeSide < 0)
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.nodeType == 1) return flattenH((before as HTMLElement).getBoundingClientRect(), false)
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.nodeType == 1) return flattenH((after as HTMLElement).getBoundingClientRect(), true)
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.nodeType == 3 ? textRange(before as Text, nodeSize(before) - (supportEmptyRange ? 0 : 1))
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.nodeType == 1 && (before.nodeName != "BR" || !before.nextSibling) ? before : null
403
- if (target) return flattenV(singleRect(target as Range | HTMLElement, 1), false)
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.nodeType == 3 ? textRange(after as Text, 0, (supportEmptyRange ? 0 : 1))
409
- : after.nodeType == 1 ? after : null
410
- if (target) return flattenV(singleRect(target as Range | HTMLElement, -1), true)
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.nodeType == 3 ? textRange(node as Text) : node as HTMLElement, -side), side >= 0)
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.nodeType == 1) boxes = (child as HTMLElement).getClientRects()
457
- else if (child.nodeType == 3) boxes = textRange(child as Text, 0, child.nodeValue!.length).getClientRects()
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 && !parentDOM.contains(newNode.nodeType == 1 ? newNode : newNode.parentNode) ||
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)
@@ -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!.nodeType == 3 ? container!.parentNode! : 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.nodeType == 1) {
204
- if ((after as HTMLElement).contentEditable == "false") {
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.nodeType == 3) this.lastChangedTextNode = node as Text
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.nodeType != 1 || (first as HTMLElement).contentEditable == "false" ||
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)