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

worktile / slate-angular / 543eb906-9e4d-47ae-b0a3-bf4f680c78ba

pending completion
543eb906-9e4d-47ae-b0a3-bf4f680c78ba

push

circleci

Maple13
build: optimizing angular 15 upgrade

267 of 876 branches covered (30.48%)

Branch coverage included in aggregate %.

682 of 1485 relevant lines covered (45.93%)

30.73 hits per line

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

28.0
/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 { FAKE_LEFT_BLOCK_CARD_OFFSET, FAKE_RIGHT_BLOCK_CARD_OFFSET, getCardTargetAttribute, isCardCenterByTargetAttr, isCardLeftByTargetAttr, isCardRightByTargetAttr } from '../utils/block-card';
31
import { SafeAny } from '../types';
32

33
/**
34
 * An Angular and DOM-specific version of the `Editor` interface.
35
 */
36

37
export interface AngularEditor extends BaseEditor {
38
    insertData: (data: DataTransfer) => void;
39
    insertFragmentData: (data: DataTransfer) => boolean
40
    insertTextData: (data: DataTransfer) => boolean
41
    setFragmentData: (data: DataTransfer, originEvent?: 'drag' | 'copy' | 'cut') => void;
42
    deleteCutData: () => void;
43
    onKeydown: (event: KeyboardEvent) => void;
44
    onClick: (event: MouseEvent) => void;
45
    injector: Injector;
46
    isBlockCard: (node: Node) => boolean;
47
    onError: (errorData: SlateError) => void;
48
    hasRange: (editor: AngularEditor, range: Range) => boolean;
49
}
50

