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

atinc / ngx-tethys / 3b40a702-4b4d-4ddb-81a7-a96baae6d682

08 Nov 2024 05:40AM UTC coverage: 90.395% (-0.04%) from 90.431%
3b40a702-4b4d-4ddb-81a7-a96baae6d682

push

circleci

why520crazy
Merge branch 'master' into feat-theme

5503 of 6730 branches covered (81.77%)

Branch coverage included in aggregate %.

424 of 431 new or added lines in 171 files covered. (98.38%)

344 existing lines in 81 files now uncovered.

13150 of 13905 relevant lines covered (94.57%)

999.86 hits per line

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

93.75
/src/rate/rate.component.ts
1
import { TabIndexDisabledControlValueAccessorMixin } from 'ngx-tethys/core';
2
import { coerceBooleanProperty, helpers } from 'ngx-tethys/util';
3

4
import { NgClass } from '@angular/common';
5
import {
6
    ChangeDetectionStrategy,
7
    ChangeDetectorRef,
8
    Component,
9
    EventEmitter,
10
    forwardRef,
11
    HostBinding,
1✔
12
    Input,
13
    numberAttribute,
14
    OnChanges,
15
    OnInit,
16
    Output,
17
    SimpleChanges,
1✔
18
    TemplateRef,
19
    inject
3✔
20
} from '@angular/core';
21
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
22
import { ThyStopPropagationDirective } from 'ngx-tethys/shared';
187✔
23
import { ThyTooltipDirective } from 'ngx-tethys/tooltip';
24
import { ThyRateItem } from './rate-item.component';
25

6✔
26
const noop = () => {};
6✔
27

2✔
28
/**
2✔
29
 * 评分组件
30
 * @name thy-rate
31
 * @order 10
4✔
32
 */
