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

worktile / slate-angular / 4cb1f589-6adf-471f-b778-518472090912

15 Jan 2026 07:27AM UTC coverage: 36.392% (-0.04%) from 36.429%
4cb1f589-6adf-471f-b778-518472090912

push

circleci

pubuzhixing8
fix(virtual-scroll): support get all visible states to performance improvement #WIK-19805

402 of 1307 branches covered (30.76%)

Branch coverage included in aggregate %.

3 of 26 new or added lines in 3 files covered. (11.54%)

3 existing lines in 2 files now uncovered.

1109 of 2845 relevant lines covered (38.98%)

23.54 hits per line

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

32.56
/packages/src/plugins/with-angular.ts
1
import { Editor, Element, Node, Operation, Path, PathRef, Range, Transforms } from 'slate';
2
import { ClipboardData, OriginEvent } from '../types/clipboard';
3
import { SlateError } from '../types/error';
4
import { completeTable, EDITOR_TO_VIRTUAL_SCROLL_SELECTION, isInvalidTable, VIRTUAL_SCROLL_DEFAULT_BLOCK_HEIGHT } from '../utils';
5
import { getClipboardData, setClipboardData } from '../utils/clipboard/clipboard';
6
import { AngularEditor } from './angular-editor';
7
import { Key, NODE_TO_KEY, withDOM } from 'slate-dom';
8
import { getPlainText, isDOMText } from '../utils/dom';
9

