• 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

90.19
/src/date-picker/lib/date-carousel/date-carousel.component.ts
1
import { NgClass, NgFor, NgIf, NgTemplateOutlet } from '@angular/common';
2
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, HostBinding, Input, OnDestroy, OnInit } from '@angular/core';
3
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
4
import { ThyButton } from 'ngx-tethys/button';
5
import { ThyIcon } from 'ngx-tethys/icon';
6
import { TinyDate } from 'ngx-tethys/util';
7
import { Subject } from 'rxjs';
8
import { AdvancedSelectableCell, RangeAdvancedValue } from '../../inner-types';
9
import { DatePickerAdvancedShowYearTipPipe } from '../../picker.pipes';
10
import { ThyDateGranularity } from '../../standard-types';
11

12
/**
13
 * @private
14
 */
1✔
15
@Component({
16
    // eslint-disable-next-line @angular-eslint/component-selector
24✔
17
    selector: 'date-carousel',
24✔
18
    templateUrl: './date-carousel.component.html',
24✔
19
    changeDetection: ChangeDetectionStrategy.OnPush,
6✔
20
    providers: [
6✔
21
        {
22
            provide: NG_VALUE_ACCESSOR,
23
            multi: true,
18✔
24
            useExisting: forwardRef(() => DateCarousel)
25
        }
24✔
26
    ],
27
    standalone: true,
28
    imports: [NgTemplateOutlet, ThyButton, ThyIcon, NgFor, NgClass, NgIf, DatePickerAdvancedShowYearTipPipe]
17✔
29
})
17✔
30
export class DateCarousel implements OnInit, ControlValueAccessor, OnDestroy {
17✔
31
    @HostBinding('class') className = 'thy-date-picker-advanced-carousel';
17✔
32

17✔
33
    @Input() activeDate: TinyDate;
17✔
34

35
    set defaultValue(value: RangeAdvancedValue) {
36
        this.dateGranularity = value.dateGranularity;
17✔
37
        this.buildSelectableData(value.begin);
14✔
38

13✔
39
        if (value.begin && value.end) {
40
            const shouldBeSelectValue = this.getShouldBeToggleValue(value.begin, value.end);
42✔
41
            this.select(...shouldBeSelectValue);
56✔
42
        } else {
56✔
43
            this.clearSelect(true);
14✔
44
        }
11✔
45
        this.initialized = true;
1✔
46
    }
47

48
    selectableData: { year?: AdvancedSelectableCell[]; quarter?: AdvancedSelectableCell[]; month?: AdvancedSelectableCell[] } = {};
49

50
    dateGranularity: ThyDateGranularity;
51

52
    selectedValue: AdvancedSelectableCell[] = [];
10✔
53

10✔
54
    private initialized = false;
55

56
    private selectedValueChange$ = new Subject<RangeAdvancedValue>();
57

58
    private _onChange: (value: RangeAdvancedValue) => void;
59

60
    private _onTouched: (value: RangeAdvancedValue) => void;
61

62
    constructor(private cdr: ChangeDetectorRef) {}
63

41✔
64
    ngOnInit(): void {
24✔
65
        this.selectedValueChange$.subscribe(() => {
66
            if (this.selectedValue.length) {
67
                this.buildSelectableData(this.selectedValue[0]?.startValue, this.dateGranularity);
68
            }
17✔
69
            this.selectableData.year.forEach(item => (item.classMap = this.getClassMap(item)));
70
            this.selectableData.quarter.forEach(item => (item.classMap = this.getClassMap(item)));
71
            this.selectableData.month.forEach(item => (item.classMap = this.getClassMap(item)));
17✔
72
            if (this.initialized) {
73
                if (this.isSelectEmpty()) {
74
                    this._onChange({
192✔
75
                        dateGranularity: null,
76
                        begin: null,
77
                        end: null
78
                    });
79
                } else {
80
                    const selctedValue = this.selectedValue;
81
                    this._onChange({
82
                        dateGranularity: this.dateGranularity,
83
                        begin: selctedValue[0]?.startValue,
192✔
84
                        end: selctedValue[selctedValue.length - 1]?.endValue
85
                    });
86
                }
192✔
87
            }
56✔
88
        });
89
    }
90

136✔
91
    writeValue(value: RangeAdvancedValue): void {
32✔
92
        if (value) {
93
            this.defaultValue = value;
94
        }
104✔
95
    }
41✔
96

65✔
97
    registerOnChange(fn: any): void {
98
        this._onChange = fn;
99
    }
100

101
    registerOnTouched(fn: any): void {
301✔
102
        this._onTouched = fn;
103
    }
104

22✔
105
    getClassMap(cell: AdvancedSelectableCell) {
106
        return {
107
            [`active`]: this.isSelected(cell),
14✔
108
            [`indeterminate`]: this.isCellIndeterminate(this.selectedValue, cell),
109
            [`type-active`]: this.isTypeActive(this.selectedValue, cell),
110
            ['in-hover-range']: cell.isInRange,
13✔
111
            ['out-range']: cell.isOutRange
19✔
112
        };
13✔
113
    }
114

115
    isTypeActive(originalValue: AdvancedSelectableCell[], value: AdvancedSelectableCell) {
13✔
116
        return originalValue?.length && originalValue[0].type === value.type;
13✔
117
    }
118

119
    isCellIndeterminate(originalValue: AdvancedSelectableCell[], value: AdvancedSelectableCell) {
1✔
120
        if (originalValue[0]?.type === value.type) {
1✔
121
            return false;
122
        } else {
1✔
123
            if (originalValue[0]?.type === 'year') {
1✔
124
                return !!originalValue.find(item => item.startValue.isSameYear(value.startValue));
125
            } else {
126
                return value.type === 'year'
20✔
127
                    ? !!originalValue.find(item => item.startValue.isSameYear(value.startValue))
20!
UNCOV
128
                    : !!originalValue.find(item => item.startValue.isSameQuarter(value.startValue));
×
129
            }
130
        }
131
    }
132

7✔
133
    isSelected(value: AdvancedSelectableCell) {
7✔
134
        return this.selectedValue.find(item => item.startValue.isSameDay(value.startValue)) && this.dateGranularity === value.type;
135
    }
1✔
136

1!
UNCOV
137
    isSelectEmpty() {
×
138
        return this.selectedValue.length == 0;
139
    }
140

1✔
141
    selectSort() {
1✔
142
        this.selectedValue.sort((a, b) => a.startValue.getTime() - b.startValue.getTime());
1✔
143
    }
1✔
144

145
    select(...value: AdvancedSelectableCell[]) {
146
        value.forEach(item => {
1✔
147
            if (!this.isSelected(item)) {
148
                this.selectedValue.push(...value);
3✔
149
            }
3!
UNCOV
150
        });
×
151
        this.selectSort();
152
        this.selectedValueChange$.next(undefined);
153
    }
3✔
154

3✔
155
    deselect(...value: AdvancedSelectableCell[]) {
5✔
156
        value.forEach(item => {
5✔
157
            this.selectedValue = this.selectedValue.filter(selected => !selected.startValue.isSameDay(item.startValue));
158
        });
159
        this.selectSort();
3✔
160
        this.selectedValueChange$.next(undefined);
161
    }
3✔
162

3!
163
    clearSelect(hidden?: boolean) {
3✔
164
        this.selectedValue = [];
165
        if (!hidden) {
UNCOV
166
            this.selectedValueChange$.next(undefined);
×
167
        }
×
168
    }
×
169

×
170
    getShouldBeToggleValue(begin: TinyDate, end: TinyDate) {
171
        let selectedValue: AdvancedSelectableCell[] = [];
172
        switch (this.dateGranularity) {
173
            case 'year':
7✔
174
                this.dateGranularity = 'year';
175
                if (begin.isSameYear(end)) {
176
                    selectedValue.push(this.getSelectableYear(begin));
111✔
177
                } else {
37✔
178
                    selectedValue.push(this.getSelectableYear(begin));
98✔
179
                    while (!begin.isSameYear(end)) {
180
                        begin = begin.addYears(1);
33✔
181
                        selectedValue.push(this.getSelectableYear(begin));
99✔
182
                    }
183
                }
33✔
184
                break;
185
            case 'month':
33✔
186
                this.dateGranularity = 'month';
132✔
187
                if (begin.isSameMonth(end)) {
188
                    selectedValue.push(this.getSelectableMonth(begin));
33✔
189
                } else {
190
                    selectedValue.push(this.getSelectableMonth(begin));
32✔
191
                    while (!begin.isSameMonth(end)) {
128✔
192
                        begin = begin.addMonths(1);
193
                        selectedValue.push(this.getSelectableMonth(begin));
32✔
194
                    }
195
                }
196
                break;
37✔
197
            case 'quarter':
198
                this.dateGranularity = 'quarter';
2✔
199
                if (begin.isSameQuarter(end)) {
107✔
200
                    selectedValue.push(this.getSelectableQuarter(begin));
107✔
201
                } else {
202
                    selectedValue.push(this.getSelectableQuarter(begin));
203
                    while (!begin.isSameQuarter(end)) {
204
                        begin = begin.addQuarters(1);
205
                        selectedValue.push(this.getSelectableQuarter(begin));
206
                    }
207
                }
208
        }
3✔
209
        return selectedValue;
143✔
210
    }
143✔
211

212
    buildSelectableData(startDate: TinyDate, excludeGranularity?: ThyDateGranularity) {
213
        const buildGranularity = ['year', 'month', 'quarter'].filter(item => item !== excludeGranularity);
214
        buildGranularity.forEach(granularity => {
215
            switch (granularity) {
216
                case 'year':
217
                    this.selectableData.year = [...Array(3).keys()].map((item, index) => {
218
                        return this.getSelectableYear(startDate, index);
8✔
219
                    });
144✔
220
                    break;
144✔
221
                case 'quarter':
222
                    this.selectableData.quarter = [...Array(4).keys()].map((item, index) => {
223
                        return this.getSelectableQuarter(startDate, index);
224
                    });
225
                    break;
226
                case 'month':
227
                    this.selectableData.month = [...Array(4).keys()].map((item, index) => {
144✔
228
                        return this.getSelectableMonth(startDate, index);
229
                    });
230
                    break;
3✔
231
            }
232
        });
3✔
233
        this.cdr.markForCheck();
1✔
234
    }
235

4✔
236
    getSelectableYear(currentDate: TinyDate, preOrNextcount: number = 0): AdvancedSelectableCell {
1✔
237
        currentDate = currentDate || this.activeDate || new TinyDate().startOfYear();
238
        return {
4✔
239
            type: 'year',
240
            content: `${currentDate.addYears(preOrNextcount).getYear()}`,
11✔
241
            startValue: currentDate.startOfYear().addYears(preOrNextcount),
242
            endValue: currentDate.endOfYear().addYears(preOrNextcount),
243
            classMap: {}
3✔
244
        };
245
    }
3✔
246

1✔
247
    getSelectableQuarter(currentDate: TinyDate, preOrNextcount: number = 0): AdvancedSelectableCell {
248
        currentDate = currentDate || this.activeDate || new TinyDate().startOfQuarter();
4✔
249
        return {
1✔
250
            type: 'quarter',
251
            content: `Q${currentDate.addQuarters(preOrNextcount).getQuarter()}`,
4✔
252
            startValue: currentDate.startOfQuarter().addQuarters(preOrNextcount),
253
            endValue: currentDate.endOfQuarter().addQuarters(preOrNextcount),
11✔
254
            classMap: {}
255
        };
256
    }
8✔
257

28✔
258
    getSelectableMonth(currentDate: TinyDate, preOrNextcount: number = 0): AdvancedSelectableCell {
28✔
259
        currentDate = currentDate || this.activeDate || new TinyDate().startOfMonth();
260
        const cell: AdvancedSelectableCell = {
8✔
261
            type: 'month',
4✔
262
            content: `${currentDate.addMonths(preOrNextcount).getMonth() + 1}月`,
4✔
263
            startValue: currentDate.startOfMonth().addMonths(preOrNextcount),
264
            endValue: currentDate.endOfMonth().addMonths(preOrNextcount),
4✔
265
            classMap: {}
266
        };
4✔
267
        return cell;
1✔
268
    }
1!
269

1✔
270
    prevClick(type: ThyDateGranularity) {
271
        switch (type) {
1✔
272
            case 'year':
273
                this.selectableData.year = this.selectableData.year.map(item => this.getSelectableYear(item.startValue, -1));
3✔
274
                break;
1✔
275
            case 'quarter':
1✔
276
                this.selectableData.quarter = this.selectableData.quarter.map(item => this.getSelectableQuarter(item.startValue, -2));
1✔
277
                break;
278
            case 'month':
279
                this.selectableData.month = this.selectableData.month.map(item => this.getSelectableMonth(item.startValue, -2));
280
        }
2✔
281
        this.selectableData[type].forEach(item => (item.classMap = this.getClassMap(item)));
2✔
282
    }
2✔
283

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

298
    selectDate(type: ThyDateGranularity, value: AdvancedSelectableCell) {
299
        this.selectableData[type].forEach(item => {
300
            item.isInRange = false;
2!
UNCOV
301
            item.isOutRange = false;
×
302
        });
303

2✔
304
        if (this.isSelectEmpty()) {
1✔
305
            this.dateGranularity = type;
1!
UNCOV
306
            this.select(value);
×
307
            // this.selectedValueChange$.next();
308
            return;
309
        }
1✔
310
        if (this.isSelected(value)) {
1✔
311
            this.toggleSelect(value);
1✔
312
            if (this.isSelectEmpty()) {
4✔
313
                this.dateGranularity = null;
2✔
314
            }
315
            return;
316
        }
2✔
317

318
        if (this.dateGranularity === value.type) {
319
            const { rangeStart, rangeEnd } = this.getActualStartAndEnd(value);
320
            const shouldBeSelectValue = this.getShouldBeToggleValue(rangeStart, rangeEnd);
321
            this.select(...shouldBeSelectValue);
322
            // this.selectedValueChange$.next();
1✔
323
        } else {
1✔
324
            this.dateGranularity = type;
4✔
325
            this.clearSelect(true);
1✔
326
            this.select(value);
327
            // this.selectedValueChange$.next();
328
        }
3✔
329
    }
330
    toggleSelect(value: AdvancedSelectableCell) {
331
        if (value.startValue.isSameDay(this.selectedValue[0].startValue)) {
332
            // only deselect first one
8✔
333
            this.deselect(value);
334
        } else {
335
            // deselect current and all after current
2!
336
            const rangeStart = value.startValue;
8✔
337
            const rangeEnd = this.selectedValue[this.selectedValue.length - 1].endValue;
338
            const shouldBeDeselectValue = this.getShouldBeToggleValue(rangeStart, rangeEnd);
2✔
339
            this.deselect(...shouldBeDeselectValue);
4✔
340
        }
341
    }
8✔
342

343
    onMouseenter(event: Event, type: ThyDateGranularity, value: AdvancedSelectableCell) {
344
        if (this.isSelectEmpty() || this.dateGranularity !== type) {
2✔
345
            return;
2✔
346
        }
347
        if (this.isSelected(value)) {
2!
UNCOV
348
            value.isInRange = true;
×
349
            if (value.startValue.isSameDay(this.selectedValue[0].startValue)) {
×
350
                value.isOutRange = true;
351
            } else {
2!
352
                const rangeStart = value.startValue;
2✔
353
                const rangeEnd = this.selectedValue[this.selectedValue.length - 1].endValue;
2✔
354
                this.selectableData[type].forEach((item: AdvancedSelectableCell) => {
355
                    if (item.startValue.getTime() >= rangeStart.getTime() && item.startValue.getTime() < rangeEnd.getTime()) {
2✔
356
                        item.isOutRange = true;
357
                    } else {
358
                        item.isOutRange = false;
17✔
359
                    }
360
                });
1✔
361
            }
362
        } else {
363
            const { rangeStart, rangeEnd } = this.getActualStartAndEnd(value);
1✔
364
            this.selectableData[type].forEach((item: AdvancedSelectableCell) => {
365
                if (item.startValue.getTime() >= rangeStart.getTime() && item.startValue.getTime() < rangeEnd.getTime()) {
366
                    item.isInRange = true;
367
                } else {
368
                    item.isInRange = false;
1✔
369
                }
370
            });
371
        }
372
        this.selectableData[type].forEach(item => (item.classMap = this.getClassMap(item)));
373
    }
374

375
    onMouseleave(event: Event, type: ThyDateGranularity, value: AdvancedSelectableCell) {
376
        if (value.isInRange) {
377
            this.selectableData[type].forEach(item => (item.isInRange = false));
378
        }
17✔
379
        if (value.isOutRange) {
380
            this.selectableData[type].forEach(item => (item.isOutRange = false));
381
        }
382
        this.selectableData[type].forEach(item => (item.classMap = this.getClassMap(item)));
383
    }
384

385
    getActualStartAndEnd(value: AdvancedSelectableCell) {
386
        const selectedStart = this.selectedValue[0].startValue;
387
        const selectedEnd = this.selectedValue[this.selectedValue.length - 1].endValue;
388
        let rangeStart: TinyDate, rangeEnd: TinyDate;
389
        if (value.startValue.isBeforeDay(selectedStart)) {
390
            rangeStart = value.startValue;
391
            rangeEnd = selectedStart;
392
        }
393
        if (value.startValue.isAfterDay(selectedEnd)) {
394
            rangeStart = selectedEnd;
395
            rangeEnd = value.endValue;
396
        }
397
        return { rangeStart, rangeEnd };
398
    }
399

400
    ngOnDestroy(): void {
401
        this.selectedValueChange$.complete();
402
    }
403
}
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