33
@Component({
34
    selector: 'thy-rate',
35
    templateUrl: './rate.component.html',
41✔
36
    providers: [
41✔
37
        {
41✔
38
            provide: NG_VALUE_ACCESSOR,
41✔
39
            useExisting: forwardRef(() => ThyRate),
41✔
40
            multi: true
41✔
41
        }
41✔
42
    ],
41✔
43
    host: {
41✔
44
        '[attr.tabindex]': `tabIndex`,
41✔
45
        class: 'thy-rate'
41✔
46
    },
41✔
47
    changeDetection: ChangeDetectionStrategy.OnPush,
41✔
48
    standalone: true,
41✔
49
    imports: [ThyStopPropagationDirective, ThyRateItem, NgClass, ThyTooltipDirective]
41✔
50
})
41✔
51
export class ThyRate extends TabIndexDisabledControlValueAccessorMixin implements ControlValueAccessor, OnInit, OnChanges {
41✔
52
    private cdr = inject(ChangeDetectorRef);
41✔
53

41✔
54
    private _value = 0;
55

56
    private currentValue = 0;
10✔
57

58
    private hasHalf = false;
59

33✔
60
    public rateArray: number[] = [];
18✔
61

62
    public rateStyleArray: Record<string, boolean>[] = [];
15✔
63

15✔
64
    private icons: string | TemplateRef<any> | string[] | TemplateRef<any>[] = null;
15✔
65

66
    public iconValue: string = null;
67

30✔
68
    public iconTemplate: TemplateRef<any> = null;
30✔
69

30✔
70
    private onTouchedCallback: () => void = noop;
71

72
    private onChangeCallback: (_: any) => void = noop;
73

16✔
74
    /**
16✔
75
     * 自定义评分的总数
1✔
76
     */
77
    @Input({ transform: numberAttribute }) thyCount = 5;
16!
UNCOV
78

×
79
    /**
80
     * 是否只读
16✔
81
     * @default false
82
     */
83
    @Input({ transform: coerceBooleanProperty })
7!
UNCOV
84
    override set thyDisabled(value: boolean) {
×
85
        this.disabled = value;
86
    }
7✔
87
    override get thyDisabled(): boolean {
7✔
88
        return this.disabled;
7!
89
    }
7✔
90

7✔
91
    disabled = false;
92

93
    /**
4✔
94
     * 是否允许半选
1✔
95
     * @default false
96
     */
3✔
97
    @Input({ transform: coerceBooleanProperty }) thyAllowHalf = false;
3!
98

3✔
99
    /**
1!
100
     * 是否允许再次点击后清除
1✔
101
     */
1✔
102
    @Input({ transform: coerceBooleanProperty }) thyAllowClear = true;
1✔
103

104
    /**
105
     * 自定义每项的提示信息
106
     * @type string[]
2✔
107
     */
2✔
108
    @Input() thyTooltips: string[] = [];
2✔
109

110
    /**
3✔
111
     * 自定义模板,目前支持传单个模板或图标名称、数组(模板 | 图标名称)
112
     * @type string | TemplateRef<any> | string[] | TemplateRef<any>[]
113
     */
2✔
114
    @Input('thyIconTemplate')
2✔
115
    set thyIconTemplate(value: string | TemplateRef<any> | string[] | TemplateRef<any>[]) {
2✔
116
        this.icons = value;
2✔
117
        if (!this.icons) {
118
            this.iconValue = null;
119
            this.iconTemplate = null;
31✔
120
        } else {
121
            this.setIconTemplate();
122
        }
149✔
123
    }
124

31✔
125
    /**
126
     * 当前值hover时的回调
127
     */
43✔
128
    @Output() readonly thyItemHoverChange = new EventEmitter<number>();
43✔
129

43✔
130
    @HostBinding('class.thy-rate') className = true;
209✔
131

209✔
132
    constructor() {
406✔
133
        super();
219✔
134
    }
219✔
135

136
    get thyValue(): number {
137
        return this._value;
138
    }
139

140
    set thyValue(value: number) {
43✔
141
        if (this._value === value) {
29✔
142
            return;
29✔
143
        }
144
        this._value = value;
145
        this.hasHalf = !Number.isInteger(value);
14✔
146
        this.currentValue = Math.ceil(value);
147
    }
148

149
    writeValue(value: number): void {
18✔
150
        this.thyValue = value || 0;
13✔
151
        this.updateRateArray();
13✔
152
        this.cdr.markForCheck();
8✔
153
    }
154

155
    ngOnInit() {}
5✔
156

157
    ngOnChanges(changes: SimpleChanges): void {
158
        const { thyCount, thyValue } = changes;
5!
159
        if (thyCount) {
5✔
160
            this.updateRateArray();
2✔
161
        }
162

163
        if (thyValue) {
3✔
164
            this.updateItemStyle();
165
        }
166
        this.cdr.detectChanges();
167
    }
168

14✔
169
    itemHover(isHalf: boolean, index: number): void {
170
        if (this.thyDisabled || (this.currentValue === index + 1 && this.hasHalf === isHalf)) {
171
            return;
14✔
172
        }
173
        this.currentValue = index + 1;
174
        this.hasHalf = isHalf;
224✔
175
        const _value = isHalf ? Number(this.currentValue - 0.5) : this.currentValue;
176
        this.thyItemHoverChange.emit(_value);
1✔
177
        this.updateItemStyle();
1✔
178
    }
179

180
    itemClick(isHalf: boolean, index: number) {
181
        if (this.thyDisabled) {
182
            return;
183
        }
184
        this.currentValue = index + 1;
185
        const _value = isHalf ? index + 1 - 0.5 : index + 1;
186
        if (this.thyValue === _value) {
187
            if (this.thyAllowClear) {
188
                this.thyValue = 0;
1✔
189
                this.onChangeCallback(this.thyValue);
190
                this.onTouchedCallback();
191
            }
192
        } else {
193
            this.thyValue = _value;
194
            this.onChangeCallback(this.thyValue);
195
            this.onTouchedCallback();
41✔
196
        }
197
        this.updateItemStyle();
198
    }
199

200
    onRateLeave(event: Event): void {
201
        event.stopPropagation();
202
        this.hasHalf = !Number.isInteger(this.thyValue);
203
        this.currentValue = Math.ceil(this.thyValue);
204
        this.updateItemStyle();
205
    }
206

207
    updateRateArray(): void {
208
        this.rateArray = Array(this.thyCount)
209
            .fill(0)
210
            .map((_, i) => {
211
                return i;
212
            });
213
        this.updateItemStyle();
214
    }
215

216
    updateItemStyle(): void {
217
        this.updateIcon();
218
        const rateStyle = 'thy-rate-star';
219
        this.rateStyleArray = this.rateArray.map(i => {
220
            const value = i + 1;
221
            return {
222
                [`${rateStyle}--full`]: value < this.currentValue || (value === this.currentValue && !this.hasHalf),
223
                [`${rateStyle}--half`]: this.hasHalf && value === this.currentValue,
224
                [`${rateStyle}--active`]: this.hasHalf && value === this.currentValue,
225
                [`${rateStyle}--zero`]: value > this.currentValue
226
            };
227
        });
228
    }
229

230
    updateIcon(): void {
231
        if (!this.icons) {
232
            this.iconValue = null;
233
            this.iconTemplate = null;
234
        } else {
235
            this.setIconTemplate();
236
        }
237
    }
238

239
    setIconTemplate(): void {
240
        if (helpers.isArray(this.icons) && this.icons.length > 0) {
241
            const currentIcon = (this.currentValue && this.currentValue - 1) || 0;
242
            if (this.icons[currentIcon] instanceof TemplateRef) {
243
                this.iconTemplate = this.icons[currentIcon] as TemplateRef<any>;
244
            } else {
245
                this.iconValue = this.icons[currentIcon] as string;
246
            }
247
        } else if (!helpers.isArray(this.icons)) {
248
            if (this.icons instanceof TemplateRef) {
249
                this.iconTemplate = this.icons;
250
            } else {
251
                this.iconValue = this.icons;
252
            }
253
        }
254
    }
255

256
    registerOnChange(fn: any): void {
257
        this.onChangeCallback = fn;
258
    }
259

260
    registerOnTouched(fn: any): void {
261
        this.onTouchedCallback = fn;
262
    }
263

264
    trackByFn(index: number, item: any) {
265
        return item || index;
266
    }
267
}
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