51
export const AngularEditor = {
1✔
52
    /**
53
     * Return the host window of the current editor.
54
     */
55

56
    getWindow(editor: AngularEditor): Window {
57
        const window = EDITOR_TO_WINDOW.get(editor);
17✔
58
        if (!window) {
17!
59
            throw new Error('Unable to find a host window element for this editor');
×
60
        }
61
        return window;
17✔
62
    },
63
    /**
64
     * Find a key for a Slate node.
65
     */
66

67
    findKey(editor: AngularEditor, node: Node): Key {
68
        let key = NODE_TO_KEY.get(node);
467✔
69

70
        if (!key) {
467✔
71
            key = new Key();
220✔
72
            NODE_TO_KEY.set(node, key);
220✔
73
        }
74

75
        return key;
467✔
76
    },
77

78
    /**
79
     * handle editor error.
80
     */
81

82
    onError(errorData: SlateError) {
83
        if (errorData.nativeError) {
×
84
            throw errorData.nativeError;
×
85
        }
86
    },
87

88
    /**
89
     * Find the path of Slate node.
90
     */
91

92
    findPath(editor: AngularEditor, node: Node): Path {
93
        const path: Path = [];
353✔
94
        let child = node;
353✔
95

96
        while (true) {
353✔
97
            const parent = NODE_TO_PARENT.get(child);
1,170✔
98

99
            if (parent == null) {
1,170✔
100
                if (Editor.isEditor(child)) {
353!
101
                    return path;
353✔
102
                } else {
103
                    break;
×
104
                }
105
            }
106

107
            const i = NODE_TO_INDEX.get(child);
817✔
108

109
            if (i == null) {
817!
110
                break;
×
111
            }
112

113
            path.unshift(i);
817✔
114
            child = parent;
817✔
115
        }
116
        throw new Error(`Unable to find the path for Slate node: ${JSON.stringify(node)}`);
×
117
    },
118

119
    /**
120
     * Find the DOM node that implements DocumentOrShadowRoot for the editor.
121
     */
122

123
    findDocumentOrShadowRoot(editor: AngularEditor): Document | ShadowRoot {
124
        const el = AngularEditor.toDOMNode(editor, editor)
11✔
125
        const root = el.getRootNode()
11✔
126
        if (
11✔
127
            (root instanceof Document || root instanceof ShadowRoot) &&
22!
128
            (root as Document).getSelection != null
129
        ) {
130
            return root
11✔
131
        }
132

133
        return el.ownerDocument
×
134
    },
135

136
    /**
137
     * Check if the editor is focused.
138
     */
139

140
    isFocused(editor: AngularEditor): boolean {
141
        return !!IS_FOCUSED.get(editor);
10✔
142
    },
143

144
    /**
145
     * Check if the editor is in read-only mode.
146
     */
147

148
    isReadonly(editor: AngularEditor): boolean {
149
        return !!IS_READONLY.get(editor);
×
150
    },
151

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

168
    /**
169
     * Blur the editor.
170
     */
171

172
    blur(editor: AngularEditor): void {
173
        const el = AngularEditor.toDOMNode(editor, editor);
×
174
        const root = AngularEditor.findDocumentOrShadowRoot(editor);
×
175
        IS_FOCUSED.set(editor, false);
×
176

177
        if (root.activeElement === el) {
×
178
            el.blur();
×
179
        }
180
    },
181

182
    /**
183
     * Focus the editor.
184
     */
185

186
    focus(editor: AngularEditor): void {
187
        const el = AngularEditor.toDOMNode(editor, editor);
1✔
188
        IS_FOCUSED.set(editor, true);
1✔
189

190
        const window = AngularEditor.getWindow(editor);
1✔
191
        if (window.document.activeElement !== el) {
1✔
192
            el.focus({ preventScroll: true });
1✔
193
        }
194
    },
195

196
    /**
197
     * Deselect the editor.
198
     */
199

200
    deselect(editor: AngularEditor): void {
201
        const { selection } = editor;
×
202
        const root = AngularEditor.findDocumentOrShadowRoot(editor);
×
203
        const domSelection = (root as Document).getSelection();
×
204

205
        if (domSelection && domSelection.rangeCount > 0) {
×
206
            domSelection.removeAllRanges();
×
207
        }
208

209
        if (selection) {
×
210
            Transforms.deselect(editor);
×
211
        }
212
    },
213

214
    /**
215
     * Check if a DOM node is within the editor.
216
     */
217

218
    hasDOMNode(editor: AngularEditor, target: DOMNode, options: { editable?: boolean } = {}): boolean {
×
219
        const { editable = false } = options;
2!
220
        const editorEl = AngularEditor.toDOMNode(editor, editor);
2✔
221
        let targetEl;
222

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

235
        if (!targetEl) {
2!
236
            return false;
×
237
        }
238

239
        return targetEl.closest(`[data-slate-editor]`) === editorEl &&
2!
240
            (!editable || targetEl.isContentEditable ||
241
                !!targetEl.getAttribute('data-slate-zero-width'));
242
    },
243

244
    /**
245
     * Insert data from a `DataTransfer` into the editor.
246
     */
247

248
    insertData(editor: AngularEditor, data: DataTransfer): void {
249
        editor.insertData(data);
×
250
    },
251

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

256
    insertFragmentData(editor: AngularEditor, data: DataTransfer): boolean {
257
        return editor.insertFragmentData(data)
×
258
    },
259

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

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

268
    /**
269
     * onKeydown hook.
270
     */
271
    onKeydown(editor: AngularEditor, data: KeyboardEvent): void {
272
        editor.onKeydown(data);
×
273
    },
274

275
    /**
276
    * onClick hook.
277
    */
278
    onClick(editor: AngularEditor, data: MouseEvent): void {
279
        editor.onClick(data);
×
280
    },
281

282
    /**
283
     * Sets data from the currently selected fragment on a `DataTransfer`.
284
     */
285

286
    setFragmentData(editor: AngularEditor, data: DataTransfer, originEvent?: 'drag' | 'copy' | 'cut'): void {
287
        editor.setFragmentData(data, originEvent);
×
288
    },
289

290
    deleteCutData(editor: AngularEditor): void {
291
        editor.deleteCutData();
×
292
    },
293

294
    /**
295
     * Find the native DOM element from a Slate node.
296
     */
297

298
    toDOMNode(editor: AngularEditor, node: Node): HTMLElement {
299
        const domNode = Editor.isEditor(node)
20✔
300
            ? EDITOR_TO_ELEMENT.get(editor)
301
            : NODE_TO_ELEMENT.get(node);
302

303
        if (!domNode) {
20!
304
            throw new Error(`Cannot resolve a DOM node from Slate node: ${JSON.stringify(node)}`);
×
305
        }
306

307
        return domNode;
20✔
308
    },
309

310
    /**
311
     * Find a native DOM selection point from a Slate point.
312
     */
313

314
    toDOMPoint(editor: AngularEditor, point: Point): DOMPoint {
315
        const [node] = Editor.node(editor, point.path);
2✔
316
        const el = AngularEditor.toDOMNode(editor, node);
2✔
317
        let domPoint: DOMPoint | undefined;
318

319
        // block card
320
        const cardTargetAttr = getCardTargetAttribute(el);
2✔
321
        if (cardTargetAttr) {
2!
322
            if (point.offset === FAKE_LEFT_BLOCK_CARD_OFFSET) {
×
323
                const cursorNode = AngularEditor.getCardCursorNode(editor, node, { direction: 'left' });
×
324
                return [cursorNode, 1];
×
325
            } else {
326
                const cursorNode = AngularEditor.getCardCursorNode(editor, node, { direction: 'right' });
×
327
                return [cursorNode, 1];
×
328
            }
329
        }
330

331
        // If we're inside a void node, force the offset to 0, otherwise the zero
332
        // width spacing character will result in an incorrect offset of 1
333
        if (Editor.void(editor, { at: point })) {
2!
334
            point = { path: point.path, offset: 0 };
×
335
        }
336

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

344
        for (const text of texts) {
2✔
345
            const domNode = text.childNodes[0] as HTMLElement;
2✔
346

347
            if (domNode == null || domNode.textContent == null) {
2!
348
                continue;
×
349
            }
350

351
            const { length } = domNode.textContent;
2✔
352
            const attr = text.getAttribute('data-slate-length');
2✔
353
            const trueLength = attr == null ? length : parseInt(attr, 10);
2✔
354
            const end = start + trueLength;
2✔
355

356
            if (point.offset <= end) {
2✔
357
                const offset = Math.min(length, Math.max(0, point.offset - start));
2✔
358
                domPoint = [domNode, offset];
2✔
359
                // fixed cursor position after zero width char
360
                if (offset === 0 && length === 1 && domNode.textContent === '\uFEFF') {
2✔
361
                    domPoint = [domNode, offset + 1];
1✔
362
                }
363
                break;
2✔
364
            }
365

366
            start = end;
×
367
        }
368

369
        if (!domPoint) {
2!
370
            throw new Error(`Cannot resolve a DOM point from Slate point: ${JSON.stringify(point)}`);
×
371
        }
372

373
        return domPoint;
2✔
374
    },
375

376
    /**
377
     * Find a native DOM range from a Slate `range`.
378
     */
379

380
    toDOMRange(editor: AngularEditor, range: Range): DOMRange {
381
        const { anchor, focus } = range;
2✔
382
        const isBackward = Range.isBackward(range);
2✔
383
        const domAnchor = AngularEditor.toDOMPoint(editor, anchor);
2✔
384
        const domFocus = Range.isCollapsed(range) ? domAnchor : AngularEditor.toDOMPoint(editor, focus);
2!
385

386
        const window = AngularEditor.getWindow(editor);
2✔
387
        const domRange = window.document.createRange();
2✔
388
        const [startNode, startOffset] = isBackward ? domFocus : domAnchor;
2!
389
        const [endNode, endOffset] = isBackward ? domAnchor : domFocus;
2!
390

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

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

408
    /**
409
     * Find a Slate node from a native DOM `element`.
410
     */
411

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

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

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

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

425
        return node;
×
426
    },
427

428
    /**
429
     * Get the target range from a DOM `event`.
430
     */
431

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

437
        const { clientX: x, clientY: y, target } = event;
×
438

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

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

446
        // If the drop target is inside a void node, move it into either the
447
        // next or previous node, depending on which side the `x` and `y`
448
        // coordinates are closest to.
449
        if (Editor.isVoid(editor, node)) {
×
450
            const rect = target.getBoundingClientRect();
×
451
            const isPrev = editor.isInline(node)
×
452
                ? x - rect.left < rect.left + rect.width - x
453
                : 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
                }
514
                else {
515
                    return { path: blockPath, offset: -2 };
×
516
                }
517
            }
