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

atinc / ngx-tethys / e62d3b10-1466-49c3-aabd-707148681fc8

14 Jun 2024 08:24AM UTC coverage: 90.422%. Remained the same
e62d3b10-1466-49c3-aabd-707148681fc8

push

circleci

minlovehua
feat: use the ngx-tethys/util's coerceBooleanProperty instead of booleanAttribute #INFR-12648

5467 of 6692 branches covered (81.69%)

Branch coverage included in aggregate %.

117 of 120 new or added lines in 66 files covered. (97.5%)

183 existing lines in 46 files now uncovered.

13216 of 13970 relevant lines covered (94.6%)

985.91 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, NgFor } 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
} from '@angular/core';
3✔
20
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
21
import { ThyStopPropagationDirective } from 'ngx-tethys/shared';
22
import { ThyTooltipDirective } from 'ngx-tethys/tooltip';
187✔
23
import { ThyRateItem } from './rate-item.component';
24

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

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

41✔
53
    private currentValue = 0;
41✔
54

55
    private hasHalf = false;
56

10✔
57
    public rateArray: number[] = [];
58

59
    public rateStyleArray: Record<string, boolean>[] = [];
33✔
60

18✔
61
    private icons: string | TemplateRef<any> | string[] | TemplateRef<any>[] = null;
62

15✔
63
    public iconValue: string = null;
15✔
64

15✔
65
    public iconTemplate: TemplateRef<any> = null;
66

67
    private onTouchedCallback: () => void = noop;
30✔
68

30✔
69
    private onChangeCallback: (_: any) => void = noop;
30✔
70

71
    /**
72
     * 自定义评分的总数
73
     */
16✔
74
    @Input({ transform: numberAttribute }) thyCount = 5;
16✔
75

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

7✔
88
    disabled = false;
7!
89

7✔
90
    /**
7✔
91
     * 是否允许半选
92
     * @default false
93
     */
4✔
94
    @Input({ transform: coerceBooleanProperty }) thyAllowHalf = false;
1✔
95

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

1✔
101
    /**
1✔
102
     * 自定义每项的提示信息
1✔
103
     * @type string[]
104
     */
105
    @Input() thyTooltips: string[] = [];
106

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

122
    /**
149✔
123
     * 当前值hover时的回调
124
     */
31✔
125
    @Output() readonly thyItemHoverChange = new EventEmitter<number>();
126

127
    @HostBinding('class.thy-rate') className = true;
43✔
128

43✔
129
    constructor(private cdr: ChangeDetectorRef) {
43✔
130
        super();
209✔
131
    }
209✔
132

406✔
133
    get thyValue(): number {
219✔
134
        return this._value;
219✔
135
    }
136

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

14✔
146
    writeValue(value: number): void {
147
        this.thyValue = value || 0;
148
        this.updateRateArray();
149
        this.cdr.markForCheck();
18✔
150
    }
13✔
151

13✔
152
    ngOnInit() {}
8✔
153

154
    ngOnChanges(changes: SimpleChanges): void {
155
        const { thyCount, thyValue } = changes;
5✔
156
        if (thyCount) {
157
            this.updateRateArray();
158
        }
5!
159

5✔
160
        if (thyValue) {
2✔
161
            this.updateItemStyle();
162
        }
163
        this.cdr.detectChanges();
3✔
164
    }
165

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

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

197
    onRateLeave(event: Event): void {
41✔
198
        event.stopPropagation();
199
        this.hasHalf = !Number.isInteger(this.thyValue);
200
        this.currentValue = Math.ceil(this.thyValue);
201
        this.updateItemStyle();
202
    }
203

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

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

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

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

253
    registerOnChange(fn: any): void {
254
        this.onChangeCallback = fn;
255
    }
256

257
    registerOnTouched(fn: any): void {
258
        this.onTouchedCallback = fn;
259
    }
260

261
    trackByFn(index: number, item: any) {
262
        return item || index;
263
    }
264
}
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