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

atinc / ngx-tethys / 82f575a8-2f12-4689-80e6-398f6f1685a3

15 Aug 2024 08:19AM UTC coverage: 90.473%. Remained the same
82f575a8-2f12-4689-80e6-398f6f1685a3

push

circleci

web-flow
build: bump prettier to 3.3.3 and other deps, refactor notify use @if (#3152)

5502 of 6726 branches covered (81.8%)

Branch coverage included in aggregate %.

112 of 116 new or added lines in 57 files covered. (96.55%)

59 existing lines in 15 files now uncovered.

13254 of 14005 relevant lines covered (94.64%)

997.41 hits per line

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

91.82
/src/icon/icon.component.ts
1
import { take } from 'rxjs/operators';
2
import { useHostRenderer } from '@tethys/cdk/dom';
3

4
import {
5
    ChangeDetectionStrategy,
6
    Component,
7
    ElementRef,
8
    HostBinding,
1✔
9
    Input,
10
    OnChanges,
11
    OnInit,
12
    Renderer2,
13
    SimpleChanges,
14
    ViewEncapsulation,
15
    numberAttribute
16
} from '@angular/core';
17

1✔
18
import { getWhetherPrintErrorWhenIconNotFound } from './config';
19
import { ThyIconRegistry } from './icon-registry';
6,706✔
20
import { coerceBooleanProperty } from 'ngx-tethys/util';
6,706✔
21

6,706✔
22
const iconSuffixMap = {
6,706✔
23
    fill: 'fill',
6,706✔
24
    twotone: 'tt'
6,706✔
25
};
26

27
/**
6,696✔
28
 * 图标组件
6,696✔
29
 * @name thy-icon,[thy-icon]
30
 * @order 10
31
 */
6,798✔
32
@Component({
102✔
33
    selector: 'thy-icon, [thy-icon]',
34
    template: '<ng-content></ng-content>',
35
    changeDetection: ChangeDetectionStrategy.OnPush,
36
    encapsulation: ViewEncapsulation.None,
37
    standalone: true,
90✔
38
    host: {
39
        class: 'thy-icon'
12✔
40
    }
10✔
41
})
42
export class ThyIcon implements OnInit, OnChanges {
43
    private initialized = false;
44

45
    /**
6,786✔
46
     * 图标的类型
6,786✔
47
     * @type outline | fill | twotone
6,641✔
48
     */
6,639✔
49
    @Input('thyIconType') iconType: 'outline' | 'fill' | 'twotone' = 'outline';
50

51
    @Input('thyTwotoneColor') iconTwotoneColor: string;
52

1,269✔
53
    /**
54
     * 图标的名字
5,168!
55
     */
×
56
    @Input('thyIconName') iconName: string;
57

58
    /**
6,639!
59
     * 图标的旋转角度
60
     * @default 0
61
     */
2✔
62
    @Input({ alias: 'thyIconRotate', transform: numberAttribute }) iconRotate: number;
63

64
    @Input('thyIconSet') iconSet: string;
2✔
65

66
    /**
67
     * 图标打底色,镂空的图标,会透过颜色来
68
     * @default false
69
     */
1,279✔
70
    @HostBinding(`class.thy-icon-legging`)
22✔
71
    @Input({ alias: 'thyIconLegging', transform: coerceBooleanProperty })
72
    iconLegging: boolean;
73

74
    @Input({ alias: 'thyIconLinearGradient', transform: coerceBooleanProperty })
75
    iconLinearGradient: boolean;
1,269✔
76

77
    private hostRenderer = useHostRenderer();
78

79
    constructor(
1,269✔
80
        private render: Renderer2,
1,269✔
81
        private elementRef: ElementRef,
16✔
82
        private iconRegistry: ThyIconRegistry
83
    ) {}
1,269✔
84

1✔
85
    ngOnInit() {
1!
86
        this.updateClasses();
1✔
87
        this.initialized = true;
2✔
88
    }
1✔
89

90
    ngOnChanges(changes: SimpleChanges) {
91
        if (this.initialized) {
92
            if (
93
                changes['iconName'] ||
94
                changes['iconSet'] ||
95
                changes['iconTwotoneColor'] ||
96
                changes['iconType'] ||
97
                changes['iconLinearGradient']
98
            ) {
99
                this.updateClasses();
100
            } else if (changes['iconRotate']) {
101
                this.setStyleRotate();
1,269✔
102
            }
1✔
103
        }
1✔
104
    }
105

1,269✔
106
    private updateClasses() {
1,269✔
107
        const [namespace, iconName] = this.iconRegistry.splitIconName(this.iconName);
108
        if (iconName) {
109
            if (this.iconRegistry.iconMode === 'svg') {
1,269✔
110
                this.iconRegistry
1,269✔
111
                    .getSvgIcon(this.buildIconNameByType(iconName), namespace)
112
                    .pipe(take(1))
113
                    .subscribe(
114
                        svg => {
115
                            this.setSvgElement(svg);
116
                        },
1,269✔
117
                        (error: Error) => {
7✔
118
                            if (getWhetherPrintErrorWhenIconNotFound()) {
119
                                console.error(`Error retrieving icon: ${error.message}`);
120
                            }
7!
121
                        }
7✔
122
                    );
123
                this.hostRenderer.updateClass([`thy-icon${namespace ? `-${namespace}` : ``}-${this.buildIconNameByType(iconName)}`]);
124
            } else {
125
                const fontSetClass = this.iconSet
126
                    ? this.iconRegistry.getFontSetClassByAlias(this.iconSet)
127
                    : this.iconRegistry.getDefaultFontSetClass();
13,278✔
128
                this.hostRenderer.updateClass([fontSetClass, `${fontSetClass}-${this.iconName}`]);
6✔
129
            }
6✔
130
        }
131
    }
132

13,272✔
133
    private setStyleRotate() {
134
        if (this.iconRotate !== undefined) {
135
            this.render.setStyle(this.elementRef.nativeElement.querySelector('svg'), 'transform', `rotate(${this.iconRotate}deg)`);
136
        }
137
    }
138

139
    //#region svg element
140

1✔
141
    private setSvgElement(svg: SVGElement) {
1✔
142
        this.clearSvgElement();
1!
UNCOV
143

×
144
        // Workaround for IE11 and Edge ignoring `style` tags inside dynamically-created SVGs.
145
        // See: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/10898469/
1!
UNCOV
146
        // Do this before inserting the element into the DOM, in order to avoid a style recalculation.
×
147
        const styleTags = svg.querySelectorAll('style') as NodeListOf<HTMLStyleElement>;
148

149
        for (let i = 0; i < styleTags.length; i++) {
150
            styleTags[i].textContent += ' ';
151
        }
1✔
152

1✔
153
        if (this.iconType === 'twotone') {
154
            const allPaths = svg.querySelectorAll('path');
1✔
155
            if (allPaths.length > 1) {
156
                allPaths.forEach((child, index: number) => {
157
                    if (child.getAttribute('id').includes('secondary-color')) {
158
                        child.setAttribute('fill', this.iconTwotoneColor);
159
                    }
1✔
160
                });
161
            }
162
        }
163

164
        // Note: we do this fix here, rather than the icon registry, because the
165
        // references have to point to the URL at the time that the icon was created.
166
        // if (this._location) {
167
        //     const path = this._location.getPathname();
168
        //     this._previousPath = path;
169
        //     this._cacheChildrenWithExternalReferences(svg);
1✔
170
        //     this._prependPathToReferences(path);
171
        // }
172
        if (this.iconLinearGradient) {
173
            this.setBaseUrl(svg);
174
            this.clearTitleElement(svg);
175
        }
176

177
        this.elementRef.nativeElement.appendChild(svg);
178
        this.setStyleRotate();
179
    }
180

181
    private clearSvgElement() {
182
        const layoutElement: HTMLElement = this.elementRef.nativeElement;
183
        let childCount = layoutElement.childNodes.length;
184

185
        // if (this._elementsWithExternalReferences) {
186
        //     this._elementsWithExternalReferences.clear();
187
        // }
188

189
        // Remove existing non-element child nodes and SVGs, and add the new SVG element. Note that
190
        // we can't use innerHTML, because IE will throw if the element has a data binding.
191
        while (childCount--) {
192
            const child = layoutElement.childNodes[childCount];
193

194
            // 1 corresponds to Node.ELEMENT_NODE. We remove all non-element nodes in order to get rid
195
            // of any loose text nodes, as well as any SVG elements in order to remove any old icons.
196
            if (child.nodeType !== 1 || child.nodeName.toLowerCase() === 'svg') {
197
                layoutElement.removeChild(child);
198
            }
199
        }
200
    }
201

202
    //#endregion
203

204
    private buildIconNameByType(iconName: string) {
205
        if (this.iconType && ['fill', 'twotone'].indexOf(this.iconType) >= 0) {
206
            const suffix = iconSuffixMap[this.iconType];
207
            return iconName.includes(`-${suffix}`) ? iconName : `${iconName}-${suffix}`;
208
        } else {
209
            return iconName;
210
        }
211
    }
212

213
    /**
214
     * Support Safari SVG LinearGradient.
215
     * @param svg
216
     */
217
    private setBaseUrl(svg: SVGElement) {
218
        const styleElements = svg.querySelectorAll('style');
219
        styleElements.forEach((n: HTMLElement) => {
220
            if (n.style.cssText.includes('url')) {
221
                n.style.fill = n.style.fill.replace('url("', 'url("' + location.pathname);
222
            }
223
            if (n.style.cssText.includes('clip-path')) {
224
                n.style.clipPath = n.style.clipPath.replace('url("', 'url("' + location.pathname);
225
            }
226
        });
227
    }
228

229
    private clearTitleElement(svg: SVGElement) {
230
        const titleElement = svg.querySelector('title');
231
        titleElement && titleElement.remove();
232
    }
233
}
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