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

worktile / slate-angular / 6ec4b98c-bf1a-4dfd-a08c-806b48306d81

pending completion
6ec4b98c-bf1a-4dfd-a08c-806b48306d81

push

circleci

pubuzhixing8
feat(core): android input handing #216

268 of 892 branches covered (30.04%)

Branch coverage included in aggregate %.

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

27.93
/packages/src/plugins/angular-editor.ts
1
import { Editor, Node, Path, Point, Range, Transforms, Element, BaseEditor } from 'slate';
2
import {
3
    EDITOR_TO_ELEMENT,
4
    ELEMENT_TO_NODE,
5
    IS_FOCUSED,
6
    IS_READONLY,
7
    NODE_TO_INDEX,
8
    NODE_TO_PARENT,
9
    NODE_TO_ELEMENT,
10
    NODE_TO_KEY,
11
    EDITOR_TO_WINDOW
12
} from '../utils/weak-maps';
13
import {
14
    DOMElement,
15
    DOMNode,
16
    DOMPoint,
17
    DOMRange,
18
    DOMSelection,
19
    DOMStaticRange,
20
    hasShadowRoot,
21
    isDOMElement,
22
    isDOMSelection,
23
    normalizeDOMPoint
24
} from '../utils/dom';
25
import { Injector } from '@angular/core';
26
import { NodeEntry } from 'slate';
27
import { SlateError } from '../types/error';
28
import { Key } from '../utils/key';
29
import { IS_CHROME, IS_FIREFOX } from '../utils/environment';
30
import {
31
    FAKE_LEFT_BLOCK_CARD_OFFSET,
32
    FAKE_RIGHT_BLOCK_CARD_OFFSET,
33
    getCardTargetAttribute,
34
    isCardCenterByTargetAttr,
35
    isCardLeftByTargetAttr,
36
    isCardRightByTargetAttr
37
} from '../utils/block-card';
38
import { SafeAny } from '../types';
39

40
/**
41
 * An Angular and DOM-specific version of the `Editor` interface.
42
 */
43

44
export interface AngularEditor extends BaseEditor {
45
    insertData: (data: DataTransfer) => void;
46
    insertFragmentData: (data: DataTransfer) => boolean;
47
    insertTextData: (data: DataTransfer) => boolean;
48
    setFragmentData: (data: DataTransfer, originEvent?: 'drag' | 'copy' | 'cut') => void;
49
    deleteCutData: () => void;
50
    onKeydown: (event: KeyboardEvent) => void;
51
    onClick: (event: MouseEvent) => void;
52
    injector: Injector;
53
    isBlockCard: (node: Node) => boolean;
54
    onError: (errorData: SlateError) => void;
55
    hasRange: (editor: AngularEditor, range: Range) => boolean;
56
}
57

