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

atinc / ngx-tethys / #55

30 Jul 2025 07:08AM UTC coverage: 9.866% (-80.4%) from 90.297%
#55

push

why520crazy
feat(empty): add setMessage for update display text #TINFR-2616

92 of 6794 branches covered (1.35%)

Branch coverage included in aggregate %.

2014 of 14552 relevant lines covered (13.84%)

6.15 hits per line

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

1.82
/src/date-picker/base-picker.component.ts
1
import { ThyClickDispatcher, ThyPlacement } from 'ngx-tethys/core';
2
import { elementMatchClosest, FunctionProp, TinyDate } from 'ngx-tethys/util';
3

4
import {
5
    Component,
6
    ElementRef,
7
    EventEmitter,
8
    inject,
9
    Input,
10
    NgZone,
11
    OnChanges,
12
    OnInit,
13
    Output,
14
    PLATFORM_ID,
1✔
15
    TemplateRef,
16
    ViewChild
×
17
} from '@angular/core';
×
18

×
19
import { coerceBooleanProperty } from '@angular/cdk/coercion';
×
20
import { isPlatformBrowser } from '@angular/common';
×
21
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
×
22
import { AbstractPickerComponent } from './abstract-picker.component';
×
23
import { CompatibleValue, RangeAdvancedValue } from './inner-types';
×
24
import { ThyPicker } from './picker.component';
×
25
import { hasTimeInStringDate, isValidStringDate, parseStringDate, transformDateValue } from './picker.util';
×
26
import { CompatibleDate, ThyPanelMode } from './standard-types';
×
27

×
28
/**
×
29
 * @private
30
 */
