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

atinc / ngx-tethys / #94

12 Aug 2025 05:53AM UTC coverage: 90.345% (+0.02%) from 90.324%
#94

push

web-flow
Merge 79e13dd53 into aa9fa8ee2

5531 of 6813 branches covered (81.18%)

Branch coverage included in aggregate %.

350 of 378 new or added lines in 20 files covered. (92.59%)

61 existing lines in 11 files now uncovered.

13970 of 14772 relevant lines covered (94.57%)

904.12 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
} from '@angular/core';
152✔
28
import { NgTemplateOutlet } from '@angular/common';
152✔
29
import { FormsModule } from '@angular/forms';
152✔
30
import { ThyButtonIcon } from 'ngx-tethys/button';
152✔
31
import { injectLocale, ThyDatePickerLocale } from 'ngx-tethys/i18n';
152✔
32
import { ThyNav, ThyNavItemDirective } from 'ngx-tethys/nav';
152✔
33
import { ThyDatePickerConfigService } from '../../date-picker.service';
152✔
34
import { CompatibleValue, DatePickerFlexibleTab, RangeAdvancedValue, RangePartType } from '../../inner-types';
152✔
35
import { dateAddAmount, getShortcutValue, hasValue, makeValue, setValueByTimestampPrecision, transformDateValue } from '../../picker.util';
152✔
36
import {
152✔
37
    ThyCompatibleDate,
152✔
38
    CompatiblePresets,
152✔
39
    DisabledDateFn,
152✔
40
    SupportTimeOptions,
152✔
41
    ThyDateChangeEvent,
152✔
42
    ThyDateGranularity,
152✔
43
    ThyPanelMode,
152✔
44
    ThyShortcutPosition,
152✔
45
    ThyShortcutPreset,
152✔
46
    ThyShortcutValue
152✔
47
} from '../../standard-types';
152✔
48
import { CalendarFooter } from '../calendar/calendar-footer.component';
152✔
49
import { DateCarousel } from '../date-carousel/date-carousel.component';
152✔
50
import { InnerPopup } from './inner-popup.component';
152✔
51

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

152✔
66
    private datePickerConfigService = inject(ThyDatePickerConfigService);
152✔
67

152!
UNCOV
68
    locale: Signal<ThyDatePickerLocale> = injectLocale('datePicker');
×
69

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

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

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

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

8✔
78
    readonly minDate = input<Date | number>();
79

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

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

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

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

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

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

204✔
98
    readonly panelMode = model<ThyPanelMode | ThyPanelMode[]>(undefined);
99

100
    readonly value = model<CompatibleValue>();
101

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

104
    readonly showShortcut = model<boolean>();
105

152✔
106
    readonly shortcutPresets = model<CompatiblePresets>();
94✔
107

91✔
108
    readonly shortcutPosition = input<ThyShortcutPosition>();
109

94✔
110
    readonly flexible = input(false, { transform: coerceBooleanProperty });
111

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

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

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

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

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

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

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

126
    readonly showTimePickerChange = output<boolean>();
127

104✔
128
    readonly dateValueChange = output<ThyDateChangeEvent>();
129

130
    prefixCls = 'thy-calendar';
131

132
    showTimePicker = false;
64✔
133

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

133✔
136
    activeDate: TinyDate | TinyDate[];
11✔
137

138
    selectedValue: TinyDate[] = []; // Range ONLY
139

122✔
140
    hoverValue: TinyDate[] = []; // Range ONLY
141

142
    advancedSelectedValue: RangeAdvancedValue; // advanced ONLY
143

144
    flexibleActiveTab: DatePickerFlexibleTab = 'advanced';
145

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

509✔
148
    [property: string]: any;
509✔
149

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

152
    innerShortcutPresets: ThyShortcutPreset[];
509✔
153

246✔
154
    readonly disableTimeConfirm = signal(false);
208✔
155

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

609
            return date;
610
        }
611
    }
612

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

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

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

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

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