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

atinc / ngx-tethys / cd64db52-e563-41a3-85f3-a0adb87ce135

30 Oct 2024 08:03AM UTC coverage: 90.402% (-0.04%) from 90.438%
cd64db52-e563-41a3-85f3-a0adb87ce135

push

circleci

web-flow
refactor: refactor constructor to the inject function (#3222)

5503 of 6730 branches covered (81.77%)

Branch coverage included in aggregate %.

422 of 429 new or added lines in 170 files covered. (98.37%)

344 existing lines in 81 files now uncovered.

13184 of 13941 relevant lines covered (94.57%)

997.19 hits per line

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

92.65
/src/action/action.component.ts
1
import {
2
    ChangeDetectionStrategy,
3
    Component,
4
    Input,
5
    OnInit,
6
    AfterViewInit,
7
    OnChanges,
8
    ElementRef,
1✔
9
    Renderer2,
10
    SimpleChanges,
11
    ChangeDetectorRef,
12
    OnDestroy,
13
    inject
14
} from '@angular/core';
15
import { useHostRenderer } from '@tethys/cdk/dom';
16
import { ThyIcon } from 'ngx-tethys/icon';
17

18
import { Subscription, timer } from 'rxjs';
19
import { coerceBooleanProperty } from 'ngx-tethys/util';
20

21
export type ThyActionType = 'primary' | 'success' | 'danger' | 'warning';
22

23
export type ThyActionFeedback = 'success' | 'error';
24

1✔
25
export interface ThyActionFeedbackOptions {
26
    icon?: string;
434✔
27
    class?: string;
434✔
28
    duration?: number;
434✔
29
}
434✔
30

434✔
31
const defaultFeedbackOptions: Record<ThyActionFeedback, ThyActionFeedbackOptions> = {
434✔
32
    success: {
434✔
33
        icon: 'check-circle-fill',
434✔
34
        class: 'text-success',
35
        duration: 3000
36
    },
18!
37
    error: {
38
        icon: 'close-circle-fill',
39
        class: 'text-danger',
201✔
40
        duration: 3000
41
    }
42
};
231✔
43
/**
44
 * 立即操作组件
45
 * @name thy-action,[thyAction]
9✔
46
 */
47
@Component({
UNCOV
48
    selector: 'thy-action, [thyAction]',
×
49
    templateUrl: './action.component.html',
50
    changeDetection: ChangeDetectionStrategy.OnPush,
51
    host: {
434✔
52
        class: 'thy-action',
53
        '[class.active]': 'active',
54
        '[class.thy-action-hover-icon]': 'thyHoverIcon',
434✔
55
        '[class.thy-action-has-feedback]': '!!feedback',
56
        '[class.disabled]': 'thyDisabled'
57
    },
434!
UNCOV
58
    standalone: true,
×
59
    imports: [ThyIcon]
60
})
61
export class ThyAction implements OnInit, AfterViewInit, OnChanges, OnDestroy {
62
    private elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
154✔
63
    private renderer = inject(Renderer2);
64
    private cdr = inject(ChangeDetectorRef);
65

66
    icon: string;
67

68
    feedback: ThyActionFeedback = null;
2✔
69

70
    feedbackOptions: ThyActionFeedbackOptions;
71

72
    active = false;
73

74
    private type: string = 'primary';
1✔
75

76
    private hostRenderer = useHostRenderer();
77

3✔
78
    private feedbackTimer: Subscription;
1✔
79

80
    /**
2✔
81
     * 操作图标的类型
2✔
82
     * @type primary | success | danger | warning
2✔
83
     * @default primary
2✔
84
     */
2!
85
    @Input()
2✔
86
    set thyType(value: ThyActionType) {
1✔
87
        this.setActionType(value || 'primary');
88
    }
2✔
89

1✔
90
    /**
1✔
91
     * 操作图标,支持传参同时也支持在投影中写 thy-icon 组件
1✔
92
     */
93
    @Input()
94
    set thyIcon(icon: string) {
95
        this.icon = icon;
96
    }
434✔
97

1,763✔
98
    /**
9✔
99
     * 操作图标,当 thyIcon 和其他指令参数名有冲突时使用 thyActionIcon
9✔
100
     */
101
    @Input()
9✔
102
    set thyActionIcon(icon: string) {
9✔
103
        this.icon = icon;
104
    }
105

106
    /**
107
     * 操作的图标 Active 状态,设置为 true 时会在 Item 上添加 active class
18✔
108
     * @default false
109
     */
110
    @Input({ transform: coerceBooleanProperty })
434✔
111
    set thyActive(value: boolean) {
434✔
112
        this.active = value;
434✔
113
    }
9✔
114

115
    /**
434✔
116
     * 操作的图标 Active 状态,当 thyActive 和其他指令参数名有冲突时使用 thyActionActive
117
     * @default false
118
     */
434✔
119
    @Input({ transform: coerceBooleanProperty })
120
    set thyActionActive(value: boolean) {
1✔
121
        this.active = value;
122
    }
123

124
    /**
125
     * 操作图标的主题
126
     * @type fill(背景色填充) | lite(简单文本颜色变化)
127
     */
128
    @Input() thyTheme: 'fill' | 'lite' = 'fill';
129

130
    /**
131
     * Hover 展示的图标
1✔
132
     */
133
    @Input() thyHoverIcon: string;
134

135
    /**
136
     * 是否处于禁用状态
137
     * @default false
138
     */
139
    @Input({ transform: coerceBooleanProperty })
140
    thyDisabled: boolean;
141

142
    ngOnInit(): void {
143
        this.updateClasses();
144
    }
145

146
    ngAfterViewInit() {
147
        this.wrapSpanForText(this.elementRef.nativeElement.childNodes);
148
    }
149

150
    ngOnChanges(changes: SimpleChanges): void {
151
        if ((changes.thyType && !changes.thyType.firstChange) || (changes.thyTheme && !changes.thyTheme.firstChange)) {
152
            this.updateClasses();
153
        }
154
    }
155

156
    setMarginRight(marginRight: string) {
157
        this.elementRef.nativeElement.style.marginRight = marginRight;
158
    }
159

160
    /**
161
     * 触发成功反馈操作
162
     */
163
    success(options?: ThyActionFeedbackOptions) {
164
        this.setFeedback('success', options);
165
    }
166

167
    /**
168
     * 触发失败反馈操作
169
     */
170
    error(options?: ThyActionFeedbackOptions) {
171
        this.setFeedback('error', options);
172
    }
173

174
    private setFeedback(feedback: ThyActionFeedback, options: ThyActionFeedbackOptions) {
175
        if (this.thyDisabled) {
176
            return;
177
        }
178
        options = Object.assign({}, defaultFeedbackOptions[feedback], options);
179
        this.feedback = feedback;
180
        this.feedbackOptions = options;
181
        this.cdr.markForCheck();
182
        if (options.duration) {
183
            if (this.feedbackTimer) {
184
                this.feedbackTimer.unsubscribe();
185
            }
186
            this.feedbackTimer = timer(options.duration).subscribe(() => {
187
                this.feedback = null;
188
                this.feedbackOptions = null;
189
                this.cdr.markForCheck();
190
            });
191
        }
192
    }
193

194
    private wrapSpanForText(nodes: NodeList): void {
195
        nodes.forEach(node => {
196
            if (node.nodeName === '#text') {
197
                const span = this.renderer.createElement('span');
198
                const parent = this.renderer.parentNode(node);
199
                // this.renderer.addClass(span, 'thy-action-wrap-span');
200
                this.renderer.insertBefore(parent, span, node);
201
                this.renderer.appendChild(span, node);
202
            }
203
        });
204
    }
205

206
    private setActionType(value: ThyActionType) {
207
        this.type = value;
208
    }
209

210
    private updateClasses() {
211
        let classNames: string[] = [];
212
        classNames.push(`action-${this.type}`);
213
        if (this.thyTheme === 'lite') {
214
            classNames.push('thy-action-lite');
215
        }
216
        this.hostRenderer.updateClass(classNames);
217
    }
218

219
    ngOnDestroy(): void {
220
        this.feedbackTimer?.unsubscribe();
221
    }
222
}
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