518
            // forward
519
            // and to the end of previous node
520
            if (isCardLeftByTargetAttr(cardTargetAttr) && !isBackward) {
×
521
                const endPath =
522
                    blockPath[blockPath.length - 1] <= 0
×
523
                        ? blockPath
524
                        : Path.previous(blockPath);
525
                return Editor.end(editor, endPath);
×
526
            }
527
            // to the of current node
528
            if (
×
529
                (isCardCenterByTargetAttr(cardTargetAttr) ||
×
530
                    isCardRightByTargetAttr(cardTargetAttr)) &&
531
                !isBackward
532
            ) {
533
                return Editor.end(editor, blockPath);
×
534
            }
535
            // backward
536
            // and to the start of next node
537
            if (isCardRightByTargetAttr(cardTargetAttr) && isBackward) {
×
538
                return Editor.start(editor, Path.next(blockPath));
×
539
            }
540
            // and to the start of current node
541
            if (
×
542
                (isCardCenterByTargetAttr(cardTargetAttr) ||
×
543
                    isCardLeftByTargetAttr(cardTargetAttr)) &&
544
                isBackward
545
            ) {
546
                return Editor.start(editor, blockPath);
×
547
            }
548
        }
549

550
        if (parentNode) {
×
551
            const voidNode = parentNode.closest('[data-slate-void="true"]');
×
552
            let leafNode = parentNode.closest('[data-slate-leaf]');
×
553
            let domNode: DOMElement | null = null;
×
554

555
            // Calculate how far into the text node the `nearestNode` is, so that we
556
            // can determine what the offset relative to the text node is.
557
            if (leafNode) {
×
558
                textNode = leafNode.closest('[data-slate-node="text"]')!;
×
559
                const window = AngularEditor.getWindow(editor);
×
560
                const range = window.document.createRange();
×
561
                range.setStart(textNode, 0);
×
562
                range.setEnd(nearestNode, nearestOffset);
×
563
                const contents = range.cloneContents();
×
564
                const removals = [
×
565
                    ...Array.prototype.slice.call(
566
                        contents.querySelectorAll('[data-slate-zero-width]')
567
                    ),
568
                    ...Array.prototype.slice.call(
569
                        contents.querySelectorAll('[contenteditable=false]')
570
                    ),
571
                ];
572

573
                removals.forEach(el => {
×
574
                    el!.parentNode!.removeChild(el);
×
575
                });
576

577
                // COMPAT: Edge has a bug where Range.prototype.toString() will
578
                // convert \n into \r\n. The bug causes a loop when slate-react
579
                // attempts to reposition its cursor to match the native position. Use
580
                // textContent.length instead.
581
                // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/10291116/
582
                offset = contents.textContent!.length;
×
583
                domNode = textNode;
×
584
            } else if (voidNode) {
×
585
                // For void nodes, the element with the offset key will be a cousin, not an
586
                // ancestor, so find it by going down from the nearest void parent.
587

588
                leafNode = voidNode.querySelector('[data-slate-leaf]')!;
×
589
                parentNode = voidNode.querySelector('[data-slate-length="0"]');
×
590
                textNode = leafNode.closest('[data-slate-node="text"]')!;
×
591
                domNode = leafNode;
×
592
                offset = domNode.textContent!.length;
×
593
            }
594

595
            // COMPAT: If the parent node is a Slate zero-width space, editor is
596
            // because the text node should have no characters. However, during IME
597
            // composition the ASCII characters will be prepended to the zero-width
598
            // space, so subtract 1 from the offset to account for the zero-width
599
            // space character.
600
            if (domNode &&
×
601
                offset === domNode.textContent!.length &&
602
                (parentNode && parentNode.hasAttribute('data-slate-zero-width'))
603
            ) {
604
                offset--;
×
605
            }
606
        }
