• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

worktile / slate-angular / 6659122c-e261-4b71-8a4c-31a76811ea45

pending completion
6659122c-e261-4b71-8a4c-31a76811ea45

Pull #226

circleci

pubuzhixing8
chore(core): update note
Pull Request #226: Android input handing

268 of 892 branches covered (30.04%)

Branch coverage included in aggregate %.

46 of 46 new or added lines in 2 files covered. (100.0%)

682 of 1526 relevant lines covered (44.69%)

29.63 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

21.57
/packages/src/utils/dom.ts
1
/**
2
 * Types.
3
 */
4

5
// COMPAT: This is required to prevent TypeScript aliases from doing some very
6
// weird things for Slate's types with the same name as globals. (2019/11/27)
7
// https://github.com/microsoft/TypeScript/issues/35002
8
import DOMNode = globalThis.Node;
1✔
9
import DOMComment = globalThis.Comment;
1✔
10
import DOMElement = globalThis.Element;
1✔
11
import DOMText = globalThis.Text;
1✔
12
import DOMRange = globalThis.Range;
1✔
13
import DOMSelection = globalThis.Selection;
1✔
14
import DOMStaticRange = globalThis.StaticRange;
1✔
15
export { DOMNode, DOMComment, DOMElement, DOMText, DOMRange, DOMSelection, DOMStaticRange };
16

17
declare global {
18
    interface Window {
19
        Selection: (typeof Selection)['constructor'];
20
        DataTransfer: (typeof DataTransfer)['constructor'];
21
        Node: (typeof Node)['constructor'];
22
    }
23
}
24

25
export type DOMPoint = [Node, number];
26

27
/**
28
 * Returns the host window of a DOM node
29
 */
30
export const getDefaultView = (value: any): Window | null => {
1✔
31
    return (value && value.ownerDocument && value.ownerDocument.defaultView) || null;
24!
32
};
33

34
/**
35
 * Check if a DOM node is a comment node.
36
 */
37
export const isDOMComment = (value: any): value is DOMComment => {
1✔
38
    return isDOMNode(value) && value.nodeType === 8;
×
39
};
40

41
/**
42
 * Check if a DOM node is an element node.
43
 */
44
export const isDOMElement = (value: any): value is DOMElement => {
1✔
45
    return isDOMNode(value) && value.nodeType === 1;
8✔
46
};
47

48
/**
49
 * Check if a value is a DOM node.
50
 */
51
export const isDOMNode = (value: any): value is DOMNode => {
1✔
52
    const window = getDefaultView(value);
10✔
53
    return !!window && value instanceof window.Node;
10✔
54
};
55

56
/**
57
 * Check if a value is a DOM selection.
58
 */
59
export const isDOMSelection = (value: any): value is DOMSelection => {
1✔
60
    const window = value && value.anchorNode && getDefaultView(value.anchorNode);
×
61
    return !!window && value instanceof window.Selection;
×
62
};
63

64
/**
65
 * Check if a DOM node is an element node.
66
 */
67
export const isDOMText = (value: any): value is DOMText => {
1✔
68
    return isDOMNode(value) && value.nodeType === 3;
×
69
};
70

71
/**
72
 * Checks whether a paste event is a plaintext-only event.
73
 */
74
export const isPlainTextOnlyPaste = (event: ClipboardEvent) => {
1✔
75
    return event.clipboardData && event.clipboardData.getData('text/plain') !== '' && event.clipboardData.types.length === 1;
×
76
};
77

78
/**
79
 * Normalize a DOM point so that it always refers to a text node.
80
 */
81
export const normalizeDOMPoint = (domPoint: DOMPoint): DOMPoint => {
1✔
82
    let [node, offset] = domPoint;
×
83

84
    // If it's an element node, its offset refers to the index of its children
85
    // including comment nodes, so try to find the right text child node.
86
    if (isDOMElement(node) && node.childNodes.length) {
×
87
        let isLast = offset === node.childNodes.length;
×
88
        let index = isLast ? offset - 1 : offset;
×
89
        [node, index] = getEditableChildAndIndex(node, index, isLast ? 'backward' : 'forward');
×
90

91
        // If the editable child found is in front of input offset, we instead seek to its end
92
        isLast = index < offset;
×
93

94
        // If the node has children, traverse until we have a leaf node. Leaf nodes
95
        // can be either text nodes, or other void DOM nodes.
96
        while (isDOMElement(node) && node.childNodes.length) {
×
97
            const i = isLast ? node.childNodes.length - 1 : 0;
×
98
            node = getEditableChild(node, i, isLast ? 'backward' : 'forward');
×
99
        }
100

101
        // Determine the new offset inside the text node.
102
        offset = isLast && node.textContent != null ? node.textContent.length : 0;
×
103
    }
104

105
    // Return the node and offset.
106
    return [node, offset];
×
107
};
108

