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

atinc / ngx-tethys / 9db35bbc-bcad-48fa-a922-e81e652bf188

18 Nov 2024 05:22AM UTC coverage: 90.351% (-0.001%) from 90.352%
9db35bbc-bcad-48fa-a922-e81e652bf188

push

circleci

minlovehua
feat(i18n): i18n design and review #TINFR-916 

5522 of 6760 branches covered (81.69%)

Branch coverage included in aggregate %.

32 of 35 new or added lines in 10 files covered. (91.43%)

20 existing lines in 3 files now uncovered.

13197 of 13958 relevant lines covered (94.55%)

996.51 hits per line

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

90.23
/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
    Input,
9
    OnDestroy,
10
    OnInit,
11
    inject,
12
    Signal
13
} from '@angular/core';
14
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
15
import { ThyButton } from 'ngx-tethys/button';
16
import { ThyIcon } from 'ngx-tethys/icon';
1✔
17
import { TinyDate } from 'ngx-tethys/util';
18
import { Subject } from 'rxjs';
17✔
19
import { AdvancedSelectableCell, RangeAdvancedValue } from '../../inner-types';
17✔
20
import { DatePickerAdvancedShowYearTipPipe } from '../../picker.pipes';
17✔
21
import { ThyDateGranularity } from '../../standard-types';
17✔
22
import { injectLocale, ThyDatePickerLocale } from 'ngx-tethys/i18n';
17✔
23
import { DateHelperService } from '../../date-helper.service';
17✔
24

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

14✔
48
    @HostBinding('class') className = 'thy-date-picker-advanced-carousel';
11✔
49

1✔
50
    @Input() activeDate: TinyDate;
51

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

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

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

67
    dateGranularity: ThyDateGranularity;
41✔
68

24✔
69
    selectedValue: AdvancedSelectableCell[] = [];
70

71
    private initialized = false;
72

17✔
73
    private selectedValueChange$ = new Subject<RangeAdvancedValue>();
74

75
    private _onChange: (value: RangeAdvancedValue) => void;
17✔
76

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

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

301✔
106
    writeValue(value: RangeAdvancedValue): void {
107
        if (value) {
108
            this.defaultValue = value;
22✔
109
        }
110
    }
111

14✔
112
    registerOnChange(fn: any): void {
113
        this._onChange = fn;
114
    }
13✔
115

19✔
116
    registerOnTouched(fn: any): void {
13✔
117
        this._onTouched = fn;
118
    }
119

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

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

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

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

152
    isSelectEmpty() {
3✔
153
        return this.selectedValue.length == 0;
3!
UNCOV
154
    }
×
155

156
    selectSort() {
157
        this.selectedValue.sort((a, b) => a.startValue.getTime() - b.startValue.getTime());
3✔
158
    }
3✔
159

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

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

7✔
178
    clearSelect(hidden?: boolean) {
179
        this.selectedValue = [];
180
        if (!hidden) {
111✔
181
            this.selectedValueChange$.next(undefined);
37✔
182
        }
98✔
183
    }
184

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

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

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

8✔
262
    getSelectableQuarter(currentDate: TinyDate, preOrNextcount: number = 0): AdvancedSelectableCell {
28✔
263
        currentDate = currentDate || this.activeDate || new TinyDate().startOfQuarter();
28✔
264
        return {
265
            type: 'quarter',
8✔
266
            content: `Q${currentDate.addQuarters(preOrNextcount).getQuarter()}`,
4✔
267
            startValue: currentDate.startOfQuarter().addQuarters(preOrNextcount),
4✔
268
            endValue: currentDate.endOfQuarter().addQuarters(preOrNextcount),
269
            classMap: {}
4✔
270
        };
271
    }
4✔
272

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

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

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

314
    selectDate(type: ThyDateGranularity, value: AdvancedSelectableCell) {
1✔
315
        this.selectableData[type].forEach(item => {
1✔
316
            item.isInRange = false;
1✔
317
            item.isOutRange = false;
4✔
318
        });
2✔
319

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

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

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

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

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

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