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

atinc / ngx-tethys / 3033f133-0f0d-43eb-a07d-e1848354018a

07 Mar 2024 01:58AM UTC coverage: 90.58% (-0.02%) from 90.604%
3033f133-0f0d-43eb-a07d-e1848354018a

Pull #3022

circleci

web-flow
feat(schematics): improve schematics for select and custom-select in template #INFR-11735 (#3047)
Pull Request #3022: feat: upgrade ng to 17 #INFR-11427 (#3021)

5422 of 6642 branches covered (81.63%)

Branch coverage included in aggregate %.

328 of 338 new or added lines in 193 files covered. (97.04%)

141 existing lines in 29 files now uncovered.

13502 of 14250 relevant lines covered (94.75%)

982.04 hits per line

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

93.88
/src/rate/rate.component.ts
1
import { InputBoolean, InputNumber, 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
    OnChanges,
14
    OnInit,
15
    Output,
16
    SimpleChanges,
17
    TemplateRef
1✔
18
} from '@angular/core';
19
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
187✔
20
import { ThyStopPropagationDirective } from 'ngx-tethys/shared';
21
import { ThyTooltipDirective } from 'ngx-tethys/tooltip';
22
import { ThyRateItem } from './rate-item.component';
3✔
23

24
const noop = () => {};
25

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

41✔
52
    private currentValue = 0;
41✔
53

41✔
54
    private hasHalf = false;
55

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

58
    public rateStyleArray: Record<string, boolean>[] = [];
59

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

62
    public iconValue: string = null;
15✔
63

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

66
    private onTouchedCallback: () => void = noop;
67

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

30✔
70
    /**
71
     * 自定义评分的总数
72
     */
73
    @Input() @InputNumber() thyCount = 5;
16✔
74

16✔
75
    /**
1✔
76
     * 是否只读
77
     * @default false
16!
UNCOV
78
     */
×
79
    @Input()
80
    override get thyDisabled(): boolean {
16✔
81
        return this.disabled;
82
    }
83

7!
UNCOV
84
    override set thyDisabled(value: boolean) {
×
85
        this.disabled = coerceBooleanProperty(value);
86
    }
7✔
87

7✔
88
    disabled = false;
7!
89
    /**
7✔
90
     * 是否允许半选
7✔
91
     * @default false
92
     */
93
    @Input() @InputBoolean() thyAllowHalf = false;
4✔
94

1✔
95
    /**
96
     * 是否允许再次点击后清除
3✔
97
     */
3!
98
    @Input() @InputBoolean() thyAllowClear = true;
3✔
99

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

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

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

126
    @HostBinding('class.thy-rate') className = true;
127

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

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

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

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

13✔
151
    ngOnInit() {}
13✔
152

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

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

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

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

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

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

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

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

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

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

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

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