109
/**
110
 * Determines wether the active element is nested within a shadowRoot
111
 */
112

113
export const hasShadowRoot = () => {
1✔
114
    return !!(window.document.activeElement && window.document.activeElement.shadowRoot);
×
115
};
116

117
/**
118
 * Get the nearest editable child and index at `index` in a `parent`, preferring
119
 * `direction`.
120
 */
121

122
export const getEditableChildAndIndex = (parent: DOMElement, index: number, direction: 'forward' | 'backward'): [DOMNode, number] => {
1✔
123
    const { childNodes } = parent;
×
124
    let child = childNodes[index];
×
125
    let i = index;
×
126
    let triedForward = false;
×
127
    let triedBackward = false;
×
128

129
    // While the child is a comment node, or an element node with no children,
130
    // keep iterating to find a sibling non-void, non-comment node.
131
    while (
×
132
        isDOMComment(child) ||
×
133
        (isDOMElement(child) && child.childNodes.length === 0) ||
134
        (isDOMElement(child) && child.getAttribute('contenteditable') === 'false')
135
    ) {
136
        if (triedForward && triedBackward) {
×
137
            break;
×
138
        }
139

140
        if (i >= childNodes.length) {
×
141
            triedForward = true;
×
142
            i = index - 1;
×
143
            direction = 'backward';
×
144
            continue;
×
145
        }
146

147
        if (i < 0) {
×
148
            triedBackward = true;
×
149
            i = index + 1;
×
150
            direction = 'forward';
×
151
            continue;
×
152
        }
153

154
        child = childNodes[i];
×
155
        index = i;
×
156
        i += direction === 'forward' ? 1 : -1;
×
157
    }
158

159
    return [child, index];
×
160
};
161

162
/**
163
 * Get the nearest editable child at `index` in a `parent`, preferring
164
 * `direction`.
165
 */
166

167
export const getEditableChild = (parent: DOMElement, index: number, direction: 'forward' | 'backward'): DOMNode => {
1✔
168
    const [child] = getEditableChildAndIndex(parent, index, direction);
×
169
    return child;
×
170
};
171

172
/**
173
 * Get a plaintext representation of the content of a node, accounting for block
174
 * elements which get a newline appended.
175
 *
176
 * The domNode must be attached to the DOM.
177
 */
178
export const getPlainText = (domNode: DOMNode) => {
1✔
179
    let text = '';
×
180

181
    if (isDOMText(domNode) && domNode.nodeValue) {
×
182
        return domNode.nodeValue;
×
183
    }
184

185
    if (isDOMElement(domNode)) {
×
186
        for (const childNode of Array.from(domNode.childNodes)) {
×
187
            text += getPlainText(childNode);
×
188
        }
189

190
        const display = getComputedStyle(domNode).getPropertyValue('display');
×
191

192
        if (display === 'block' || display === 'list' || domNode.tagName === 'BR') {
×
193
            text += '\n';
×
194
        }
195
    }
196

197
    return text;
×
198
};
199

200
/**
201
 * Get x-slate-fragment attribute from data-slate-fragment
202
 */
203
const catchSlateFragment = /data-slate-fragment="(.+?)"/m;
1✔
204
export const getSlateFragmentAttribute = (dataTransfer: DataTransfer): string | void => {
1✔
205
    const htmlData = dataTransfer.getData('text/html');
×
206
    const [, fragment] = htmlData.match(catchSlateFragment) || [];
×
207
    return fragment;
×
208
};
209

210
/**
211
 * Get the x-slate-fragment attribute that exist in text/html data
212
 * and append it to the DataTransfer object
213
 */
214
export const getClipboardData = (dataTransfer: DataTransfer, clipboardFormatKey = 'x-slate-fragment'): DataTransfer => {
1!
215
    if (!dataTransfer.getData(`application/${clipboardFormatKey}`)) {
×
216
        const fragment = getSlateFragmentAttribute(dataTransfer);
×
217
        if (fragment) {
×
218
            const clipboardData = new DataTransfer();
×
219
            dataTransfer.types.forEach(type => {
×
220
                clipboardData.setData(type, dataTransfer.getData(type));
×
221
            });
222
            clipboardData.setData(`application/${clipboardFormatKey}`, fragment);
×
223
            return clipboardData;
×
224
        }
225
    }
226
    return dataTransfer;
×
227
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc