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

atinc / ngx-tethys / #96

12 Aug 2025 06:20AM UTC coverage: 90.341% (+0.02%) from 90.324%
#96

push

web-flow
refactor(date-picker): migrate to signal for date-picker #TINFR-1463 (#3513)

* refactor(date-picker): migrate to signal for calendar header

* refactor(date-picker): migrate to signal for calendar footer

* refactor(date-picker): migrate to signal for calendar table

* refactor(date-picker): migrate to signal for date table cell

* refactor(date-picker): migrate to signal for date carousel

* refactor(date-picker): migrate to signal for inner-popup and date-popup

* refactor(date-picker): migrate to signal for pickers

5531 of 6813 branches covered (81.18%)

Branch coverage included in aggregate %.

342 of 367 new or added lines in 20 files covered. (93.19%)

66 existing lines in 11 files now uncovered.

13969 of 14772 relevant lines covered (94.56%)

904.1 hits per line

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

90.72
/src/date-picker/lib/popups/date-popup.component.ts
1
import {
2
    coerceBooleanProperty,
3
    endOfDay,
4
    FunctionProp,
5
    helpers,
6
    isFunction,
7
    isUndefinedOrNull,
8
    sortRangeValue,
9
    startOfDay,
10
    TinyDate,
11
    TinyDateCompareGrain
12
} from 'ngx-tethys/util';
13
import {
14
    ChangeDetectionStrategy,
15
    ChangeDetectorRef,
16
    Component,
17
    inject,
18
    input,
19
    model,
1✔
20
    OnInit,
21
    output,
152✔
22
    Signal,
152✔
23
    SimpleChanges,
152✔
24
    TemplateRef,
152✔
25
    signal,
152✔
26
    OutputEmitterRef,
152✔
27
    OnChanges
152✔
28
} from '@angular/core';
152✔
29
import { NgTemplateOutlet } from '@angular/common';
152✔
30
import { FormsModule } from '@angular/forms';
152✔
31
import { ThyButtonIcon } from 'ngx-tethys/button';
152✔
32
import { injectLocale, ThyDatePickerLocale } from 'ngx-tethys/i18n';
152✔
33
import { ThyNav, ThyNavItemDirective } from 'ngx-tethys/nav';
152✔
34
import { ThyDatePickerConfigService } from '../../date-picker.service';
152✔
35
import { CompatibleValue, DatePickerFlexibleTab, RangeAdvancedValue, RangePartType } from '../../inner-types';
152✔
36
import { dateAddAmount, getShortcutValue, hasValue, makeValue, setValueByTimestampPrecision, transformDateValue } from '../../picker.util';
152✔
37
import {
152✔
38
    ThyCompatibleDate,
152✔
39
    CompatiblePresets,
152✔
40
    DisabledDateFn,
152✔
41
    SupportTimeOptions,
152✔
42
    ThyDateChangeEvent,
152✔
43
    ThyDateGranularity,
152✔
44
    ThyPanelMode,
152✔
45
    ThyShortcutPosition,
152✔
46
    ThyShortcutPreset,
152✔
47
    ThyShortcutValue
152✔
48
} from '../../standard-types';
152✔
49
import { CalendarFooter } from '../calendar/calendar-footer.component';
152✔
50
import { DateCarousel } from '../date-carousel/date-carousel.component';
152✔
51
import { InnerPopup } from './inner-popup.component';
152✔
52

152✔
53
/**
152✔
54
 * @private
152✔
55
 */
152✔
56
@Component({
152✔
57
    changeDetection: ChangeDetectionStrategy.OnPush,
152✔
58
    // eslint-disable-next-line @angular-eslint/component-selector
152✔
59
    selector: 'date-popup',
60
    exportAs: 'datePopup',
61
    templateUrl: './date-popup.component.html',
1✔
62
    imports: [ThyNav, ThyNavItemDirective, ThyButtonIcon, DateCarousel, FormsModule, NgTemplateOutlet, InnerPopup, CalendarFooter]
1✔
63
})
64
export class DatePopup implements OnInit, OnChanges {
65
    private cdr = inject(ChangeDetectorRef);
152✔
66

152✔
67
    private datePickerConfigService = inject(ThyDatePickerConfigService);
152!
NEW
68

×
69
    locale: Signal<ThyDatePickerLocale> = injectLocale('datePicker');
70

152!
NEW
71
    readonly isRange = input(false, { transform: coerceBooleanProperty });
×
NEW
72

×
73
    readonly showWeek = input(false, { transform: coerceBooleanProperty });
74

152✔
75
    readonly format = input<string>();
152✔
76

152✔
77
    readonly disabledDate = model<DisabledDateFn>();
8✔
78

79
    readonly minDate = input<Date | number>();
80

81
    readonly maxDate = input<Date | number>();
82

83
    readonly showToday = input(false, { transform: coerceBooleanProperty });
84

85
    /**
335✔
86
     * 是否支持设置时间(时、分)
152✔
87
     */
61✔
88
    readonly showTime = input<SupportTimeOptions | boolean>();
89

90
    /**
91✔
91
     * 是否展示时间(时、分)
92
     */
93
    readonly mustShowTime = input(false, { transform: coerceBooleanProperty });
335✔
94

152✔
95
    readonly dateRender = input<FunctionProp<TemplateRef<Date> | string>>();
96

335✔
97
    readonly className = input<string>();
204✔
98

99
    readonly panelMode = model<ThyPanelMode | ThyPanelMode[]>(undefined);
100

101
    readonly value = model<CompatibleValue>();
152✔
102

152✔
103
    readonly defaultPickerValue = input<ThyCompatibleDate | number>();
104

105
    readonly showShortcut = model<boolean>();
152✔
106

94✔
107
    readonly shortcutPresets = model<CompatiblePresets>();
91✔
108

109
    readonly shortcutPosition = input<ThyShortcutPosition>();
94✔
110

111
    readonly flexible = input(false, { transform: coerceBooleanProperty });
112

94!
113
    readonly flexibleDateGranularity = model<ThyDateGranularity>();
94✔
114

94✔
115
    readonly timestampPrecision = input<'seconds' | 'milliseconds'>();
94✔
116

94✔
117
    readonly timeZone = input<string>();
94✔
118

30✔
119
    readonly panelModeChange = output<ThyPanelMode | ThyPanelMode[]>();
114✔
120

114✔
121
    readonly calendarChange = output<CompatibleValue>();
114✔
122

114✔
123
    readonly valueChange: OutputEmitterRef<CompatibleValue | RangeAdvancedValue> = output();
114✔
124

10✔
125
    readonly resultOk = output<void>(); // Emitted when done with date selecting
126

127
    readonly showTimePickerChange = output<boolean>();
104✔
128

129
    readonly dateValueChange = output<ThyDateChangeEvent>();
130

131
    prefixCls = 'thy-calendar';
132

64✔
133
    showTimePicker = false;
133✔
134

133✔
135
    timeOptions: SupportTimeOptions | SupportTimeOptions[] | null;
133✔
136

11✔
137
    activeDate: TinyDate | TinyDate[];
138

139
    selectedValue: TinyDate[] = []; // Range ONLY
122✔
140

141
    hoverValue: TinyDate[] = []; // Range ONLY
142

143
    advancedSelectedValue: RangeAdvancedValue; // advanced ONLY
144

145
    flexibleActiveTab: DatePickerFlexibleTab = 'advanced';
146

147
    private partTypeMap: { [key: string]: number } = { left: 0, right: 1 };
509✔
148

509✔
149
    [property: string]: any;
143✔
150

143✔
151
    readonly endPanelMode = signal<ThyPanelMode | ThyPanelMode[]>(undefined);
152

509✔
153
    innerShortcutPresets: ThyShortcutPreset[];
246✔
154

208✔
155
    readonly disableTimeConfirm = signal(false);
156

246✔
157
    setProperty<T extends keyof DatePopup>(key: T, value: this[T]): void {
158
        this[key] = value;
159
        this.cdr.markForCheck();
263✔
160
    }
161

509✔
162
    ngOnInit(): void {
163
        this.initShortcutPresets();
164
        this.initPanelMode();
152!
NEW
165
        if (this.flexible() && this.flexibleDateGranularity() === 'day') {
×
UNCOV
166
            this.flexibleActiveTab = 'custom';
×
167
        }
168
        if (this.defaultPickerValue() && !hasValue(this.value())) {
NEW
169
            const { value } = transformDateValue(this.defaultPickerValue());
×
170
            this.value.set(makeValue(value, this.isRange(), this.timeZone()));
171
        }
172
        this.updateActiveDate();
173
        this.initDisabledDate();
152✔
174
        if (this.isRange() && this.flexible() && this.value()) {
61✔
175
            this.advancedSelectedValue = {
176
                begin: (this.value() as TinyDate[])[0],
177
                end: (this.value() as TinyDate[])[1],
91✔
178
                dateGranularity: this.flexibleDateGranularity()
179
            };
180
        }
181
    }
182

183
    ngOnChanges(changes: SimpleChanges): void {
184
        if (changes.panelMode) {
185
            if (helpers.isArray(this.panelMode())) {
152✔
186
                this.endPanelMode.set([...(this.panelMode() as ThyPanelMode[])]);
15✔
187
            } else {
15✔
188
                this.endPanelMode.set(this.panelMode());
189
            }
152✔
190
        }
13✔
191
        if (changes.defaultPickerValue) {
13✔
192
            this.updateActiveDate();
193
        }
152✔
194
        if (changes.value && changes.value.currentValue) {
6✔
195
            this.updateActiveDate();
196
        }
152✔
197
    }
21,722✔
198

21,722✔
199
    initShortcutPresets(): void {
2,310✔
200
        const { shortcutRangesPresets, shortcutDatePresets, showShortcut } = this.datePickerConfigService;
201

21,722✔
202
        this.showShortcut.set(
1,957✔
203
            ['date', 'date,date'].includes(this.panelMode().toString()) && isUndefinedOrNull(this.showShortcut())
204
                ? showShortcut
21,722✔
205
                : this.showShortcut()
630✔
206
        );
207

21,722✔
208
        if (this.showShortcut()) {
209
            if (!this.shortcutPresets()) {
210
                this.shortcutPresets.set(this.isRange() ? shortcutRangesPresets : shortcutDatePresets);
211
            }
46✔
212

46✔
213
            this.innerShortcutPresets = isFunction(this.shortcutPresets())
214
                ? (this.shortcutPresets() as () => ThyShortcutPreset[])()
215
                : (this.shortcutPresets() as ThyShortcutPreset[]);
5✔
216
            if (this.innerShortcutPresets.length) {
5✔
217
                const minDate: TinyDate = this.getMinTinyDate();
5✔
218
                const maxDate: TinyDate = this.getMaxTinyDate();
219

220
                const minTime = minDate ? minDate.getTime() : null;
2!
221
                const maxTime = maxDate ? maxDate.getTime() : null;
2✔
222

223
                if (this.isRange()) {
224
                    this.innerShortcutPresets.forEach((preset: ThyShortcutPreset) => {
1!
225
                        const begin: number | Date = getShortcutValue((preset.value as [ThyShortcutValue, ThyShortcutValue])[0]);
NEW
226
                        const beginTime: number = new TinyDate(startOfDay(begin), this.timeZone()).getTime();
×
227

×
NEW
228
                        const end: number | Date = getShortcutValue((preset.value as [ThyShortcutValue, ThyShortcutValue])[1]);
×
229
                        const endTime: number = new TinyDate(endOfDay(end), this.timeZone()).getTime();
230

UNCOV
231
                        if ((minDate && endTime < minTime) || (maxDate && beginTime > maxTime)) {
×
232
                            preset.disabled = true;
233
                        } else {
234
                            preset.disabled = false;
235
                        }
236
                    });
14✔
237
                } else {
1✔
238
                    this.innerShortcutPresets.forEach((preset: ThyShortcutPreset) => {
239
                        const singleValue: number | Date = getShortcutValue(preset.value as ThyShortcutValue);
240
                        const singleTime: number = new TinyDate(singleValue, this.timeZone()).getTime();
13✔
241

242
                        if ((minDate && singleTime < minTime) || (maxDate && singleTime > maxTime)) {
14✔
243
                            preset.disabled = true;
244
                        } else {
245
                            preset.disabled = false;
42✔
246
                        }
10✔
247
                    });
10✔
248
                }
249
            }
250
        }
32✔
251
    }
252

253
    updateActiveDate() {
254
        this.clearHoverValue();
26!
255
        if (!this.value()) {
256
            const { value } = transformDateValue(this.defaultPickerValue());
257
            this.value.set(makeValue(value, this.isRange(), this.timeZone()));
258
        }
26✔
259
        if (this.isRange()) {
260
            if (!this.flexible() || this.flexibleDateGranularity() === 'day') {
261
                this.selectedValue = this.value() as TinyDate[];
262
            }
2✔
263
            this.activeDate = this.normalizeRangeValue(this.value() as TinyDate[], this.getPanelMode(this.endPanelMode()) as ThyPanelMode);
264
        } else {
265
            this.activeDate = this.value() as TinyDate;
3✔
266
        }
3!
267
        this.isDisableTimeConfirm();
3✔
268
    }
269

UNCOV
270
    initPanelMode() {
×
271
        if (!this.endPanelMode()) {
272
            if (helpers.isArray(this.panelMode())) {
3✔
273
                this.endPanelMode.set([...(this.panelMode() as ThyPanelMode[])]);
274
            } else {
275
                this.endPanelMode.set(this.panelMode());
5✔
276
            }
277
        } else {
5✔
278
            if (helpers.isArray(this.endPanelMode())) {
5✔
279
                this.panelMode.set([...(this.endPanelMode() as ThyPanelMode[])]);
280
            } else {
281
                this.panelMode.set(this.endPanelMode());
38✔
282
            }
283
        }
24✔
284
    }
24✔
285

24✔
286
    initDisabledDate(): void {
287
        let minDate: TinyDate;
12✔
288
        let maxDate: TinyDate;
12✔
289
        let disabledDateFn: DisabledDateFn;
12✔
290
        if (this.minDate()) {
291
            const { value } = transformDateValue(this.minDate());
12!
292
            minDate = new TinyDate(value as Date, this.timeZone());
293
        }
12✔
294
        if (this.maxDate()) {
12✔
295
            const { value } = transformDateValue(this.maxDate());
12✔
296
            maxDate = new TinyDate(value as Date, this.timeZone());
12✔
297
        }
12✔
298
        if (this.disabledDate()) {
12✔
299
            disabledDateFn = this.disabledDate();
12✔
300
        }
12✔
301
        this.disabledDate.set(d => {
302
            let expression = false;
303
            if (minDate) {
304
                expression = d < minDate.startOfDay().nativeDate;
14✔
305
            }
14✔
306
            if (maxDate && !expression) {
14✔
307
                expression = d > maxDate.endOfDay().nativeDate;
308
            }
309
            if (disabledDateFn && typeof disabledDateFn === 'function' && !expression) {
310
                expression = disabledDateFn(d);
12✔
311
            }
12✔
312
            return expression;
1✔
313
        });
314
    }
11✔
315

1✔
316
    onShowTimePickerChange(show: boolean): void {
317
        this.showTimePicker = show;
10!
UNCOV
318
        this.showTimePickerChange.emit(show);
×
319
    }
320

10!
UNCOV
321
    onClickOk(): void {
×
322
        this.setValue(this.value());
323
        this.valueChange.emit(this.value());
324
        this.resultOk.emit();
10✔
325
    }
326

327
    onClickRemove(): void {
328
        this.value.set(this.isRange() ? [] : null);
25✔
329
        this.valueChange.emit(this.value());
12✔
330
    }
331

13✔
332
    onDayHover(value: TinyDate): void {
13✔
333
        if (this.isRange() && this.selectedValue[0] && !this.selectedValue[1]) {
13✔
334
            // When right value is selected, don't do hover
13✔
335
            const base = this.selectedValue[0]; // Use the left of selected value as the base to decide later hoverValue
13✔
336
            if (base.isBeforeDay(value)) {
11✔
337
                this.hoverValue = [base, value];
338
            } else {
339
                this.hoverValue = [value, base];
2✔
340
            }
341
        }
342
    }
343

2!
UNCOV
344
    onPanelModeChange(mode: ThyPanelMode, partType?: RangePartType): void {
×
NEW
345
        if (this.isRange()) {
×
NEW
346
            (this.panelMode() as ThyPanelMode[])[this.getPartTypeIndex(partType)] = mode;
×
UNCOV
347
        } else {
×
348
            this.panelMode.set(mode);
UNCOV
349
        }
×
350
        this.panelModeChange.emit(this.panelMode());
351
    }
352

2✔
353
    onHeaderChange(value: TinyDate, partType?: RangePartType): void {
354
        if (this.isRange()) {
355
            (this.activeDate as TinyDate[])[this.getPartTypeIndex(partType)] = value;
356
            this.activeDate = this.normalizeRangeValue(
1,378✔
357
                this.activeDate as TinyDate[],
848✔
358
                this.getPanelMode(this.endPanelMode(), partType) as ThyPanelMode
359
            );
360
        } else {
530✔
361
            this.activeDate = value;
362
        }
363
    }
364

549✔
365
    onSelectTime(value: TinyDate, partType?: RangePartType): void {
284✔
366
        if (this.isRange()) {
284✔
367
            // TODO:range picker set time
368
        } else {
369
            this.setValue(new TinyDate(value.nativeDate, this.timeZone()));
265✔
370
        }
371
    }
372

373
    selectTab(active: DatePickerFlexibleTab) {
549✔
374
        this.flexibleActiveTab = active;
284✔
375
    }
376

377
    clearFlexibleValue() {
265✔
378
        this.flexibleDateGranularity.set(null);
379
        if (this.flexibleActiveTab === 'advanced') {
380
            this.advancedSelectedValue = {};
379✔
381
        } else {
1,439✔
382
            this.selectedValue = [];
383
        }
384
        this.valueChange.emit({ begin: null, end: null, dateGranularity: this.flexibleDateGranularity() });
213✔
385
    }
386

387
    changeValueFromAdvancedSelect(value: RangeAdvancedValue) {
213✔
388
        this.valueChange.emit(value);
389
        // clear custom date when select a advanced date
390
        this.selectedValue = [];
521✔
391
        this.dateValueChange.emit({ value: [value.begin, value.end] });
392
    }
393

78✔
394
    changeValueFromSelect(value: TinyDate, partType?: RangePartType): void {
78✔
395
        if (this.isRange()) {
1✔
396
            // clear advanced date when select a custom date
1✔
397
            this.advancedSelectedValue = {};
398

399
            const [left, right] = this.selectedValue as TinyDate[];
400

401
            if ((!left && !right) || (left && right)) {
402
                // If totally full or empty, clean up && re-assign left first
403
                this.hoverValue = this.selectedValue = [value];
77✔
404
                this.selectedValue = [this.selectedValue[0].startOfDay()];
43✔
405
                this.calendarChange.emit([this.selectedValue[0].clone()]);
406
            } else if (left && !right) {
407
                // If one of them is empty, assign the other one and sort, then set the final values
78✔
408
                this.clearHoverValue(); // Clean up
409
                this.setRangeValue('right', value);
×
410
                this.selectedValue = sortRangeValue(this.selectedValue); // Sort
268✔
411
                this.selectedValue = this.getSelectedRangeValueByMode(this.selectedValue);
412
                this.activeDate = this.normalizeRangeValue(
413
                    this.selectedValue,
414
                    this.getPanelMode(this.endPanelMode(), partType) as ThyPanelMode
415
                );
416
                this.setValue(this.cloneRangeDate(this.selectedValue));
417
                this.calendarChange.emit(this.cloneRangeDate(this.selectedValue));
268✔
418
                this.dateValueChange.emit({ value: this.cloneRangeDate(this.selectedValue) });
268✔
419
            }
268✔
420
        } else {
268✔
421
            const updatedValue = this.updateHourMinute(value);
268✔
422
            this.setValue(updatedValue);
214✔
423
            this.dateValueChange.emit({ value: updatedValue });
424
        }
268✔
425
    }
426

427
    private getSelectedRangeValueByMode(value: TinyDate[]): TinyDate[] {
12✔
428
        const panelMode = this.getPanelMode(this.endPanelMode());
12✔
429
        if (panelMode === 'year') {
430
            return [value[0].startOfYear(), value[1].endOfYear()];
431
        } else if (panelMode === 'quarter') {
58✔
432
            return [value[0].startOfQuarter(), value[1].endOfQuarter()];
433
        } else if (panelMode === 'month') {
434
            return [value[0].startOfMonth(), value[1].endOfMonth()];
587✔
435
        } else if (panelMode === 'week') {
489✔
436
            return [value[0].startOfISOWeek(), value[1].endOfISOWeek()];
437
        } else {
98✔
438
            return [value[0].startOfDay(), value[1].endOfDay()];
98✔
439
        }
98✔
440
    }
98✔
441

16✔
442
    private updateHourMinute(value: TinyDate): TinyDate {
443
        if (!this.value()) {
444
            return value;
82✔
445
        }
446
        const originDate = this.value() as TinyDate;
447
        const dateTime = [value.getHours(), value.getMinutes(), value.getSeconds()];
448
        const originDateTime = [originDate.getHours(), originDate.getMinutes(), originDate.getSeconds()];
21✔
449

21✔
450
        const isEqualTime = dateTime.toString() === originDateTime.toString();
21✔
451
        if (isEqualTime) {
21✔
452
            return value;
21✔
453
        } else {
10✔
454
            return value.setHms(originDateTime[0], originDateTime[1], originDateTime[2]);
10✔
455
        }
10✔
456
    }
10✔
457

10!
UNCOV
458
    enablePrevNext(direction: 'prev' | 'next', partType?: RangePartType): boolean {
×
459
        if (this.isRange() && this.panelMode() === this.endPanelMode()) {
460
            const [start, end] = this.activeDate as TinyDate[];
10✔
461
            const showMiddle = !start.addMonths(1).isSame(end, 'month'); // One month diff then don't show middle prev/next
1✔
462
            if ((partType === 'left' && direction === 'next') || (partType === 'right' && direction === 'prev')) {
463
                return showMiddle;
9✔
464
            }
1✔
465
            return true;
466
        } else {
8✔
467
            return true;
1✔
468
        }
469
    }
7✔
470

471
    getPanelMode(panelMode: ThyPanelMode | ThyPanelMode[], partType?: RangePartType): ThyPanelMode {
472
        if (this.isRange()) {
11✔
473
            return panelMode[this.getPartTypeIndex(partType)] as ThyPanelMode;
11!
UNCOV
474
        } else {
×
475
            return panelMode as ThyPanelMode;
476
        }
11✔
477
    }
478

479
    getValueBySelector(partType?: RangePartType): TinyDate {
480
        if (this.isRange()) {
24✔
481
            const valueShow = this.selectedValue; // Use the real time value that without decorations when timepicker is shown up
2✔
482
            return (valueShow as TinyDate[])[this.getPartTypeIndex(partType)];
483
        } else {
22✔
484
            return this.value() as TinyDate;
22✔
485
        }
1✔
486
    }
487

488
    getActiveDate(partType?: RangePartType): TinyDate {
21✔
489
        if (this.isRange()) {
10✔
490
            return (this.activeDate as TinyDate[])[this.getPartTypeIndex(partType)];
10✔
491
        } else {
10!
492
            return this.activeDate as TinyDate;
10✔
493
        }
494
    }
495

496
    getPartTypeIndex(partType: RangePartType = 'left'): number {
10✔
497
        return this.partTypeMap[partType];
498
    }
499

500
    private getMinTinyDate() {
11✔
501
        return this.minDate() ? new TinyDate(transformDateValue(this.minDate()).value as Date, this.timeZone()) : null;
11✔
502
    }
11✔
503

11✔
504
    private getMaxTinyDate() {
505
        return this.maxDate() ? new TinyDate(transformDateValue(this.maxDate()).value as Date, this.timeZone()) : null;
21✔
506
    }
21✔
507

21✔
508
    private clearHoverValue(): void {
21✔
509
        this.hoverValue = [];
510
    }
511

21✔
512
    private setValue(value: CompatibleValue): void {
1✔
513
        this.value.set(value);
514
        if (this.isRange() && this.flexible()) {
515
            this.flexibleDateGranularity.set('day');
516
            this.valueChange.emit({
11✔
517
                begin: (value as TinyDate[])[0],
518
                end: (value as TinyDate[])[1],
1✔
519
                dateGranularity: this.flexibleDateGranularity()
520
            });
521
        } else {
522
            if (!this.showTime() || !this.showTimePicker) {
523
                this.valueChange.emit(this.value());
524
            }
525
        }
526
        this.isDisableTimeConfirm();
527
    }
528

529
    private normalizeRangeValue(value: TinyDate[], mode: ThyPanelMode = 'month'): TinyDate[] {
530
        const headerModes: { [key in ThyPanelMode]?: ThyPanelMode } = {
531
            week: 'month',
532
            date: 'month',
533
            month: 'year',
534
            quarter: 'year',
535
            year: 'decade'
536
        };
537
        const headerMode = headerModes[mode];
538
        const [start, end] = value;
539
        const newStart = start || new TinyDate(undefined, this.timeZone());
540
        let newEnd = end;
541
        if (!newEnd || newStart.isSame(end, headerMode as TinyDateCompareGrain)) {
542
            newEnd = dateAddAmount(newStart, 1, headerMode);
543
        }
544
        return [newStart, newEnd];
545
    }
546

547
    private setRangeValue(partType: RangePartType, value: TinyDate): void {
548
        const ref = (this.selectedValue = this.cloneRangeDate(this.selectedValue as TinyDate[]));
1✔
549
        ref[this.getPartTypeIndex(partType)] = value;
550
    }
551

552
    private cloneRangeDate(value: TinyDate[]): TinyDate[] {
553
        return [value[0] && value[0].clone(), value[1] && value[1].clone()] as TinyDate[];
554
    }
555

556
    private isDisableTimeConfirm() {
557
        if (this.isRange() || !this.showTime()) {
558
            return;
559
        }
560

561
        const date: TinyDate = this.value() ? (this.value() as TinyDate) : new TinyDate(undefined, this.timeZone());
562
        const minDate: TinyDate = this.getMinTinyDate();
563
        const maxDate: TinyDate = this.getMaxTinyDate();
564

565
        if ((minDate && date.getTime() < minDate.getTime()) || (maxDate && date.getTime() > maxDate.getTime())) {
566
            this.disableTimeConfirm.set(true);
567
        } else {
568
            this.disableTimeConfirm.set(false);
569
        }
570
    }
571

572
    private getSelectedShortcutPreset(date: CompatibleValue): CompatibleValue {
573
        const minDate: TinyDate = this.getMinTinyDate();
574
        const maxDate: TinyDate = this.getMaxTinyDate();
575

576
        const minTime: number = (minDate && minDate.getTime()) || null;
577
        const maxTime: number = (maxDate && maxDate.getTime()) || null;
578

579
        if (helpers.isArray(date)) {
580
            const startDate: TinyDate = date[0];
581
            const endDate: TinyDate = date[1];
582

583
            const startTime: number = startDate.getTime();
584
            const endTime: number = endDate.getTime();
585

586
            if ((maxDate && startTime > maxTime) || (minDate && endTime < minTime)) {
587
                return [];
588
            }
589

590
            if (minDate && startTime < minTime && maxDate && endTime > maxTime) {
591
                return [minDate, maxDate];
592
            }
593

594
            if (minDate && startTime < minTime) {
595
                return [minDate, endDate];
596
            }
597

598
            if (maxDate && endTime > maxTime) {
599
                return [startDate, maxDate];
600
            }
601

602
            return date;
603
        } else {
604
            const singleTime: number = date.getTime();
605

606
            if ((minDate && singleTime < minTime) || (maxDate && singleTime > maxTime)) {
607
                return null;
608
            }
609

610
            return date;
611
        }
612
    }
613

614
    shortcutSetValue(shortcutPresets: ThyShortcutPreset) {
615
        if (shortcutPresets.disabled) {
616
            return;
617
        }
618

619
        const { value } = shortcutPresets;
620
        if (!value) {
621
            return;
622
        }
623

624
        let selectedPresetValue: CompatibleValue;
625
        if (helpers.isArray(value)) {
626
            const begin: number | Date = getShortcutValue(value[0]);
627
            const end: number | Date = getShortcutValue(value[1]);
628

629
            if (begin && end) {
630
                this.selectedValue = this.getSelectedShortcutPreset([
631
                    new TinyDate(begin, this.timeZone()).startOfDay(),
632
                    new TinyDate(end, this.timeZone()).endOfDay()
633
                ]) as TinyDate[];
634
                selectedPresetValue = this.cloneRangeDate(this.selectedValue);
635
            }
636
        } else {
637
            const originDate = this.value() as TinyDate;
638
            const zonedTime = this.createInZoneTime(
639
                new TinyDate(getShortcutValue(value), this.timeZone()),
640
                originDate?.getHours() ?? 0,
641
                originDate?.getMinutes() ?? 0,
642
                originDate?.getSeconds() ?? 0
643
            );
644
            const singleTinyDate: TinyDate = this.updateHourMinute(new TinyDate(zonedTime, this.timeZone()));
645
            selectedPresetValue = this.getSelectedShortcutPreset(singleTinyDate) as TinyDate;
646
        }
647
        this.setValue(selectedPresetValue);
648
        const shortcutPresetsValue = setValueByTimestampPrecision(
649
            shortcutPresets?.value,
650
            this.isRange(),
651
            this.timestampPrecision(),
652
            this.timeZone()
653
        ) as number;
654
        this.dateValueChange.emit({
655
            value: helpers.isArray(value) ? this.selectedValue : selectedPresetValue,
656
            triggerPreset: Object.assign({}, shortcutPresets, { value: shortcutPresetsValue })
657
        });
658
        if (!helpers.isArray(value) && this.showTime() && this.showTimePicker) {
659
            this.updateActiveDate();
660
        }
661
    }
662

663
    private createInZoneTime(date: TinyDate, hours?: number, minutes?: number, seconds?: number): Date {
664
        return TinyDate.createDateInTimeZone(date.getYear(), date.getMonth(), date.getDate(), hours, minutes, seconds, this.timeZone());
665
    }
666
}
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