forked from ianstormtaylor/slate
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdom.ts
147 lines (122 loc) · 3.66 KB
/
dom.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/**
* Types.
*/
// COMPAT: This is required to prevent TypeScript aliases from doing some very
// weird things for Slate's types with the same name as globals. (2019/11/27)
// https://github.com/microsoft/TypeScript/issues/35002
import DOMNode = globalThis.Node
import DOMComment = globalThis.Comment
import DOMElement = globalThis.Element
import DOMText = globalThis.Text
import DOMRange = globalThis.Range
import DOMSelection = globalThis.Selection
import DOMStaticRange = globalThis.StaticRange
export {
DOMNode,
DOMComment,
DOMElement,
DOMText,
DOMRange,
DOMSelection,
DOMStaticRange,
}
export type DOMPoint = [Node, number]
/**
* Check if a DOM node is a comment node.
*/
export const isDOMComment = (value: any): value is DOMComment => {
return isDOMNode(value) && value.nodeType === 8
}
/**
* Check if a DOM node is an element node.
*/
export const isDOMElement = (value: any): value is DOMElement => {
return isDOMNode(value) && value.nodeType === 1
}
/**
* Check if a value is a DOM node.
*/
export const isDOMNode = (value: any): value is DOMNode => {
return value instanceof Node
}
/**
* Check if a DOM node is an element node.
*/
export const isDOMText = (value: any): value is DOMText => {
return isDOMNode(value) && value.nodeType === 3
}
/**
* Checks whether a paste event is a plaintext-only event.
*/
export const isPlainTextOnlyPaste = (event: ClipboardEvent) => {
return (
event.clipboardData &&
event.clipboardData.getData('text/plain') !== '' &&
event.clipboardData.types.length === 1
)
}
/**
* Normalize a DOM point so that it always refers to a text node.
*/
export const normalizeDOMPoint = (domPoint: DOMPoint): DOMPoint => {
let [node, offset] = domPoint
// If it's an element node, its offset refers to the index of its children
// including comment nodes, so try to find the right text child node.
if (isDOMElement(node) && node.childNodes.length) {
const isLast = offset === node.childNodes.length
const direction = isLast ? 'backward' : 'forward'
const index = isLast ? offset - 1 : offset
node = getEditableChild(node, index, direction)
// If the node has children, traverse until we have a leaf node. Leaf nodes
// can be either text nodes, or other void DOM nodes.
while (isDOMElement(node) && node.childNodes.length) {
const i = isLast ? node.childNodes.length - 1 : 0
node = getEditableChild(node, i, direction)
}
// Determine the new offset inside the text node.
offset = isLast && node.textContent != null ? node.textContent.length : 0
}
// Return the node and offset.
return [node, offset]
}
/**
* Get the nearest editable child at `index` in a `parent`, preferring
* `direction`.
*/
export const getEditableChild = (
parent: DOMElement,
index: number,
direction: 'forward' | 'backward'
): DOMNode => {
const { childNodes } = parent
let child = childNodes[index]
let i = index
let triedForward = false
let triedBackward = false
// While the child is a comment node, or an element node with no children,
// keep iterating to find a sibling non-void, non-comment node.
while (
isDOMComment(child) ||
(isDOMElement(child) && child.childNodes.length === 0) ||
(isDOMElement(child) && child.getAttribute('contenteditable') === 'false')
) {
if (triedForward && triedBackward) {
break
}
if (i >= childNodes.length) {
triedForward = true
i = index - 1
direction = 'backward'
continue
}
if (i < 0) {
triedBackward = true
i = index + 1
direction = 'forward'
continue
}
child = childNodes[i]
i += direction === 'forward' ? 1 : -1
}
return child
}