• 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

1.13
/src/date-picker/lib/date-carousel/date-carousel.component.ts
1
import { NgClass, NgTemplateOutlet } from '@angular/common';
2
import {
3
    ChangeDetectionStrategy,
4
    ChangeDetectorRef,
5
    Component,
6
    forwardRef,
7
    HostBinding,
8
    inject,
9
    Input,
10
    OnDestroy,
11
    OnInit,
12
    Signal
13
} from '@angular/core';
14
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
15
import { ThyButton } from 'ngx-tethys/button';
16
import { injectLocale, ThyDatePickerLocale } from 'ngx-tethys/i18n';
1✔
17
import { ThyIcon } from 'ngx-tethys/icon';
UNCOV
18
import { TinyDate } from 'ngx-tethys/util';
×
UNCOV
19
import { Subject } from 'rxjs';
×
UNCOV
20
import { DateHelperService } from '../../date-helper.service';
×
UNCOV
21
import { AdvancedSelectableCell, RangeAdvancedValue } from '../../inner-types';
×
UNCOV
22
import { DatePickerAdvancedShowYearTipPipe } from '../../picker.pipes';
×
UNCOV
23
import { ThyDateGranularity } from '../../standard-types';
×
UNCOV
24

×
UNCOV
25
/**
×
26
 * @private
27
 */