607

608
        if (!textNode) {
×
609
            throw new Error(`Cannot resolve a Slate point from DOM point: ${domPoint}`);
×
610
        }
611

612
        // COMPAT: If someone is clicking from one Slate editor into another,
613
        // the select event fires twice, once for the old editor's `element`
614
        // first, and then afterwards for the correct `element`. (2017/03/03)
615
        const slateNode = AngularEditor.toSlateNode(editor, textNode!);
×
616
        const path = AngularEditor.findPath(editor, slateNode);
×
617
        return { path, offset };
×
618
    },
619

620
    /**
621
     * Find a Slate range from a DOM range or selection.
622
     */
623

624
    toSlateRange(editor: AngularEditor, domRange: DOMRange | DOMStaticRange | DOMSelection): Range {
625
        const el = isDOMSelection(domRange) ? domRange.anchorNode : domRange.startContainer;
×
626
        let anchorNode;
627
        let anchorOffset;
628
        let focusNode;
629
        let focusOffset;
630
        let isCollapsed;
631

632
        if (el) {
×
633
            if (isDOMSelection(domRange)) {
×
634
                anchorNode = domRange.anchorNode;
×
635
                anchorOffset = domRange.anchorOffset;
×
636
                focusNode = domRange.focusNode;
×
637
                focusOffset = domRange.focusOffset;
×
638
                // COMPAT: There's a bug in chrome that always returns `true` for
639
                // `isCollapsed` for a Selection that comes from a ShadowRoot.
640
                // (2020/08/08)
641
                // https://bugs.chromium.org/p/chromium/issues/detail?id=447523
642
                if (IS_CHROME && hasShadowRoot()) {
×
643
                    isCollapsed =
×
644
                        domRange.anchorNode === domRange.focusNode &&
×
645
                        domRange.anchorOffset === domRange.focusOffset;
646
                } else {
647
                    isCollapsed = domRange.isCollapsed;
×
648
                }
649
            } else {
650
                anchorNode = domRange.startContainer;
×
651
                anchorOffset = domRange.startOffset;
×
652
                focusNode = domRange.endContainer;
×
653
                focusOffset = domRange.endOffset;
×
654
                isCollapsed = domRange.collapsed;
×
655
            }
656
        }
657

658
        if (anchorNode == null || focusNode == null || anchorOffset == null || focusOffset == null) {
×
659
            throw new Error(`Cannot resolve a Slate range from DOM range: ${domRange}`);
×
660
        }
661

662
        const anchor = AngularEditor.toSlatePoint(editor, [anchorNode, anchorOffset]);
×
663
        const focus = isCollapsed ? anchor : AngularEditor.toSlatePoint(editor, [focusNode, focusOffset]);
×
664

665
        return { anchor, focus };
×
666
    },
