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

atinc / ngx-tethys / 68ef226c-f83e-44c1-b8ed-e420a83c5d84

28 May 2025 10:31AM UTC coverage: 10.352% (-80.0%) from 90.316%
68ef226c-f83e-44c1-b8ed-e420a83c5d84

Pull #3460

circleci

pubuzhixing8
chore: xxx
Pull Request #3460: refactor(icon): migrate signal input #TINFR-1476

132 of 6823 branches covered (1.93%)

Branch coverage included in aggregate %.

10 of 14 new or added lines in 1 file covered. (71.43%)

11648 existing lines in 344 files now uncovered.

2078 of 14525 relevant lines covered (14.31%)

6.69 hits per line

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

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

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

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

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

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

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

66
    public readonly icon: Signal<string> = computed(() => this.thyActionIcon() || this.thyIcon());
UNCOV
67

×
68
    feedback: ThyActionFeedback = null;
69

UNCOV
70
    feedbackOptions: ThyActionFeedbackOptions;
×
UNCOV
71

×
72
    readonly active: Signal<boolean> = computed(() => this.thyActionActive() || this.thyActive());
UNCOV
73

×
UNCOV
74
    private hostRenderer = useHostRenderer();
×
UNCOV
75

×
UNCOV
76
    private feedbackTimer: Subscription;
×
UNCOV
77

×
UNCOV
78
    /**
×
UNCOV
79
     * 操作图标的类型
×
80
     * @type primary | success | danger | warning
UNCOV
81
     */
×
UNCOV
82
    readonly thyType = input<ThyActionType, ThyActionType>('primary', {
×
UNCOV
83
        transform: (value: ThyActionType) => value || 'primary'
×
UNCOV
84
    });
×
85

86
    /**
87
     * 操作图标,支持传参同时也支持在投影中写 thy-icon 组件
88
     */
UNCOV
89
    readonly thyIcon = input<string>('');
×
UNCOV
90

×
UNCOV
91
    /**
×
UNCOV
92
     * 操作图标,当 thyIcon 和其他指令参数名有冲突时使用 thyActionIcon
×
93
     */
UNCOV
94
    readonly thyActionIcon = input<string>('');
×
UNCOV
95

×
96
    /**
97
     * 操作的图标 Active 状态,设置为 true 时会在 Item 上添加 active class
98
     */
99
    readonly thyActive = input(false, { transform: coerceBooleanProperty });
UNCOV
100

×
UNCOV
101
    /**
×
UNCOV
102
     * 操作的图标 Active 状态,当 thyActive 和其他指令参数名有冲突时使用 thyActionActive
×
UNCOV
103
     */
×
104
    readonly thyActionActive = input(false, { transform: coerceBooleanProperty });
UNCOV
105

×
106
    /**
107
     * 操作图标的主题
UNCOV
108
     * @type fill(背景色填充) | lite(简单文本颜色变化)
×
109
     */
110
    readonly thyTheme = input<'fill' | 'lite'>('fill');
1✔
111

1✔
112
    /**
113
     * Hover 展示的图标
114
     */
115
    readonly thyHoverIcon = input<string>();
116

117
    /**
118
     * 是否处于禁用状态
119
     */
120
    readonly thyDisabled = input<boolean, boolean | string | number>(false, { transform: coerceBooleanProperty });
121

122
    ngOnInit(): void {
1✔
123
        this.updateClasses();
124
    }
125

126
    ngAfterViewInit() {
127
        this.wrapSpanForText(this.elementRef.nativeElement.childNodes);
128
    }
129

130
    constructor() {
131
        effect(() => {
132
            this.updateClasses();
133
        });
134
    }
135

136
    setMarginRight(marginRight: string) {
137
        this.elementRef.nativeElement.style.marginRight = marginRight;
138
    }
139

140
    /**
141
     * 触发成功反馈操作
142
     */
143
    success(options?: ThyActionFeedbackOptions) {
144
        this.setFeedback('success', options);
145
    }
146

147
    /**
148
     * 触发失败反馈操作
149
     */
150
    error(options?: ThyActionFeedbackOptions) {
151
        this.setFeedback('error', options);
152
    }
153

154
    private setFeedback(feedback: ThyActionFeedback, options: ThyActionFeedbackOptions) {
155
        if (this.thyDisabled()) {
156
            return;
157
        }
158
        options = Object.assign({}, defaultFeedbackOptions[feedback], options);
159
        this.feedback = feedback;
160
        this.feedbackOptions = options;
161
        this.cdr.markForCheck();
162
        if (options.duration) {
163
            if (this.feedbackTimer) {
164
                this.feedbackTimer.unsubscribe();
165
            }
166
            this.feedbackTimer = timer(options.duration).subscribe(() => {
167
                this.feedback = null;
168
                this.feedbackOptions = null;
169
                this.cdr.markForCheck();
170
            });
171
        }
172
    }
173

174
    private wrapSpanForText(nodes: NodeList): void {
175
        nodes.forEach(node => {
176
            if (node.nodeName === '#text') {
177
                const span = this.renderer.createElement('span');
178
                const parent = this.renderer.parentNode(node);
179
                // this.renderer.addClass(span, 'thy-action-wrap-span');
180
                this.renderer.insertBefore(parent, span, node);
181
                this.renderer.appendChild(span, node);
182
            }
183
        });
184
    }
185

186
    private updateClasses() {
187
        let classNames: string[] = [];
188
        classNames.push(`action-${this.thyType()}`);
189
        if (this.thyTheme() === 'lite') {
190
            classNames.push('thy-action-lite');
191
        }
192
        this.hostRenderer.updateClass(classNames);
193
    }
194

195
    ngOnDestroy(): void {
196
        this.feedbackTimer?.unsubscribe();
197
    }
198
}
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