UNCOV
28
@Component({
×
UNCOV
29
    // eslint-disable-next-line @angular-eslint/component-selector
×
UNCOV
30
    selector: 'date-carousel',
×
UNCOV
31
    templateUrl: './date-carousel.component.html',
×
UNCOV
32
    changeDetection: ChangeDetectionStrategy.OnPush,
×
33
    providers: [
34
        {
UNCOV
35
            provide: NG_VALUE_ACCESSOR,
×
36
            multi: true,
UNCOV
37
            useExisting: forwardRef(() => DateCarousel)
×
38
        }
39
    ],
UNCOV
40
    imports: [NgTemplateOutlet, ThyButton, ThyIcon, NgClass, DatePickerAdvancedShowYearTipPipe]
×
UNCOV
41
})
×
UNCOV
42
export class DateCarousel implements OnInit, ControlValueAccessor, OnDestroy {
×
43
    private cdr = inject(ChangeDetectorRef);
UNCOV
44
    private dateHelper = inject(DateHelperService);
×
UNCOV
45
    locale: Signal<ThyDatePickerLocale> = injectLocale('datePicker');
×
UNCOV
46

×
UNCOV
47
    @HostBinding('class') className = 'thy-date-picker-advanced-carousel';
×
UNCOV
48

×
UNCOV
49
    @Input() activeDate: TinyDate;
×
50

51
    set defaultValue(value: RangeAdvancedValue) {
52
        this.dateGranularity = value.dateGranularity;
53
        this.buildSelectableData(value.begin);
54

55
        if (value.begin && value.end) {
UNCOV
56
            const shouldBeSelectValue = this.getShouldBeToggleValue(value.begin, value.end);
×
UNCOV
57
            this.select(...shouldBeSelectValue);
×
58
        } else {
59
            this.clearSelect(true);
60
        }
61
        this.initialized = true;
62
    }
63

64
    selectableData: { year?: AdvancedSelectableCell[]; quarter?: AdvancedSelectableCell[]; month?: AdvancedSelectableCell[] } = {};
65

66
    dateGranularity: ThyDateGranularity;
UNCOV
67

×
UNCOV
68
    selectedValue: AdvancedSelectableCell[] = [];
×
69

70
    private initialized = false;
71

UNCOV
72
    private selectedValueChange$ = new Subject<RangeAdvancedValue>();
×
73

74
    private _onChange: (value: RangeAdvancedValue) => void;
UNCOV
75

×
76
    private _onTouched: (value: RangeAdvancedValue) => void;
77

UNCOV
78
    ngOnInit(): void {
×
79
        this.selectedValueChange$.subscribe(() => {
80
            if (this.selectedValue.length) {
81
                this.buildSelectableData(this.selectedValue[0]?.startValue, this.dateGranularity);
82
            }
83
            this.selectableData.year.forEach(item => (item.classMap = this.getClassMap(item)));
84
            this.selectableData.quarter.forEach(item => (item.classMap = this.getClassMap(item)));
85
            this.selectableData.month.forEach(item => (item.classMap = this.getClassMap(item)));
86
            if (this.initialized) {
UNCOV
87
                if (this.isSelectEmpty()) {
×
88
                    this._onChange({
89
                        dateGranularity: null,
UNCOV
90
                        begin: null,
×
UNCOV
91
                        end: null
×
92
                    });
93
                } else {
UNCOV
94
                    const selctedValue = this.selectedValue;
×
UNCOV
95
                    this._onChange({
×
96
                        dateGranularity: this.dateGranularity,
97
                        begin: selctedValue[0]?.startValue,
UNCOV
98
                        end: selctedValue[selctedValue.length - 1]?.endValue
×
UNCOV
99
                    });
×
UNCOV
100
                }
×
101
            }
102
        });
103
    }
104

UNCOV
105
    writeValue(value: RangeAdvancedValue): void {
×
106
        if (value) {
107
            this.defaultValue = value;
UNCOV
108
        }
×
109
    }
110

UNCOV
111
    registerOnChange(fn: any): void {
×
112
        this._onChange = fn;
113
    }
UNCOV
114

×
UNCOV
115
    registerOnTouched(fn: any): void {
×
UNCOV
116
        this._onTouched = fn;
×
117
    }
118

UNCOV
119
    getClassMap(cell: AdvancedSelectableCell) {
×
UNCOV
120
        return {
×
121
            [`active`]: this.isSelected(cell),
122
            [`indeterminate`]: this.isCellIndeterminate(this.selectedValue, cell),
UNCOV
123
            [`type-active`]: this.isTypeActive(this.selectedValue, cell),
×
UNCOV
124
            ['in-hover-range']: cell.isInRange,
×
125
            ['out-range']: cell.isOutRange
UNCOV
126
        };
×
UNCOV
127
    }
×
128

129
    isTypeActive(originalValue: AdvancedSelectableCell[], value: AdvancedSelectableCell) {
UNCOV
130
        return originalValue?.length && originalValue[0].type === value.type;
×
UNCOV
131
    }
×
132

×
133
    isCellIndeterminate(originalValue: AdvancedSelectableCell[], value: AdvancedSelectableCell) {
134
        if (originalValue[0]?.type === value.type) {
135
            return false;
UNCOV
136
        } else {
×
UNCOV
137
            if (originalValue[0]?.type === 'year') {
×
138
                return !!originalValue.find(item => item.startValue.isSameYear(value.startValue));
UNCOV
139
            } else {
×
UNCOV
140
                return value.type === 'year'
×
141
                    ? !!originalValue.find(item => item.startValue.isSameYear(value.startValue))
×
142
                    : !!originalValue.find(item => item.startValue.isSameQuarter(value.startValue));
143
            }
UNCOV
144
        }
×
UNCOV
145
    }
×
UNCOV
146

×
UNCOV
147
    isSelected(value: AdvancedSelectableCell) {
×
148
        return this.selectedValue.find(item => item.startValue.isSameDay(value.startValue)) && this.dateGranularity === value.type;
149
    }
UNCOV
150

×
151
    isSelectEmpty() {
UNCOV
152
        return this.selectedValue.length == 0;
×
UNCOV
153
    }
×
154

×
155
    selectSort() {
156
        this.selectedValue.sort((a, b) => a.startValue.getTime() - b.startValue.getTime());
UNCOV
157
    }
×
UNCOV
158

×
UNCOV
159
    select(...value: AdvancedSelectableCell[]) {
×
UNCOV
160
        value.forEach(item => {
×
161
            if (!this.isSelected(item)) {
162
                this.selectedValue.push(...value);
UNCOV
163
            }
×
164
        });
UNCOV
165
        this.selectSort();
×
UNCOV
166
        this.selectedValueChange$.next(undefined);
×
167
    }
×
168

169
    deselect(...value: AdvancedSelectableCell[]) {
UNCOV
170
        value.forEach(item => {
×
UNCOV
171
            this.selectedValue = this.selectedValue.filter(selected => !selected.startValue.isSameDay(item.startValue));
×
UNCOV
172
        });
×
UNCOV
173
        this.selectSort();
×
174
        this.selectedValueChange$.next(undefined);
175
    }
176

UNCOV
177
    clearSelect(hidden?: boolean) {
×
178
        this.selectedValue = [];
179
        if (!hidden) {
UNCOV
180
            this.selectedValueChange$.next(undefined);
×
UNCOV
181
        }
×
UNCOV
182
    }
×
183

UNCOV
184
    getShouldBeToggleValue(begin: TinyDate, end: TinyDate) {
×
UNCOV
185
        let selectedValue: AdvancedSelectableCell[] = [];
×
186
        switch (this.dateGranularity) {
UNCOV
187
            case 'year':
×
188
                this.dateGranularity = 'year';
UNCOV
189
                if (begin.isSameYear(end)) {
×
UNCOV
190
                    selectedValue.push(this.getSelectableYear(begin));
×
191
                } else {
UNCOV
192
                    selectedValue.push(this.getSelectableYear(begin));
×
193
                    while (!begin.isSameYear(end)) {
UNCOV
194
                        begin = begin.addYears(1);
×
UNCOV
195
                        selectedValue.push(this.getSelectableYear(begin));
×
196
                    }
UNCOV
197
                }
×
198
                break;
199
            case 'month':
UNCOV
200
                this.dateGranularity = 'month';
×
201
                if (begin.isSameMonth(end)) {
202
                    selectedValue.push(this.getSelectableMonth(begin));
×
UNCOV
203
                } else {
×
UNCOV
204
                    selectedValue.push(this.getSelectableMonth(begin));
×
205
                    while (!begin.isSameMonth(end)) {
206
                        begin = begin.addMonths(1);
207
                        selectedValue.push(this.getSelectableMonth(begin));
208
                    }
209
                }
210
                break;
211
            case 'quarter':
212
                this.dateGranularity = 'quarter';
×
UNCOV
213
                if (begin.isSameQuarter(end)) {
×
UNCOV
214
                    selectedValue.push(this.getSelectableQuarter(begin));
×
215
                } else {
216
                    selectedValue.push(this.getSelectableQuarter(begin));
217
                    while (!begin.isSameQuarter(end)) {
218
                        begin = begin.addQuarters(1);
219
                        selectedValue.push(this.getSelectableQuarter(begin));
220
                    }
221
                }
222
        }
×
UNCOV
223
        return selectedValue;
×
224
    }
UNCOV
225

×
226
    buildSelectableData(startDate: TinyDate, excludeGranularity?: ThyDateGranularity) {
227
        const buildGranularity = ['year', 'month', 'quarter'].filter(item => item !== excludeGranularity);
228
        buildGranularity.forEach(granularity => {
229
            switch (granularity) {
230
                case 'year':
231
                    this.selectableData.year = [...Array(3).keys()].map((item, index) => {
UNCOV
232
                        return this.getSelectableYear(startDate, index);
×
233
                    });
234
                    break;
UNCOV
235
                case 'quarter':
×
236
                    this.selectableData.quarter = [...Array(4).keys()].map((item, index) => {
UNCOV
237
                        return this.getSelectableQuarter(startDate, index);
×
UNCOV
238
                    });
×
239
                    break;
UNCOV
240
                case 'month':
×
UNCOV
241
                    this.selectableData.month = [...Array(4).keys()].map((item, index) => {
×
242
                        return this.getSelectableMonth(startDate, index);
UNCOV
243
                    });
×
244
                    break;
UNCOV
245
            }
×
246
        });
247
        this.cdr.markForCheck();
UNCOV
248
    }
×
249

UNCOV
250
    getSelectableYear(currentDate: TinyDate, preOrNextcount: number = 0): AdvancedSelectableCell {
×
UNCOV
251
        currentDate = currentDate || this.activeDate || new TinyDate().startOfYear();
×
252
        return {
UNCOV
253
            type: 'year',
×
UNCOV
254
            content: `${currentDate.addYears(preOrNextcount).getYear()}`,
×
255
            startValue: currentDate.startOfYear().addYears(preOrNextcount),
UNCOV
256
            endValue: currentDate.endOfYear().addYears(preOrNextcount),
×
257
            classMap: {}
UNCOV
258
        };
×
259
    }
260

UNCOV
261
    getSelectableQuarter(currentDate: TinyDate, preOrNextcount: number = 0): AdvancedSelectableCell {
×
UNCOV
262
        currentDate = currentDate || this.activeDate || new TinyDate().startOfQuarter();
×
UNCOV
263
        return {
×
264
            type: 'quarter',
UNCOV
265
            content: `${currentDate.addQuarters(preOrNextcount).format('qqq')}`,
×
UNCOV
266
            startValue: currentDate.startOfQuarter().addQuarters(preOrNextcount),
×
UNCOV
267
            endValue: currentDate.endOfQuarter().addQuarters(preOrNextcount),
×
268
            classMap: {}
UNCOV
269
        };
×
270
    }
UNCOV
271

×
UNCOV
272
    getSelectableMonth(currentDate: TinyDate, preOrNextcount: number = 0): AdvancedSelectableCell {
×
UNCOV
273
        currentDate = currentDate || this.activeDate || new TinyDate().startOfMonth();
×
UNCOV
274
        // Selectable months for advanced range selector
×
275
        const cell: AdvancedSelectableCell = {
UNCOV
276
            type: 'month',
×
277
            content: this.dateHelper.format(currentDate.addMonths(preOrNextcount).nativeDate, this.locale().monthFormat),
UNCOV
278
            startValue: currentDate.startOfMonth().addMonths(preOrNextcount),
×
UNCOV
279
            endValue: currentDate.endOfMonth().addMonths(preOrNextcount),
×
UNCOV
280
            classMap: {}
×
UNCOV
281
        };
×
282
        return cell;
283
    }
284

UNCOV
285
    prevClick(type: ThyDateGranularity) {
×
UNCOV
286
        switch (type) {
×
UNCOV
287
            case 'year':
×
288
                this.selectableData.year = this.selectableData.year.map(item => this.getSelectableYear(item.startValue, -1));
289
                break;
290
            case 'quarter':
291
                this.selectableData.quarter = this.selectableData.quarter.map(item => this.getSelectableQuarter(item.startValue, -2));
UNCOV
292
                break;
×
293
            case 'month':
UNCOV
294
                this.selectableData.month = this.selectableData.month.map(item => this.getSelectableMonth(item.startValue, -2));
×
295
        }
296
        this.selectableData[type].forEach(item => (item.classMap = this.getClassMap(item)));
297
    }
298

×
299
    nextClick(type: ThyDateGranularity) {
×
300
        switch (type) {
×
301
            case 'year':
×
302
                this.selectableData.year = this.selectableData.year.map(item => this.getSelectableYear(item.startValue, 1));
303
                break;
304
            case 'quarter':
UNCOV
305
                this.selectableData.quarter = this.selectableData.quarter.map(item => this.getSelectableQuarter(item.startValue, 2));
×
306
                break;
×
307
            case 'month':
UNCOV
308
                this.selectableData.month = this.selectableData.month.map(item => this.getSelectableMonth(item.startValue, 2));
×
UNCOV
309
        }
×
UNCOV
310
        this.selectableData[type].forEach(item => (item.classMap = this.getClassMap(item)));
×
311
    }
×
312

313
    selectDate(type: ThyDateGranularity, value: AdvancedSelectableCell) {
UNCOV
314
        this.selectableData[type].forEach(item => {
×
UNCOV
315
            item.isInRange = false;
×
UNCOV
316
            item.isOutRange = false;
×
UNCOV
317
        });
×
UNCOV
318

×
319
        if (this.isSelectEmpty()) {
320
            this.dateGranularity = type;
UNCOV
321
            this.select(value);
×
322
            // this.selectedValueChange$.next();
323
            return;
324
        }
325
        if (this.isSelected(value)) {
326
            this.toggleSelect(value);
327
            if (this.isSelectEmpty()) {
×
328
                this.dateGranularity = null;
×
329
            }
×
330
            return;
×
331
        }
332

333
        if (this.dateGranularity === value.type) {
×
334
            const { rangeStart, rangeEnd } = this.getActualStartAndEnd(value);
335
            const shouldBeSelectValue = this.getShouldBeToggleValue(rangeStart, rangeEnd);
336
            this.select(...shouldBeSelectValue);
UNCOV
337
            // this.selectedValueChange$.next();
×
338
        } else {
339
            this.dateGranularity = type;
UNCOV
340
            this.clearSelect(true);
×
UNCOV
341
            this.select(value);
×
342
            // this.selectedValueChange$.next();
UNCOV
343
        }
×
UNCOV
344
    }
×
345
    toggleSelect(value: AdvancedSelectableCell) {
UNCOV
346
        if (value.startValue.isSameDay(this.selectedValue[0].startValue)) {
×
347
            // only deselect first one
348
            this.deselect(value);
UNCOV
349
        } else {
×
UNCOV
350
            // deselect current and all after current
×
351
            const rangeStart = value.startValue;
UNCOV
352
            const rangeEnd = this.selectedValue[this.selectedValue.length - 1].endValue;
×
353
            const shouldBeDeselectValue = this.getShouldBeToggleValue(rangeStart, rangeEnd);
×
354
            this.deselect(...shouldBeDeselectValue);
×
355
        }
UNCOV
356
    }
×
UNCOV
357

×
UNCOV
358
    onMouseenter(event: Event, type: ThyDateGranularity, value: AdvancedSelectableCell) {
×
359
        if (this.isSelectEmpty() || this.dateGranularity !== type) {
UNCOV
360
            return;
×
361
        }
362
        if (this.isSelected(value)) {
UNCOV
363
            value.isInRange = true;
×
364
            if (value.startValue.isSameDay(this.selectedValue[0].startValue)) {
365
                value.isOutRange = true;
1✔
366
            } else {
367
                const rangeStart = value.startValue;
368
                const rangeEnd = this.selectedValue[this.selectedValue.length - 1].endValue;
369
                this.selectableData[type].forEach((item: AdvancedSelectableCell) => {
370
                    if (item.startValue.getTime() >= rangeStart.getTime() && item.startValue.getTime() < rangeEnd.getTime()) {
1✔
371
                        item.isOutRange = true;
372
                    } else {
373
                        item.isOutRange = false;
374
                    }
375
                });
376
            }
377
        } else {
378
            const { rangeStart, rangeEnd } = this.getActualStartAndEnd(value);
379
            this.selectableData[type].forEach((item: AdvancedSelectableCell) => {
UNCOV
380
                if (item.startValue.getTime() >= rangeStart.getTime() && item.startValue.getTime() < rangeEnd.getTime()) {
×
381
                    item.isInRange = true;
382
                } else {
383
                    item.isInRange = false;
384
                }
385
            });
386
        }
387
        this.selectableData[type].forEach(item => (item.classMap = this.getClassMap(item)));
388
    }
389

390
    onMouseleave(event: Event, type: ThyDateGranularity, value: AdvancedSelectableCell) {
391
        if (value.isInRange) {
392
            this.selectableData[type].forEach(item => (item.isInRange = false));
393
        }
394
        if (value.isOutRange) {
395
            this.selectableData[type].forEach(item => (item.isOutRange = false));
396
        }
397
        this.selectableData[type].forEach(item => (item.classMap = this.getClassMap(item)));
398
    }
399

400
    getActualStartAndEnd(value: AdvancedSelectableCell) {
401
        const selectedStart = this.selectedValue[0].startValue;
402
        const selectedEnd = this.selectedValue[this.selectedValue.length - 1].endValue;
403
        let rangeStart: TinyDate, rangeEnd: TinyDate;
404
        if (value.startValue.isBeforeDay(selectedStart)) {
405
            rangeStart = value.startValue;
406
            rangeEnd = selectedStart;
407
        }
408
        if (value.startValue.isAfterDay(selectedEnd)) {
409
            rangeStart = selectedEnd;
410
            rangeEnd = value.endValue;
411
        }
412
        return { rangeStart, rangeEnd };
413
    }
414

415
    ngOnDestroy(): void {
416
        this.selectedValueChange$.complete();
417
    }
418
}
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