667

668
    isLeafBlock(editor: AngularEditor, node: Node): boolean {
669
        return Element.isElement(node) && !editor.isInline(node) && Editor.hasInlines(editor, node);
93✔
670
    },
671

672
    isBlockCardLeftCursor(editor: AngularEditor) {
673
        return editor.selection.anchor.offset === FAKE_LEFT_BLOCK_CARD_OFFSET && editor.selection.focus.offset === FAKE_LEFT_BLOCK_CARD_OFFSET;
×
674
    },
675

676
    isBlockCardRightCursor(editor: AngularEditor) {
677
        return editor.selection.anchor.offset === FAKE_RIGHT_BLOCK_CARD_OFFSET && editor.selection.focus.offset === FAKE_RIGHT_BLOCK_CARD_OFFSET;
×
678
    },
679

680
    getCardCursorNode(editor: AngularEditor, blockCardNode: Node, options: {
681
        direction: 'left' | 'right' | 'center'
682
    }) {
683
        const blockCardElement = AngularEditor.toDOMNode(editor, blockCardNode);
×
684
        const cardCenter = blockCardElement.parentElement;
×
685
        return options.direction === 'left'
×
686
            ? cardCenter.previousElementSibling
687
            : cardCenter.nextElementSibling;
688
    },
689

690
    toSlateCardEntry(editor: AngularEditor, node: DOMNode): NodeEntry {
691
        const element = node.parentElement
×
692
            .closest('.slate-block-card')?.querySelector('[card-target="card-center"]')
693
            .firstElementChild;
694
        const slateNode = AngularEditor.toSlateNode(editor, element);
×
695
        const path = AngularEditor.findPath(editor, slateNode);
×
696
        return [slateNode, path];
×
697
    },
698

699
    /**
700
     * move native selection to card-left or card-right
701
     * @param editor 
702
     * @param blockCardNode 
703
     * @param options 
704
     */
705
    moveBlockCard(editor: AngularEditor, blockCardNode: Node, options: {
706
        direction: 'left' | 'right'
707
    }) {
708
        const cursorNode = AngularEditor.getCardCursorNode(editor, blockCardNode, options);
×
709
        const window = AngularEditor.getWindow(editor);
×
710
        const domSelection = window.getSelection();
×
711
        domSelection.setBaseAndExtent(cursorNode, 1, cursorNode, 1);
×
712
    },
713

714
    /**
715
     * move slate selection to card-left or card-right
716
     * @param editor 
717
     * @param path 
718
     * @param options 
719
     */
720
    moveBlockCardCursor(editor: AngularEditor, path: Path, options: {
721
        direction: 'left' | 'right'
722
    }) {
723
        const cursor = { path, offset: options.direction === 'left' ? FAKE_LEFT_BLOCK_CARD_OFFSET : FAKE_RIGHT_BLOCK_CARD_OFFSET };
×
724
        Transforms.select(editor, { anchor: cursor, focus: cursor });
×
725
    },
726

727
    hasRange(editor: AngularEditor, range: Range): boolean {
728
        const { anchor, focus } = range;
×
729
        return (
×
730
            Editor.hasPath(editor, anchor.path) && Editor.hasPath(editor, focus.path)
×
731
        );
732
    },
733
};
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