31
@Component({
×
32
    template: ``,
×
33
    host: {
×
34
        '[attr.tabindex]': `tabIndex`,
35
        '(focus)': 'onFocus($event)',
36
        '(blur)': 'onBlur($event)'
37
    }
×
38
})
39
export class BasePicker extends AbstractPickerComponent implements OnInit, OnChanges {
40
    protected element = inject(ElementRef);
×
41

42
    showWeek = false;
43

×
44
    panelMode: ThyPanelMode | ThyPanelMode[];
45

46
    initialized: boolean;
×
47

×
48
    private innerPreviousDate: string;
×
49

×
50
    @ViewChild('thyPicker', { static: true }) thyPicker: ThyPicker;
×
51

52
    @Input() thyDateRender: FunctionProp<TemplateRef<Date> | string>;
53

54
    @Input() set thyMode(value: ThyPanelMode) {
×
55
        this._panelMode = value ?? 'date';
56
        if (this.initialized) {
57
            this.setDefaultTimePickerState(this._panelMode);
×
58
        }
×
59
    }
×
60

61
    get thyMode() {
62
        return this._panelMode;
63
    }
64

65
    /**
66
     * 是否有幕布
×
67
     * @default true
×
68
     */
×
69
    @Input({ transform: coerceBooleanProperty }) thyHasBackdrop = true;
×
70

×
71
    /**
72
     * @type EventEmitter<ThyPanelMode | ThyPanelMode[]>
×
73
     */
74
    @Output() readonly thyOnPanelChange = new EventEmitter<ThyPanelMode | ThyPanelMode[]>();
75

×
76
    /**
×
77
     * @type EventEmitter<Date[]>
×
78
     */
×
79
    @Output() readonly thyOnCalendarChange = new EventEmitter<Date[]>();
×
80

81
    private _showTime: object | boolean;
×
82

×
83
    /**
×
84
     * 增加时间选择功能
×
85
     * @default false
×
86
     */
87
    @Input() get thyShowTime(): object | boolean {
88
        return this._showTime;
×
89
    }
90
    set thyShowTime(value: object | boolean) {
×
91
        this._showTime = typeof value === 'object' ? value : coerceBooleanProperty(value);
×
92
    }
93

94
    /**
95
     * 是否展示时间(时、分)
×
96
     * @default false
×
97
     */
98
    @Input({ transform: coerceBooleanProperty }) thyMustShowTime = false;
99

100
    /**
×
101
     * 弹出位置
×
102
     * @type top | topLeft | topRight | bottom | bottomLeft | bottomRight | left | leftTop | leftBottom | right | rightTop | rightBottom
×
103
     */
104
    @Input() thyPlacement: ThyPlacement = 'bottomLeft';
105

×
106
    /**
107
     * @type EventEmitter<CompatibleDate | null>
×
108
     */
×
109
    @Output() readonly thyOnOk = new EventEmitter<CompatibleDate | null>();
×
110

111
    takeUntilDestroyed = takeUntilDestroyed();
112

113
    thyClickDispatcher = inject(ThyClickDispatcher);
114

×
115
    platformId = inject(PLATFORM_ID);
116

×
117
    ngZone = inject(NgZone);
118

119
    ngOnInit(): void {
120
        super.ngOnInit();
121
        this.setDefaultTimePickerState(this._panelMode);
×
122
        this.initialized = true;
×
123

124
        if (isPlatformBrowser(this.platformId)) {
125
            this.thyClickDispatcher
126
                .clicked(0)
127
                .pipe(this.takeUntilDestroyed)
×
128
                .subscribe((event: Event) => {
×
129
                    if (
×
130
                        !this.element.nativeElement.contains(event.target) &&
131
                        !this.thyPicker?.overlayContainer?.nativeElement.contains(event.target as Node) &&
132
                        this.realOpenState
133
                    ) {
×
134
                        this.ngZone.run(() => {
135
                            this.closeOverlay();
136
                            this.cdr.markForCheck();
×
137
                        });
×
138
                    }
×
139
                });
×
140
        }
141
    }
142

×
143
    onValueChange(value: CompatibleValue | RangeAdvancedValue): void {
144
        this.thyPicker.entering = false;
145
        this.restoreTimePickerState(value as CompatibleValue);
146
        super.onValueChange(value);
×
147
        if (!this.flexible) {
×
148
            this.closeOverlay();
149
        }
150
        this.innerPreviousDate = this.thyPicker.getReadableValue(this.thyValue);
×
151
    }
152

153
    onInputValueChange(formatDate: string | null | Array<null>) {
×
154
        if (!formatDate || !formatDate.length) {
155
            const compatibleValue = formatDate ? (formatDate as CompatibleValue) : null;
156
            this.restoreTimePickerState(compatibleValue);
×
157
            super.onValueChange(compatibleValue);
×
158
            return;
×
159
        }
160
        let value = formatDate as string;
161
        const valueValid = isValidStringDate(value, this.thyTimeZone);
162
        const valueLimitValid = valueValid ? this.isValidDateLimit(parseStringDate(value, this.thyTimeZone)) : false;
×
163
        if (valueValid && valueLimitValid) {
164
            this.innerPreviousDate = value;
165
        } else {
166
            value = this.innerPreviousDate;
×
167
        }
×
168
        const tinyDate = value
169
            ? this.thyShowTime
×
170
                ? parseStringDate(value, this.thyTimeZone)
171
                : parseStringDate(value, this.thyTimeZone).startOfDay()
172
            : null;
×
173
        this.restoreTimePickerState(tinyDate);
×
174
        super.onValueChange(tinyDate);
×
175
    }
176

×
177
    // Displays the time directly when the time must be displayed by default
178
    setDefaultTimePickerState(value: ThyPanelMode) {
179
        this.withTime = this.thyMustShowTime;
180
        if (this.isRange) {
×
181
            this.panelMode = this.flexible ? ['date', 'date'] : [value, value];
×
182
        } else {
×
183
            this.panelMode = value;
184
        }
×
185
        this.showWeek = value === 'week';
×
186
        if (!this.thyFormat) {
×
187
            const inputFormats: { [key in ThyPanelMode]?: string } = {
188
                year: 'yyyy',
189
                quarter: 'yyyy-qqq',
190
                month: 'yyyy-MM',
1✔
191
                week: this.locale().weekThFormat,
192
                date: this.thyShowTime ? 'yyyy-MM-dd HH:mm' : 'yyyy-MM-dd'
193
            };
194
            this.thyFormat = this.flexible ? inputFormats['date'] : inputFormats[value];
195
        }
196
    }
197

198
    // Restore after clearing time to select whether the original picker time is displayed or not
199
    restoreTimePickerState(value: CompatibleValue | null) {
200
        if (!value) {
201
            this.withTime = this.thyMustShowTime || this.originWithTime;
202
        }
203
    }
1✔
204

205
    // Emit thyOnCalendarChange when select date by thy-range-picker
206
    onCalendarChange(value: TinyDate[]): void {
207
        if (this.isRange) {
208
            const rangeValue = value.map(x => x.nativeDate);
209
            this.thyOnCalendarChange.emit(rangeValue);
210
        }
211
    }
212

213
    onShowTimePickerChange(show: boolean): void {
214
        this.withTime = show;
215
    }
216

217
    onResultOk(): void {
218
        if (this.isRange) {
219
            const value = this.thyValue as TinyDate[];
220
            if (value.length) {
221
                this.thyOnOk.emit([value[0].nativeDate, value[1].nativeDate]);
222
            } else {
223
                this.thyOnOk.emit([]);
224
            }
225
        } else {
226
            if (this.thyValue) {
227
                this.thyOnOk.emit((this.thyValue as TinyDate).nativeDate);
228
            } else {
229
                this.thyOnOk.emit(null);
230
            }
231
        }
232
        this.closeOverlay();
233
    }
234

235
    onOpenChange(open: boolean): void {
236
        this.thyOpenChange.emit(open);
237
        if (!open) {
238
            this.onTouchedFn();
239
        }
240
    }
241

242
    onFocus(event: Event) {
243
        this.picker.focus();
244
    }
245

246
    onBlur(event?: FocusEvent) {
247
        // Tab 聚焦后自动聚焦到 input 输入框,此分支下直接返回,无需触发 onTouchedFn
248
        if (elementMatchClosest(event?.relatedTarget as HTMLElement, ['date-popup', 'thy-picker'])) {
249
            return;
250
        }
251
        this.onTouchedFn();
252
    }
253

254
    onInputDate(value: string) {
255
        if (value && isValidStringDate(value, this.thyTimeZone)) {
256
            if (this.thyShowTime) {
257
                this.withTime = hasTimeInStringDate(value, this.thyTimeZone);
258
            }
259
            this.thyValue = parseStringDate(value, this.thyTimeZone);
260
        }
261
    }
262

263
    private isValidDateLimit(date: TinyDate): boolean {
264
        let disable = false;
265
        if (this.thyDisabledDate !== undefined) {
266
            disable = this.thyDisabledDate(date.nativeDate);
267
        }
268
        const minDate = this.thyMinDate ? new TinyDate(transformDateValue(this.thyMinDate).value as Date, this.thyTimeZone) : null;
269
        const maxDate = this.thyMaxDate ? new TinyDate(transformDateValue(this.thyMaxDate).value as Date, this.thyTimeZone) : null;
270
        return (
271
            (!minDate || date.startOfDay().nativeDate >= minDate.startOfDay().nativeDate) &&
272
            (!maxDate || date.startOfDay().nativeDate <= maxDate.startOfDay().nativeDate) &&
273
            !disable
274
        );
275
    }
276
}
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

© 2026 Coveralls, Inc