58
export const AngularEditor = {
1✔
59
    /**
60
     * Return the host window of the current editor.
61
     */
62

63
    getWindow(editor: AngularEditor): Window {
64
        const window = EDITOR_TO_WINDOW.get(editor);
17✔
65
        if (!window) {
17!
66
            throw new Error('Unable to find a host window element for this editor');
×
67
        }
68
        return window;
17✔
69
    },
70
    /**
71
     * Find a key for a Slate node.
72
     */
73

74
    findKey(editor: AngularEditor, node: Node): Key {
75
        let key = NODE_TO_KEY.get(node);
467✔
76

77
        if (!key) {
467✔
78
            key = new Key();
220✔
79
            NODE_TO_KEY.set(node, key);
220✔
80
        }
81

82
        return key;
467✔
83
    },
84

85
    /**
86
     * handle editor error.
87
     */
88

89
    onError(errorData: SlateError) {
90
        if (errorData.nativeError) {
×
91
            throw errorData.nativeError;
×
92
        }
93
    },
94

95
    /**
96
     * Find the path of Slate node.
97
     */
98

99
    findPath(editor: AngularEditor, node: Node): Path {
100
        const path: Path = [];
353✔
101
        let child = node;
353✔
102

103
        while (true) {
353✔
104
            const parent = NODE_TO_PARENT.get(child);
1,170✔
105

106
            if (parent == null) {
1,170✔
107
                if (Editor.isEditor(child)) {
353!
108
                    return path;
353✔
109
                } else {
110
                    break;
×
111
                }
112
            }
113

114
            const i = NODE_TO_INDEX.get(child);
817✔
115

116
            if (i == null) {
817!
117
                break;
×
118
            }
119

120
            path.unshift(i);
817✔
121
            child = parent;
817✔
122
        }
123
        throw new Error(`Unable to find the path for Slate node: ${JSON.stringify(node)}`);
×
124
    },
125

126
    /**
127
     * Find the DOM node that implements DocumentOrShadowRoot for the editor.
128
     */
129

130
    findDocumentOrShadowRoot(editor: AngularEditor): Document | ShadowRoot {
131
        const el = AngularEditor.toDOMNode(editor, editor);
11✔
132
        const root = el.getRootNode();
11✔
133
        if ((root instanceof Document || root instanceof ShadowRoot) && (root as Document).getSelection != null) {
11!
134
            return root;
11✔
135
        }
136

137
        return el.ownerDocument;
×
138
    },
139

140
    /**
141
     * Check if the editor is focused.
142
     */
143

144
    isFocused(editor: AngularEditor): boolean {
145
        return !!IS_FOCUSED.get(editor);
10✔
146
    },
147

148
    /**
149
     * Check if the editor is in read-only mode.
150
     */
151

152
    isReadonly(editor: AngularEditor): boolean {
153
        return !!IS_READONLY.get(editor);
×
154
    },
155

156
    /**
157
     * Check if the editor is hanging right.
158
     */
159
    isBlockHangingRight(editor: AngularEditor): boolean {
160
        const { selection } = editor;
×
161
        if (!selection) {
×
162
            return false;
×
163
        }
164
        if (Range.isCollapsed(selection)) {
×
165
            return false;
×
166
        }
167
        const [start, end] = Range.edges(selection);
×
168
        const endBlock = Editor.above(editor, {
×
169
            at: end,
170
            match: node => Editor.isBlock(editor, node)
×
171
        });
172
        return Editor.isStart(editor, end, endBlock[1]);
×
173
    },
174

175
    /**
176
     * Blur the editor.
177
     */
178

179
    blur(editor: AngularEditor): void {
180
        const el = AngularEditor.toDOMNode(editor, editor);
×
181
        const root = AngularEditor.findDocumentOrShadowRoot(editor);
×
182
        IS_FOCUSED.set(editor, false);
×
183

184
        if (root.activeElement === el) {
×
185
            el.blur();
×
186
        }
187
    },
188

189
    /**
190
     * Focus the editor.
191
     */
192

193
    focus(editor: AngularEditor): void {
194
        const el = AngularEditor.toDOMNode(editor, editor);
1✔
195
        IS_FOCUSED.set(editor, true);
1✔
196

197
        const window = AngularEditor.getWindow(editor);
1✔
198
        if (window.document.activeElement !== el) {
1✔
199
            el.focus({ preventScroll: true });
1✔
200
        }
201
    },
202

203
    /**
204
     * Deselect the editor.
205
     */
206

207
    deselect(editor: AngularEditor): void {
208
        const { selection } = editor;
×
209
        const root = AngularEditor.findDocumentOrShadowRoot(editor);
×
210
        const domSelection = (root as Document).getSelection();
×
211

212
        if (domSelection && domSelection.rangeCount > 0) {
×
213
            domSelection.removeAllRanges();
×
214
        }
215

216
        if (selection) {
×
217
            Transforms.deselect(editor);
×
218
        }
219
    },
220

221
    /**
222
     * Check if a DOM node is within the editor.
223
     */
224

225
    hasDOMNode(editor: AngularEditor, target: DOMNode, options: { editable?: boolean } = {}): boolean {
×
226
        const { editable = false } = options;
2!
227
        const editorEl = AngularEditor.toDOMNode(editor, editor);
2✔
228
        let targetEl;
229

230
        // COMPAT: In Firefox, reading `target.nodeType` will throw an error if
231
        // target is originating from an internal "restricted" element (e.g. a
232
        // stepper arrow on a number input). (2018/05/04)
233
        // https://github.com/ianstormtaylor/slate/issues/1819
234
        try {
2✔
235
            targetEl = (isDOMElement(target) ? target : target.parentElement) as HTMLElement;
2!
236
        } catch (err) {
237
            if (!err.message.includes('Permission denied to access property "nodeType"')) {
×
238
                throw err;
×
239
            }
240
        }
241

242
        if (!targetEl) {
2!
243
            return false;
×
244
        }
245

246
        return (
2✔
247
            targetEl.closest(`[data-slate-editor]`) === editorEl &&
6!
248
            (!editable || targetEl.isContentEditable || !!targetEl.getAttribute('data-slate-zero-width'))
249
        );
250
    },
251

252
    /**
253
     * Insert data from a `DataTransfer` into the editor.
254
     */
255

256
    insertData(editor: AngularEditor, data: DataTransfer): void {
257
        editor.insertData(data);
×
258
    },
259

260
    /**
261
     * Insert fragment data from a `DataTransfer` into the editor.
262
     */
263

264
    insertFragmentData(editor: AngularEditor, data: DataTransfer): boolean {
265
        return editor.insertFragmentData(data);
×
266
    },
267

268
    /**
269
     * Insert text data from a `DataTransfer` into the editor.
270
     */
271

272
    insertTextData(editor: AngularEditor, data: DataTransfer): boolean {
273
        return editor.insertTextData(data);
×
274
    },
275

276
    /**
277
     * onKeydown hook.
278
     */
279
    onKeydown(editor: AngularEditor, data: KeyboardEvent): void {
280
        editor.onKeydown(data);
×
281
    },
282

283
    /**
284
     * onClick hook.
285
     */
286
    onClick(editor: AngularEditor, data: MouseEvent): void {
287
        editor.onClick(data);
×
288
    },
289

290
    /**
291
     * Sets data from the currently selected fragment on a `DataTransfer`.
292
     */
293

294
    setFragmentData(editor: AngularEditor, data: DataTransfer, originEvent?: 'drag' | 'copy' | 'cut'): void {
295
        editor.setFragmentData(data, originEvent);
×
296
    },
297

298
    deleteCutData(editor: AngularEditor): void {
299
        editor.deleteCutData();
×
300
    },
301

302
    /**
303
     * Find the native DOM element from a Slate node.
304
     */
305

306
    toDOMNode(editor: AngularEditor, node: Node): HTMLElement {
307
        const domNode = Editor.isEditor(node) ? EDITOR_TO_ELEMENT.get(editor) : NODE_TO_ELEMENT.get(node);
20✔
308

309
        if (!domNode) {
20!
310
            throw new Error(`Cannot resolve a DOM node from Slate node: ${JSON.stringify(node)}`);
×
311
        }
312

313
        return domNode;
20✔
314
    },
315

316
    /**
317
     * Find a native DOM selection point from a Slate point.
318
     */
319

320
    toDOMPoint(editor: AngularEditor, point: Point): DOMPoint {
321
        const [node] = Editor.node(editor, point.path);
2✔
322
        const el = AngularEditor.toDOMNode(editor, node);
2✔
323
        let domPoint: DOMPoint | undefined;
324

325
        // block card
326
        const cardTargetAttr = getCardTargetAttribute(el);
2✔
327
        if (cardTargetAttr) {
2!
328
            if (point.offset === FAKE_LEFT_BLOCK_CARD_OFFSET) {
×
329
                const cursorNode = AngularEditor.getCardCursorNode(editor, node, { direction: 'left' });
×
330
                return [cursorNode, 1];
×
331
            } else {
332
                const cursorNode = AngularEditor.getCardCursorNode(editor, node, { direction: 'right' });
×
333
                return [cursorNode, 1];
×
334
            }
335
        }
336

337
        // If we're inside a void node, force the offset to 0, otherwise the zero
338
        // width spacing character will result in an incorrect offset of 1
339
        if (Editor.void(editor, { at: point })) {
2!
340
            point = { path: point.path, offset: 0 };
×
341
        }
342

343
        // For each leaf, we need to isolate its content, which means filtering
344
        // to its direct text and zero-width spans. (We have to filter out any
345
        // other siblings that may have been rendered alongside them.)
346
        const selector = `[data-slate-string], [data-slate-zero-width]`;
2✔
347
        const texts = Array.from(el.querySelectorAll(selector));
2✔
348
        let start = 0;
2✔
349

350
        for (const text of texts) {
2✔
351
            const domNode = text.childNodes[0] as HTMLElement;
2✔
352

353
            if (domNode == null || domNode.textContent == null) {
2!
354
                continue;
×
355
            }
356

357
            const { length } = domNode.textContent;
2✔
358
            const attr = text.getAttribute('data-slate-length');
2✔
359
            const trueLength = attr == null ? length : parseInt(attr, 10);
2✔
360
            const end = start + trueLength;
2✔
361

362
            if (point.offset <= end) {
2✔
363
                const offset = Math.min(length, Math.max(0, point.offset - start));
2✔
364
                domPoint = [domNode, offset];
2✔
365
                // fixed cursor position after zero width char
366
                if (offset === 0 && length === 1 && domNode.textContent === '\uFEFF') {
2✔
367
                    domPoint = [domNode, offset + 1];
1✔
368
                }
369
                break;
2✔
370
            }
371

372
            start = end;
×
373
        }
374

375
        if (!domPoint) {
2!
376
            throw new Error(`Cannot resolve a DOM point from Slate point: ${JSON.stringify(point)}`);
×
377
        }
378

379
        return domPoint;
2✔
380
    },
381

382
    /**
383
     * Find a native DOM range from a Slate `range`.
384
     */
385

386
    toDOMRange(editor: AngularEditor, range: Range): DOMRange {
387
        const { anchor, focus } = range;
2✔
388
        const isBackward = Range.isBackward(range);
2✔
389
        const domAnchor = AngularEditor.toDOMPoint(editor, anchor);
2✔
390
        const domFocus = Range.isCollapsed(range) ? domAnchor : AngularEditor.toDOMPoint(editor, focus);
2!
391

392
        const window = AngularEditor.getWindow(editor);
2✔
393
        const domRange = window.document.createRange();
2✔
394
        const [startNode, startOffset] = isBackward ? domFocus : domAnchor;
2!
395
        const [endNode, endOffset] = isBackward ? domAnchor : domFocus;
2!
396

397
        // A slate Point at zero-width Leaf always has an offset of 0 but a native DOM selection at
398
        // zero-width node has an offset of 1 so we have to check if we are in a zero-width node and
399
        // adjust the offset accordingly.
400
        const startEl = (isDOMElement(startNode) ? startNode : startNode.parentElement) as HTMLElement;
2!
401
        const isStartAtZeroWidth = !!startEl.getAttribute('data-slate-zero-width');
2✔
402
        const endEl = (isDOMElement(endNode) ? endNode : endNode.parentElement) as HTMLElement;
2!
403
        const isEndAtZeroWidth = !!endEl.getAttribute('data-slate-zero-width');
2✔
404

405
        domRange.setStart(startNode, isStartAtZeroWidth ? 1 : startOffset);
2✔
406
        domRange.setEnd(endNode, isEndAtZeroWidth ? 1 : endOffset);
2✔
407
        return domRange;
2✔
408
    },
409

410
    /**
411
     * Find a Slate node from a native DOM `element`.
412
     */
413

414
    toSlateNode(editor: AngularEditor, domNode: DOMNode): Node {
415
        let domEl = isDOMElement(domNode) ? domNode : domNode.parentElement;
×
416

417
        if (domEl && !domEl.hasAttribute('data-slate-node')) {
×
418
            domEl = domEl.closest(`[data-slate-node]`);
×
419
        }
420

421
        const node = domEl ? ELEMENT_TO_NODE.get(domEl as HTMLElement) : null;
×
422

423
        if (!node) {
×
424
            throw new Error(`Cannot resolve a Slate node from DOM node: ${domEl}`);
×
425
        }
426

427
        return node;
×
428
    },
429

430
    /**
431
     * Get the target range from a DOM `event`.
432
     */
433

434
    findEventRange(editor: AngularEditor, event: any): Range {
435
        if ('nativeEvent' in event) {
×
436
            event = event.nativeEvent;
×
437
        }
438

439
        const { clientX: x, clientY: y, target } = event;
×
440

441
        if (x == null || y == null) {
×
442
            throw new Error(`Cannot resolve a Slate range from a DOM event: ${event}`);
×
443
        }
444

445
        const node = AngularEditor.toSlateNode(editor, event.target);
×
446
        const path = AngularEditor.findPath(editor, node);
×
447

448
        // If the drop target is inside a void node, move it into either the
449
        // next or previous node, depending on which side the `x` and `y`
450
        // coordinates are closest to.
451
        if (Editor.isVoid(editor, node)) {
×
452
            const rect = target.getBoundingClientRect();
×
453
            const isPrev = editor.isInline(node) ? x - rect.left < rect.left + rect.width - x : y - rect.top < rect.top + rect.height - y;
×
454

455
            const edge = Editor.point(editor, path, {
×
456
                edge: isPrev ? 'start' : 'end'
×
457
            });
458
            const point = isPrev ? Editor.before(editor, edge) : Editor.after(editor, edge);
×
459

460
            if (point) {
×
461
                return Editor.range(editor, point);
×
462
            }
463
        }
464

465
        // Else resolve a range from the caret position where the drop occured.
466
        let domRange: DOMRange;
467
        const window = AngularEditor.getWindow(editor);
×
468
        const { document } = window;
×
469

470
        // COMPAT: In Firefox, `caretRangeFromPoint` doesn't exist. (2016/07/25)
471
        if (document.caretRangeFromPoint) {
×
472
            domRange = document.caretRangeFromPoint(x, y);
×
473
        } else {
474
            const position = (document as SafeAny).caretPositionFromPoint(x, y);
×
475

476
            if (position) {
×
477
                domRange = document.createRange();
×
478
                domRange.setStart(position.offsetNode, position.offset);
×
479
                domRange.setEnd(position.offsetNode, position.offset);
×
480
            }
481
        }
482

483
        if (!domRange) {
×
484
            throw new Error(`Cannot resolve a Slate range from a DOM event: ${event}`);
×
485
        }
486

487
        // Resolve a Slate range from the DOM range.
488
        const range = AngularEditor.toSlateRange(editor, domRange);
×
489
        return range;
×
490
    },
491

492
    /**
493
     * Find a Slate point from a DOM selection's `domNode` and `domOffset`.
494
     */
495

496
    toSlatePoint(editor: AngularEditor, domPoint: DOMPoint): Point {
497
        const [domNode] = domPoint;
×
498
        const [nearestNode, nearestOffset] = normalizeDOMPoint(domPoint);
×
499
        let parentNode = nearestNode.parentNode as DOMElement;
×
500
        let textNode: DOMElement | null = null;
×
501
        let offset = 0;
×
502

503
        // block card
504
        const cardTargetAttr = getCardTargetAttribute(domNode);
×
505
        if (cardTargetAttr) {
×
506
            const domSelection = window.getSelection();
×
507
            const isBackward = editor.selection && Range.isBackward(editor.selection);
×
508
            const blockCardEntry = AngularEditor.toSlateCardEntry(editor, domNode) || AngularEditor.toSlateCardEntry(editor, nearestNode);
×
509
            const [, blockPath] = blockCardEntry;
×
510
            if (domSelection.isCollapsed) {
×
511
                if (isCardLeftByTargetAttr(cardTargetAttr)) {
×
512
                    return { path: blockPath, offset: -1 };
×
513
                } else {
514
                    return { path: blockPath, offset: -2 };
×
515
                }
516
            }
517
            // forward
518
            // and to the end of previous node
519
            if (isCardLeftByTargetAttr(cardTargetAttr) && !isBackward) {
×
520
                const endPath = blockPath[blockPath.length - 1] <= 0 ? blockPath : Path.previous(blockPath);
×
521
                return Editor.end(editor, endPath);
×
522
            }
523
            // to the of current node
524
            if ((isCardCenterByTargetAttr(cardTargetAttr) || isCardRightByTargetAttr(cardTargetAttr)) && !isBackward) {
×
525
                return Editor.end(editor, blockPath);
×
526
            }
527
            // backward
528
            // and to the start of next node
529
            if (isCardRightByTargetAttr(cardTargetAttr) && isBackward) {
×
530
                return Editor.start(editor, Path.next(blockPath));
×
531
            }
532
            // and to the start of current node
533
            if ((isCardCenterByTargetAttr(cardTargetAttr) || isCardLeftByTargetAttr(cardTargetAttr)) && isBackward) {
×
534
                return Editor.start(editor, blockPath);
×
535
            }
536
        }
537

538
        if (parentNode) {
×
539
            const voidNode = parentNode.closest('[data-slate-void="true"]');
×
540
            let leafNode = parentNode.closest('[data-slate-leaf]');
×
541
            let domNode: DOMElement | null = null;
×
542

543
            // Calculate how far into the text node the `nearestNode` is, so that we
544
            // can determine what the offset relative to the text node is.
545
            if (leafNode) {
×
546
                textNode = leafNode.closest('[data-slate-node="text"]')!;
×
547
                const window = AngularEditor.getWindow(editor);
×
548
                const range = window.document.createRange();
×
549
                range.setStart(textNode, 0);
×
550
                range.setEnd(nearestNode, nearestOffset);
×
551
                const contents = range.cloneContents();
×
552
                const removals = [
×
553
                    ...Array.prototype.slice.call(contents.querySelectorAll('[data-slate-zero-width]')),
554
                    ...Array.prototype.slice.call(contents.querySelectorAll('[contenteditable=false]'))
555
                ];
556

557
                removals.forEach(el => {
×
558
                    el!.parentNode!.removeChild(el);
×
559
                });
560

561
                // COMPAT: Edge has a bug where Range.prototype.toString() will
562
                // convert \n into \r\n. The bug causes a loop when slate-react
563
                // attempts to reposition its cursor to match the native position. Use
564
                // textContent.length instead.
565
                // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/10291116/
566
                offset = contents.textContent!.length;
×
567
                domNode = textNode;
×
568
            } else if (voidNode) {
×
569
                // For void nodes, the element with the offset key will be a cousin, not an
570
                // ancestor, so find it by going down from the nearest void parent.
571

572
                leafNode = voidNode.querySelector('[data-slate-leaf]')!;
×
573
                parentNode = voidNode.querySelector('[data-slate-length="0"]');
×
574
                textNode = leafNode.closest('[data-slate-node="text"]')!;
×
575
                domNode = leafNode;
×
576
                offset = domNode.textContent!.length;
×
577
            }
578

579
            // COMPAT: If the parent node is a Slate zero-width space, editor is
580
            // because the text node should have no characters. However, during IME
581
            // composition the ASCII characters will be prepended to the zero-width
582
            // space, so subtract 1 from the offset to account for the zero-width
583
            // space character.
584
            if (domNode && offset === domNode.textContent!.length && parentNode && parentNode.hasAttribute('data-slate-zero-width')) {
×
585
                offset--;
×
586
            }
587
        }
588

589
        if (!textNode) {
×
590
            throw new Error(`Cannot resolve a Slate point from DOM point: ${domPoint}`);
×
591
        }
592

593
        // COMPAT: If someone is clicking from one Slate editor into another,
594
        // the select event fires twice, once for the old editor's `element`
595
        // first, and then afterwards for the correct `element`. (2017/03/03)
596
        const slateNode = AngularEditor.toSlateNode(editor, textNode!);
×
597
        const path = AngularEditor.findPath(editor, slateNode);
×
598
        return { path, offset };
×
599
    },
600

601
    /**
602
     * Find a Slate range from a DOM range or selection.
603
     */
604

605
    toSlateRange(editor: AngularEditor, domRange: DOMRange | DOMStaticRange | DOMSelection): Range {
606
        const el = isDOMSelection(domRange) ? domRange.anchorNode : domRange.startContainer;
×
607
        let anchorNode;
608
        let anchorOffset;
609
        let focusNode;
610
        let focusOffset;
611
        let isCollapsed;
612

613
        if (el) {
×
614
            if (isDOMSelection(domRange)) {
×
615
                anchorNode = domRange.anchorNode;
×
616
                anchorOffset = domRange.anchorOffset;
×
617
                focusNode = domRange.focusNode;
×
618
                focusOffset = domRange.focusOffset;
×
619
                // COMPAT: There's a bug in chrome that always returns `true` for
620
                // `isCollapsed` for a Selection that comes from a ShadowRoot.
621
                // (2020/08/08)
622
                // https://bugs.chromium.org/p/chromium/issues/detail?id=447523
623
                if (IS_CHROME && hasShadowRoot()) {
×
624
                    isCollapsed = domRange.anchorNode === domRange.focusNode && domRange.anchorOffset === domRange.focusOffset;
×
625
                } else {
626
                    isCollapsed = domRange.isCollapsed;
×
627
                }
628
            } else {
629
                anchorNode = domRange.startContainer;
×
630
                anchorOffset = domRange.startOffset;
×
631
                focusNode = domRange.endContainer;
×
632
                focusOffset = domRange.endOffset;
×
633
                isCollapsed = domRange.collapsed;
×
634
            }
635
        }
636

637
        if (anchorNode == null || focusNode == null || anchorOffset == null || focusOffset == null) {
×
638
            throw new Error(`Cannot resolve a Slate range from DOM range: ${domRange}`);
×
639
        }
640

641
        const anchor = AngularEditor.toSlatePoint(editor, [anchorNode, anchorOffset]);
×
642
        const focus = isCollapsed ? anchor : AngularEditor.toSlatePoint(editor, [focusNode, focusOffset]);
×
643

644
        return { anchor, focus };
×
645
    },
646

647
    isLeafBlock(editor: AngularEditor, node: Node): boolean {
648
        return Element.isElement(node) && !editor.isInline(node) && Editor.hasInlines(editor, node);
93✔
649
    },
650

651
    isBlockCardLeftCursor(editor: AngularEditor) {
652
        return (
×
653
            editor.selection.anchor.offset === FAKE_LEFT_BLOCK_CARD_OFFSET && editor.selection.focus.offset === FAKE_LEFT_BLOCK_CARD_OFFSET
×
654
        );
655
    },
656

657
    isBlockCardRightCursor(editor: AngularEditor) {
658
        return (
×
659
            editor.selection.anchor.offset === FAKE_RIGHT_BLOCK_CARD_OFFSET &&
×
660
            editor.selection.focus.offset === FAKE_RIGHT_BLOCK_CARD_OFFSET
661
        );
662
    },
663

664
    getCardCursorNode(
665
        editor: AngularEditor,
666
        blockCardNode: Node,
667
        options: {
668
            direction: 'left' | 'right' | 'center';
669
        }
670
    ) {
671
        const blockCardElement = AngularEditor.toDOMNode(editor, blockCardNode);
×
672
        const cardCenter = blockCardElement.parentElement;
×
673
        return options.direction === 'left' ? cardCenter.previousElementSibling : cardCenter.nextElementSibling;
×
674
    },
675

676
    toSlateCardEntry(editor: AngularEditor, node: DOMNode): NodeEntry {
677
        const element = node.parentElement.closest('.slate-block-card')?.querySelector('[card-target="card-center"]').firstElementChild;
×
678
        const slateNode = AngularEditor.toSlateNode(editor, element);
×
679
        const path = AngularEditor.findPath(editor, slateNode);
×
680
        return [slateNode, path];
×
681
    },
682

683
    /**
684
     * move native selection to card-left or card-right
685
     * @param editor
686
     * @param blockCardNode
687
     * @param options
688
     */
689
    moveBlockCard(
690
        editor: AngularEditor,
691
        blockCardNode: Node,
692
        options: {
693
            direction: 'left' | 'right';
694
        }
695
    ) {
696
        const cursorNode = AngularEditor.getCardCursorNode(editor, blockCardNode, options);
×
697
        const window = AngularEditor.getWindow(editor);
×
698
        const domSelection = window.getSelection();
×
699
        domSelection.setBaseAndExtent(cursorNode, 1, cursorNode, 1);
×
700
    },
701

702
    /**
703
     * move slate selection to card-left or card-right
704
     * @param editor
705
     * @param path
706
     * @param options
707
     */
708
    moveBlockCardCursor(
709
        editor: AngularEditor,
710
        path: Path,
711
        options: {
712
            direction: 'left' | 'right';
713
        }
714
    ) {
715
        const cursor = {
×
716
            path,
717
            offset: options.direction === 'left' ? FAKE_LEFT_BLOCK_CARD_OFFSET : FAKE_RIGHT_BLOCK_CARD_OFFSET
×
718
        };
719
        Transforms.select(editor, { anchor: cursor, focus: cursor });
×
720
    },
721

722
    hasRange(editor: AngularEditor, range: Range): boolean {
723
        const { anchor, focus } = range;
×
724
        return Editor.hasPath(editor, anchor.path) && Editor.hasPath(editor, focus.path);
×
725
    }
726
};
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