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

atinc / ngx-tethys / c0ef8457-a839-451f-8b72-80fd73106231

02 Apr 2024 02:27PM UTC coverage: 90.524% (-0.06%) from 90.585%
c0ef8457-a839-451f-8b72-80fd73106231

Pull #3062

circleci

minlovehua
refactor(all): use the transform attribute of @Input() instead of @InputBoolean() and @InputNumber()
Pull Request #3062: refactor(all): use the transform attribute of @input() instead of @InputBoolean() and @InputNumber()

4987 of 6108 branches covered (81.65%)

Branch coverage included in aggregate %.

217 of 223 new or added lines in 82 files covered. (97.31%)

202 existing lines in 53 files now uncovered.

12246 of 12929 relevant lines covered (94.72%)

1055.59 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,
1✔
8
    HostBinding,
9
    Input,
10
    OnChanges,
11
    OnInit,
12
    Renderer2,
13
    SimpleChanges,
14
    ViewEncapsulation,
15
    booleanAttribute,
16
    numberAttribute
1✔
17
} from '@angular/core';
18

6,206✔
19
import { getWhetherPrintErrorWhenIconNotFound } from './config';
6,206✔
20
import { ThyIconRegistry } from './icon-registry';
6,206✔
21

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

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

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

51
    @Input('thyTwotoneColor') iconTwotoneColor: string;
1,268✔
52

53
    /**
4,658!
UNCOV
54
     * 图标的名字
×
55
     */
56
    @Input('thyIconName') iconName: string;
57

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

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

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

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

77
    private hostRenderer = useHostRenderer();
78

1,268✔
79
    constructor(private render: Renderer2, private elementRef: ElementRef, private iconRegistry: ThyIconRegistry) {}
1,268✔
80

16✔
81
    ngOnInit() {
82
        this.updateClasses();
1,268✔
83
        this.initialized = true;
1✔
84
    }
1!
85

1✔
86
    ngOnChanges(changes: SimpleChanges) {
2✔
87
        if (this.initialized) {
1✔
88
            if (
89
                changes['iconName'] ||
90
                changes['iconSet'] ||
91
                changes['iconTwotoneColor'] ||
92
                changes['iconType'] ||
93
                changes['iconLinearGradient']
94
            ) {
95
                this.updateClasses();
96
            } else if (changes['iconRotate']) {
97
                this.setStyleRotate();
98
            }
99
        }
100
    }
1,268✔
101

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

6✔
129
    private setStyleRotate() {
130
        if (this.iconRotate !== undefined) {
131
            this.render.setStyle(this.elementRef.nativeElement.querySelector('svg'), 'transform', `rotate(${this.iconRotate}deg)`);
12,236✔
132
        }
133
    }
134

135
    //#region svg element
136

137
    private setSvgElement(svg: SVGElement) {
138
        this.clearSvgElement();
139

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

1!
145
        for (let i = 0; i < styleTags.length; i++) {
×
146
            styleTags[i].textContent += ' ';
147
        }
148

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

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

173
        this.elementRef.nativeElement.appendChild(svg);
174
        this.setStyleRotate();
175
    }
176

177
    private clearSvgElement() {
178
        const layoutElement: HTMLElement = this.elementRef.nativeElement;
179
        let childCount = layoutElement.childNodes.length;
180

181
        // if (this._elementsWithExternalReferences) {
182
        //     this._elementsWithExternalReferences.clear();
183
        // }
184

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

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

198
    //#endregion
199

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

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

225
    private clearTitleElement(svg: SVGElement) {
226
        const titleElement = svg.querySelector('title');
227
        titleElement && titleElement.remove();
228
    }
229
}
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