10
export const withAngular = <T extends Editor>(editor: T, clipboardFormatKey = 'x-slate-fragment') => {
1✔
11
    let e = editor as T & AngularEditor;
28✔
12
    let { apply } = e;
28✔
13

14
    e = withDOM(e, clipboardFormatKey);
28✔
15

16
    e.setFragmentData = (dataTransfer?: Pick<DataTransfer, 'getData' | 'setData'>, originEvent?: OriginEvent) => {
28✔
17
        const { selection } = e;
×
18

19
        if (!selection) {
×
20
            return;
×
21
        }
22

23
        const [start, end] = Range.edges(selection);
×
24
        const startVoid = Editor.void(e, { at: start.path });
×
25
        const endVoid = Editor.void(e, { at: end.path });
×
26

27
        if (Range.isCollapsed(selection) && !startVoid) {
×
28
            return;
×
29
        }
30

31
        // Create a fake selection so that we can add a Base64-encoded copy of the
32
        // fragment to the HTML, to decode on future pastes.
33
        let domRange: globalThis.Range;
34
        if (AngularEditor.isEnabledVirtualScroll(e)) {
×
35
            const virtualScrollSelection = EDITOR_TO_VIRTUAL_SCROLL_SELECTION.get(e);
×
36
            if (virtualScrollSelection) {
×
37
                domRange = AngularEditor.toDOMRange(e, virtualScrollSelection);
×
38
            }
39
        }
40
        domRange = domRange ?? AngularEditor.toDOMRange(e, selection);
×
41
        let contents = domRange.cloneContents();
×
42
        let attach = contents.childNodes[0] as HTMLElement;
×
43

44
        // Make sure attach is non-empty, since empty nodes will not get copied.
45
        const contentsArray = Array.from(contents.children);
×
46
        contentsArray.forEach(node => {
×
47
            if (node.textContent && node.textContent.trim() !== '') {
×
48
                attach = node as HTMLElement;
×
49
            }
50
        });
51

52
        // COMPAT: If the end node is a void node, we need to move the end of the
53
        // range from the void node's spacer span, to the end of the void node's
54
        // content, since the spacer is before void's content in the DOM.
55
        if (endVoid) {
×
56
            const [voidNode] = endVoid;
×
57
            const r = domRange.cloneRange();
×
58
            const domNode = AngularEditor.toDOMNode(e, voidNode);
×
59
            r.setEndAfter(domNode);
×
60
            contents = r.cloneContents();
×
61
        }
62

63
        // COMPAT: If the start node is a void node, we need to attach the encoded
64
        // fragment to the void node's content node instead of the spacer, because
65
        // attaching it to empty `<div>/<span>` nodes will end up having it erased by
66
        // most browsers. (2018/04/27)
67
        if (startVoid) {
×
68
            attach = contents.querySelector('[data-slate-spacer]')! as HTMLElement;
×
69
        }
70

71
        // Remove any zero-width space spans from the cloned DOM so that they don't
72
        // show up elsewhere when pasted.
73
        Array.from(contents.querySelectorAll('[data-slate-zero-width]')).forEach(zw => {
×
74
            const isNewline = zw.getAttribute('data-slate-zero-width') === 'n';
×
75
            zw.textContent = isNewline ? '\n' : '';
×
76
        });
77

78
        // Set a `data-slate-fragment` attribute on a non-empty node, so it shows up
79
        // in the HTML, and can be used for intra-Slate pasting. If it's a text
80
        // node, wrap it in a `<span>` so we have something to set an attribute on.
81
        if (isDOMText(attach)) {
×
82
            const span = attach.ownerDocument.createElement('span');
×
83
            // COMPAT: In Chrome and Safari, if we don't add the `white-space` style
84
            // then leading and trailing spaces will be ignored. (2017/09/21)
85
            span.style.whiteSpace = 'pre';
×
86
            span.appendChild(attach);
×
87
            contents.appendChild(span);
×
88
            attach = span;
×
89
        }
90

91
        const fragment = e.getFragment();
×
92

93
        // Add the content to a <div> so that we can get its inner HTML.
94
        const div = contents.ownerDocument.createElement('div');
×
95
        const attachWrapper = document.createElement('div');
×
96
        const elements = Array.from(contents.children);
×
97
        if (isInvalidTable(elements)) {
×
98
            contents = completeTable(contents.cloneNode(true) as DocumentFragment);
×
99
        }
100

101
        attachWrapper.appendChild(contents);
×
102
        div.appendChild(attachWrapper);
×
103
        div.setAttribute('hidden', 'true');
×
104
        contents.ownerDocument.body.appendChild(div);
×
105
        setClipboardData({ text: getPlainText(div), elements: fragment as Element[] }, div, attachWrapper, dataTransfer);
×
106
        contents.ownerDocument.body.removeChild(div);
×
107
    };
108

109
    e.deleteCutData = () => {
28✔
110
        const { selection } = editor;
×
111
        if (selection) {
×
112
            if (Range.isExpanded(selection)) {
×
113
                Editor.deleteFragment(editor);
×
114
            } else {
115
                const node = Node.parent(editor, selection.anchor.path);
×
116
                if (Element.isElement(node) && Editor.isVoid(editor, node)) {
×
117
                    Transforms.delete(editor);
×
118
                }
119
            }
120
        }
121
    };
122

123
    e.insertData = async (data: DataTransfer) => {
28✔
124
        if (!(await e.customInsertFragmentData(data, null))) {
×
125
            e.insertTextData(data);
×
126
        }
127
    };
128

129
    e.customInsertFragmentData = async (data: DataTransfer, contextClipboardData: ClipboardData): Promise<boolean> => {
28✔
130
        /**
131
         * Checking copied fragment from application/x-slate-fragment or data-slate-fragment
132
         */
133
        const clipboardData = contextClipboardData || (await getClipboardData(data));
×
134
        if (clipboardData && clipboardData.elements) {
×
135
            e.insertFragment(clipboardData.elements);
×
136
            return true;
×
137
        }
138
        return false;
×
139
    };
140

141
    e.customInsertTextData = async (data: DataTransfer): Promise<boolean> => {
28✔
142
        const clipboardData = await getClipboardData(data);
×
143

144
        if (clipboardData && clipboardData.text) {
×
145
            const lines = clipboardData.text.split(/\r\n|\r|\n/);
×
146
            let split = false;
×
147

148
            for (const line of lines) {
×
149
                if (split) {
×
150
                    Transforms.splitNodes(e, { always: true });
×
151
                }
152

153
                e.insertText(line);
×
154
                split = true;
×
155
            }
156
            return true;
×
157
        }
158
        return false;
×
159
    };
160

161
    e.onKeydown = () => {};
28✔
162

163
    e.onClick = () => {};
28✔
164

165
    e.isBlockCard = element => false;
364✔
166

167
    e.isExpanded = element => true;
215✔
168

169
    e.onError = (errorData: SlateError) => {
28✔
170
        if (errorData.nativeError) {
×
171
            console.error(errorData.nativeError);
×
172
        } else {
173
            console.error(errorData);
×
174
        }
175
    };
176

177
    // exist issue for move operation in withDOM
178
    e.apply = (op: Operation) => {
28✔
179
        const matches: [Path | PathRef, Key][] = [];
24✔
180

181
        switch (op.type) {
24✔
182
            case 'insert_text':
183
            case 'remove_text':
184
            case 'set_node': {
185
                for (const [node, path] of Editor.levels(e, { at: op.path })) {
3✔
186
                    const key = AngularEditor.findKey(e, node);
9✔
187
                    matches.push([path, key]);
9✔
188
                }
189

190
                break;
3✔
191
            }
192

193
            case 'insert_node':
194
            case 'remove_node':
195
            case 'merge_node':
196
            case 'split_node': {
197
                for (const [node, path] of Editor.levels(e, {
5✔
198
                    at: Path.parent(op.path)
199
                })) {
200
                    const key = AngularEditor.findKey(e, node);
7✔
201
                    matches.push([path, key]);
7✔
202
                }
203

204
                break;
5✔
205
            }
206

207
            case 'move_node': {
208
                const commonPath = Path.common(Path.parent(op.path), Path.parent(op.newPath));
8✔
209
                for (const [node, path] of Editor.levels(e, {
8✔
210
                    at: Path.parent(op.path)
211
                })) {
212
                    const key = AngularEditor.findKey(e, node);
14✔
213
                    matches.push([Editor.pathRef(editor, path), key]);
14✔
214
                }
215
                for (const [node, path] of Editor.levels(e, {
8✔
216
                    at: Path.parent(op.newPath)
217
                })) {
218
                    if (path.length > commonPath.length) {
16✔
219
                        const key = AngularEditor.findKey(e, node);
5✔
220
                        matches.push([Editor.pathRef(editor, path), key]);
5✔
221
                    }
222
                }
223
                break;
8✔
224
            }
225
        }
226

227
        apply(op);
24✔
228

229
        for (const [source, key] of matches) {
24✔
230
            const [node] = Editor.node(e, Path.isPath(source) ? source : source.current);
35✔
231
            NODE_TO_KEY.set(node, key);
35✔
232
        }
233
    };
234

235
    e.selectAll = () => {
28✔
236
        Transforms.select(e, []);
×
237
    };
238

239
    e.isVisible = element => {
28✔
240
        return true;
12✔
241
    };
242

243
    e.getAllVisibleStates = () => {
28✔
NEW
244
        return new Array(e.children.length).fill(true);
×
245
    };
246

247
    e.getRoughHeight = (element: Element, defaultHeight?: number) => {
28✔
248
        return defaultHeight === undefined ? VIRTUAL_SCROLL_DEFAULT_BLOCK_HEIGHT : defaultHeight;
×
249
    };
250

251
    return e;
28✔
252
};
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

© 2026 Coveralls, Inc