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

atinc / ngx-tethys / #55

30 Jul 2025 07:08AM UTC coverage: 9.866% (-80.4%) from 90.297%
#55

push

why520crazy
feat(empty): add setMessage for update display text #TINFR-2616

92 of 6794 branches covered (1.35%)

Branch coverage included in aggregate %.

2014 of 14552 relevant lines covered (13.84%)

6.15 hits per line

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

4.88
/src/button/button.component.ts
1
import {
2
    ChangeDetectionStrategy,
3
    Component,
4
    ElementRef,
5
    Renderer2,
6
    ViewEncapsulation,
7
    inject,
8
    input,
9
    computed,
1✔
10
    effect,
11
    afterNextRender
12
} from '@angular/core';
13

14
import { assertIconOnly, coerceBooleanProperty, ThyBooleanInput } from 'ngx-tethys/util';
15
import { useHostRenderer } from '@tethys/cdk/dom';
16
import { ThyIcon } from 'ngx-tethys/icon';
17
import { NgClass } from '@angular/common';
18

19
export type ThyButtonType =
20
    | 'primary'
21
    | 'secondary'
22
    | 'info'
23
    | 'outline-primary'
24
    | 'outline-default'
1✔
25
    | 'danger'
26
    | 'link'
27
    | 'link-secondary'
28
    | 'warning'
29
    | 'outline-warning'
30
    | 'success'
1✔
31
    | 'outline-success'
32
    | 'outline-info'
×
33
    | 'outline-danger'
34
    | 'link-danger-weak'
35
    | 'link-danger'
×
36
    | 'link-success';
×
37

×
38
const btnTypeClassesMap: Record<string, string[]> = {
×
39
    primary: ['btn-primary'],
40
    secondary: ['btn-primary', 'btn-md'],
41
    info: ['btn-info'],
42
    warning: ['btn-warning'],
×
43
    danger: ['btn-danger'],
×
44
    'outline-primary': ['btn-outline-primary'],
×
45
    'outline-default': ['btn-outline-default'],
46
    link: ['btn-link'], // 链接按钮
×
47
    'link-info': ['btn-link', 'btn-link-info'], // 幽灵链接按钮
×
48
    'link-secondary': ['btn-link', 'btn-link-primary-weak'], // 幽灵链接按钮
×
49
    'link-danger-weak': ['btn-link', 'btn-link-danger-weak'], // 幽灵危险按钮
50
    'link-danger': ['btn-link', 'btn-link-danger'], // 危险按钮
51
    'link-success': ['btn-link', 'btn-link-success'] // 成功按钮
×
52
};
×
53

54
const iconOnlyClass = 'thy-btn-icon-only';
55

×
56
/**
×
57
 * 操作按钮,支持组件`thy-button`和`thyButton`指令两种形式
×
58
 * @name thy-button,[thy-button],[thyButton]
59
 * @order 10
×
60
 */
