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

worktile / slate-angular / 7479b241-c5fc-498b-b8ec-074970549d63

08 Jan 2026 04:30AM UTC coverage: 36.838% (-0.02%) from 36.861%
7479b241-c5fc-498b-b8ec-074970549d63

push

circleci

pubuzhixing8
fix(flavour): support update compatible string to fix the issue mentioned in #WIK-19720

401 of 1283 branches covered (31.25%)

Branch coverage included in aggregate %.

2 of 7 new or added lines in 1 file covered. (28.57%)

1102 of 2797 relevant lines covered (39.4%)

23.97 hits per line

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

79.67
/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
import { getZeroTextNode } from '../../utils/dom';
5

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

14
export class SlateStringRender {
15
    nativeElement: HTMLElement;
16

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

22
    type: StringType;
23

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

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

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

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

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

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

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

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

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

123
    getElementStringLength() {
124
        return Node.string(this.context.parent).length;
8 ✔
125
    }
126
}
127

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

136
export const createEmptyOrVoidStringNode = () => {
1 ✔
137
    const stringNode = document.createElement('span');
1 ✔
138
    stringNode.setAttribute('data-slate-string', 'true');
1 ✔
139
    stringNode.setAttribute('data-slate-zero-width', 'z');
1 ✔
140
    stringNode.setAttribute('data-slate-length', '0');
1 ✔
141
    const zeroWidthSpace = getZeroTextNode();
1 ✔
142
    stringNode.appendChild(zeroWidthSpace);
1 ✔
143
    stringNode.setAttribute('editable-text', '');
1 ✔
144
    return stringNode;
1 ✔
145
};
146

147
export const createCompatibleStringNode = (text: string) => {
1 ✔
148
    const stringNode = document.createElement('span');
×
149
    stringNode.setAttribute('data-slate-string', 'true');
×
150
    stringNode.textContent = text;
×
151
    stringNode.setAttribute('editable-text', '');
×
152
    const zeroWidthSpan = document.createElement('span');
×
153
    const zeroWidthSpace = getZeroTextNode();
×
154
    zeroWidthSpan.setAttribute('data-slate-zero-width', '');
×
155
    zeroWidthSpan.appendChild(zeroWidthSpace);
×
156
    stringNode.appendChild(zeroWidthSpan);
×
157
    return stringNode;
×
158
};
159

160
export const updateCompatibleStringNode = (stringNode: HTMLSpanElement, text: string) => {
1 ✔
NEW
161
    const zeroWidthSpan = stringNode.querySelector('span');
×
NEW
162
    stringNode.textContent = text;
×
NEW
163
    stringNode.appendChild(zeroWidthSpan);
×
NEW
164
    return stringNode;
×
165
};
166

167
export const createLineBreakEmptyStringDOM = (elementStringLength: number) => {
1 ✔
168
    const stringNode = document.createElement('span');
8 ✔
169
    stringNode.setAttribute('data-slate-zero-width', 'n');
8 ✔
170
    stringNode.setAttribute('data-slate-length', `${elementStringLength}`);
8 ✔
171
    const zeroWidthSpace = getZeroTextNode();
8 ✔
172
    stringNode.appendChild(zeroWidthSpace);
8 ✔
173
    const brNode = document.createElement('br');
8 ✔
174
    stringNode.appendChild(brNode);
8 ✔
175
    stringNode.setAttribute('editable-text', '');
8 ✔
176
    return stringNode;
8 ✔
177
};
178

179
/**
180
 * TODO: remove when bump slate
181
 * copy from slate
182
 * @param editor
183
 * @param element
184
 * @returns
185
 */
186
export const isEmpty = (editor, element) => {
1 ✔
187
    const { children } = element;
11 ✔
188
    const [first] = children;
11 ✔
189
    return children.length === 0 || (children.length === 1 && Text.isText(first) && first.text === '' && !editor.isVoid(element));
11 ✔
190
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc