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

worktile / slate-angular / 0786bfb0-2da6-4a02-b2d0-80c01cd79276

12 Nov 2025 03:33AM UTC coverage: 47.244% (+0.4%) from 46.877%
0786bfb0-2da6-4a02-b2d0-80c01cd79276

push

circleci

web-flow
feat(flavour): support extendable flavour(based dom) to improve performance (#300)

* feat(flavour): support extendable flavour(based dom) to improve performance
1. Support angular-component, angular-template, flavour(dom) three kinds extendable view
2. Implement string render based native dom
3. Remove default-element, default-text, default-string, default-leaf and so on angular components
- [x] placeholder is right
- [x] void-string, empty-string, compatible-string, line-break-string are right
- [x] outline-parent is right
- [x] custom flavours are right

* fix(flavour): element support rerender and add void-text.flavour

* feat: revert SlateString component to avoid throwing error(other components need to use this component and need to bump latest slate-angular)

* chore: support flavour view(based native dom) to improve performance

* chore: enter pre

* fix: unit test

373 of 992 branches covered (37.6%)

Branch coverage included in aggregate %.

234 of 291 new or added lines in 17 files covered. (80.41%)

24 existing lines in 3 files now uncovered.

1007 of 1929 relevant lines covered (52.2%)

32.86 hits per line

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

83.48
/packages/src/components/string/string-render.ts
1
import { SlateLeafContext } from '../../view/context';
2
import { SlateViewContext } from '../../view/context';
3
import { Text, Node } from 'slate';
4

5
export enum StringType {
1✔
6
    'normalString' = 'normalString',
1✔
7
    'lineBreakEmptyString' = 'lineBreakEmptyString',
1✔
8
    'normalEmptyText' = 'normalEmptyText',
1✔
9
    'compatibleString' = 'compatibleString',
1✔
10
    'voidString' = 'voidString'
1✔
11
}
12

13
export class SlateStringRender {
14
    nativeElement: HTMLElement;
15

16
    constructor(
17
        public context: SlateLeafContext,
152✔
18
        public viewContext: SlateViewContext
152✔
19
    ) {}
20

21
    type: StringType;
22

23
    // COMPAT: If the text is empty, it's because it's on the edge of an inline
24
    // node, so we render a zero-width space so that the selection can be
25
    // inserted next to it still.
26
    isEmptyText() {
27
        return this.context.leaf.text === '';
147✔
28
    }
29

30
    // COMPAT: Browsers will collapse trailing new lines at the end of blocks,
31
    // so we need to add an extra trailing new lines to prevent that.
32
    isCompatibleString() {
33
        return this.context.isLast && this.context.leaf.text.slice(-1) === '\n';
147✔
34
    }
35

36
    // COMPAT: Render text inside void nodes with a zero-width space.
37
    // So the node can contain selection but the text is not visible.
38
    isVoid() {
39
        return this.viewContext.editor.isVoid(this.context.parent);
148✔
40
    }
41

42
    get leaf(): Text {
43
        return this.context && this.context.leaf;
147✔
44
    }
45

46
    // COMPAT: If this is the last text node in an empty block, render a zero-
47
    // width space that will convert into a line break when copying and pasting
48
    // to support expected plain text.
49
    isLineBreakEmptyString() {
50
        return (
158✔
51
            this.context.leaf.text === '' &&
191✔
52
            this.context.parent.children[this.context.parent.children.length - 1] === this.context.text &&
53
            !this.viewContext.editor.isInline(this.context.parent) &&
54
            // [list-render] performance optimization: reduce the number of calls to the `Editor.string(editor, path)` method
55
            isEmpty(this.viewContext.editor, this.context.parent)
56
        );
57
    }
58

59
    createStringNode(type: StringType) {
60
        let newNativeElement: HTMLElement;
61
        switch (type) {
155!
62
            case StringType.lineBreakEmptyString:
63
                newNativeElement = createLineBreakEmptyStringDOM(this.getElementStringLength());
8✔
64
                break;
8✔
65
            case StringType.voidString:
66
            case StringType.normalEmptyText:
67
                newNativeElement = createEmptyOrVoidStringNode();
1✔
68
                break;
1✔
69
            case StringType.compatibleString:
NEW
70
                newNativeElement = createCompatibleStringNode(this.leaf.text);
×
NEW
71
                break;
×
72
            case StringType.normalString:
73
                newNativeElement = createDefaultStringNode(this.leaf.text);
146✔
74
                break;
146✔
75
            default:
NEW
76
                newNativeElement = createDefaultStringNode(this.leaf.text);
×
77
        }
78
        return newNativeElement;
155✔
79
    }
80

81
    render() {
82
        this.type = this.getType();
152✔
83
        this.nativeElement = this.createStringNode(this.type);
152✔
84
        return this.nativeElement;
152✔
85
    }
86

87
    getType() {
88
        if (this.isLineBreakEmptyString()) {
158✔
89
            return StringType.lineBreakEmptyString;
10✔
90
        }
91
        if (this.isVoid()) {
148✔
92
            return StringType.voidString;
1✔
93
        }
94
        if (this.isEmptyText()) {
147!
NEW
95
            return StringType.normalEmptyText;
×
96
        }
97
        if (this.isCompatibleString()) {
147!
NEW
98
            return StringType.compatibleString;
×
99
        }
100
        return StringType.normalString;
147✔
101
    }
102

103
    update(context: SlateLeafContext, viewContext: SlateViewContext) {
104
        this.context = context;
6✔
105
        this.viewContext = viewContext;
6✔
106
        const type = this.getType();
6✔
107
        if (type !== this.type) {
6✔
108
            const newNativeElement = this.createStringNode(type);
3✔
109
            this.nativeElement.replaceWith(newNativeElement);
3✔
110
            this.nativeElement = newNativeElement;
3✔
111
            this.type = type;
3✔
112
            return;
3✔
113
        }
114
        if (this.type === StringType.normalString) {
3✔
115
            this.nativeElement.textContent = this.leaf.text;
1✔
116
        }
117
    }
118

119
    getElementStringLength() {
120
        return Node.string(this.context.parent).length;
8✔
121
    }
122
}
123

124
export const createDefaultStringNode = (text: string) => {
1✔
125
    const stringNode = document.createElement('span');
146✔
126
    stringNode.textContent = text;
146✔
127
    stringNode.setAttribute('data-slate-string', 'true');
146✔
128
    stringNode.setAttribute('editable-text', '');
146✔
129
    return stringNode;
146✔
130
};
131

132
export const createEmptyOrVoidStringNode = () => {
1✔
133
    const stringNode = document.createElement('span');
1✔
134
    stringNode.setAttribute('data-slate-string', 'true');
1✔
135
    stringNode.setAttribute('data-slate-zero-width', 'z');
1✔
136
    stringNode.setAttribute('data-slate-length', '0');
1✔
137
    const zeroWidthSpace = document.createTextNode('\uFEFF');
1✔
138
    stringNode.appendChild(zeroWidthSpace);
1✔
139
    stringNode.setAttribute('editable-text', '');
1✔
140
    return stringNode;
1✔
141
};
142

143
export const createCompatibleStringNode = (text: string) => {
1✔
NEW
144
    const stringNode = document.createElement('span');
×
NEW
145
    stringNode.setAttribute('data-slate-string', 'true');
×
NEW
146
    stringNode.textContent = text;
×
NEW
147
    stringNode.setAttribute('editable-text', '');
×
NEW
148
    const zeroWidthSpan = document.createElement('span');
×
NEW
149
    const zeroWidthSpace = document.createTextNode('\uFEFF');
×
NEW
150
    zeroWidthSpan.setAttribute('data-slate-zero-width', '');
×
NEW
151
    zeroWidthSpan.appendChild(zeroWidthSpace);
×
NEW
152
    stringNode.appendChild(zeroWidthSpan);
×
NEW
153
    return stringNode;
×
154
};
155

156
export const createLineBreakEmptyStringDOM = (elementStringLength: number) => {
1✔
157
    const stringNode = document.createElement('span');
8✔
158
    stringNode.setAttribute('data-slate-zero-width', 'n');
8✔
159
    stringNode.setAttribute('data-slate-length', `${elementStringLength}`);
8✔
160
    const zeroWidthSpace = document.createTextNode(`\uFEFF`);
8✔
161
    stringNode.appendChild(zeroWidthSpace);
8✔
162
    const brNode = document.createElement('br');
8✔
163
    stringNode.appendChild(brNode);
8✔
164
    stringNode.setAttribute('editable-text', '');
8✔
165
    return stringNode;
8✔
166
};
167

168
/**
169
 * TODO: remove when bump slate
170
 * copy from slate
171
 * @param editor
172
 * @param element
173
 * @returns
174
 */
175
export const isEmpty = (editor, element) => {
1✔
176
    const { children } = element;
11✔
177
    const [first] = children;
11✔
178
    return children.length === 0 || (children.length === 1 && Text.isText(first) && first.text === '' && !editor.isVoid(element));
11✔
179
};
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