×
61
@Component({
62
    selector: 'thy-button,[thy-button],[thyButton]',
×
63
    templateUrl: './button.component.html',
×
64
    encapsulation: ViewEncapsulation.None,
×
65
    changeDetection: ChangeDetectionStrategy.OnPush,
66
    host: {
×
67
        class: 'thy-btn btn',
68
        '[class.btn-block]': 'thyBlock()'
69
    },
×
70
    imports: [ThyIcon, NgClass]
×
71
})
×
72
export class ThyButton {
×
73
    private elementRef = inject(ElementRef);
×
74
    private renderer = inject(Renderer2);
×
75

76
    private _originalText: string;
×
77

×
78
    private get nativeElement(): HTMLElement {
×
79
        return this.elementRef.nativeElement;
80
    }
×
81

82
    private hostRenderer = useHostRenderer();
83

×
84
    /**
×
85
     * 按钮类型,支持添加前缀`outline-`实现线框按钮,支持添加前缀`link-`实现按钮链接
×
86
     * @type primary | info | warning | danger | success
×
87
     * @default primary
×
88
     */
×
89
    readonly thyButton = input<ThyButtonType>();
×
90

91
    /**
×
92
     * 和`thyButton`参数一样,一般使用`thyButton`,为了减少参数输入, 当通过`thy-button`使用时,只能使用该参数控制类型
×
93
     * @default primary
×
94
     */
95
    readonly thyType = input<ThyButtonType>();
×
96

97
    /**
×
98
     * 加载状态
×
99
     * @default false
×
100
     */
×
101
    readonly thyLoading = input<boolean, ThyBooleanInput>(false, {
×
102
        transform: value => {
×
103
            if (!this.thyLoading() && value) {
104
                const textElement = this.nativeElement?.querySelector('span');
×
105
                this._originalText = textElement ? textElement.innerText : '';
106
            }
×
107
            return coerceBooleanProperty(value);
108
        }
×
109
    });
×
110

111
    /**
×
112
     * 加载状态时显示的文案
×
113
     */
×
114
    readonly thyLoadingText = input<string>();
115

×
116
    /**
×
117
     * 按钮大小
×
118
     * @type xs | sm | md | default | lg
×
119
     * @default default
120
     */
121
    readonly thySize = input<string>();
×
122

123
    /**
124
     * 按钮中显示的图标,支持SVG图标名称,比如`angle-left`,也支持传之前的 wtf 字体,比如: wtf-plus
×
125
     */
×
126
    readonly thyIcon = input<string>();
127

×
128
    /**
×
129
     * 按钮整块展示
130
     * @default false
×
131
     */
×
132
    readonly thyBlock = input<boolean, ThyBooleanInput>(false, { transform: coerceBooleanProperty });
×
133

134
    private isWtfIcon = computed(() => {
135
        const icon = this.thyIcon();
×
136
        return icon && icon.includes('wtf');
137
    });
×
138

139
    protected svgIconName = computed(() => {
140
        if (!this.isWtfIcon()) {
141
            return this.thyIcon();
×
142
        }
×
143
        return null;
×
144
    });
×
145

×
146
    protected iconClass = computed<string[]>(() => {
×
147
        const icon = this.thyIcon();
×
148
        if (this.isWtfIcon()) {
149
            const classes = icon.split(' ');
150
            if (classes.length === 1) {
151
                classes.unshift('wtf');
1✔
152
            }
1✔
153
            return classes;
154
        }
155
        return null;
156
    });
157

158
    private buttonType = computed(() => {
159
        return this.thyButton() || this.thyType();
160
    });
161

162
    protected isRadiusSquare = computed(() => {
1✔
163
        const type = this.buttonType();
164
        return !!type?.includes('-square');
165
    });
166

167
    protected type = computed(() => {
168
        const type = this.buttonType();
169
        if (this.isRadiusSquare()) {
170
            return type.replace('-square', '');
171
        } else {
172
            return type;
173
        }
174
    });
175

176
    private setButtonText() {
177
        const text = this.thyLoading() ? this.thyLoadingText() : this._originalText;
178
        const spanElement = this.nativeElement.querySelector('span');
179
        if (spanElement && text) {
180
            this.renderer.setProperty(spanElement, 'innerText', text);
181
        }
182
    }
183

184
    private updateClasses() {
185
        const type = this.type();
186
        if (!type) {
187
            return;
188
        }
189

190
        let classNames: string[] = [];
191
        if (btnTypeClassesMap[type]) {
192
            classNames = [...btnTypeClassesMap[type]];
193
        } else {
194
            if (type) {
195
                classNames.push(`btn-${type}`);
196
            }
197
        }
198

199
        const size = this.thySize();
200
        if (size) {
201
            classNames.push(`btn-${size}`);
202
        }
203
        if (this.isRadiusSquare()) {
204
            classNames.push('btn-square');
205
        }
206
        const loading = this.thyLoading();
207
        if (loading) {
208
            classNames.push('loading');
209
        }
210
        this.hostRenderer.updateClass(classNames);
211
    }
212

213
    constructor() {
214
        effect(() => {
215
            this.updateClasses();
216
        });
217

218
        effect(() => {
219
            this.setButtonText();
220
        });
221

222
        afterNextRender(() => {
223
            if (assertIconOnly(this.nativeElement)) {
224
                this.hostRenderer.addClass(iconOnlyClass);
225
            } else {
226
                this.hostRenderer.removeClass(iconOnlyClass);
227
            }
228
            this.wrapSpanForText(this.nativeElement.childNodes);
229
        });
230
    }
231

232
    private wrapSpanForText(nodes: NodeList): void {
233
        nodes.forEach(node => {
234
            if (node.nodeName === '#text') {
235
                const span = this.renderer.createElement('span');
236
                const parent = this.renderer.parentNode(node);
237
                this.renderer.addClass(span, 'thy-btn-wrap-span');
238
                this.renderer.insertBefore(parent, span, node);
239
                this.renderer.appendChild(span, node);
240
            }
241
        });
242
    }
243
}
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