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

atinc / ngx-tethys / 82f575a8-2f12-4689-80e6-398f6f1685a3

15 Aug 2024 08:19AM UTC coverage: 90.473%. Remained the same
82f575a8-2f12-4689-80e6-398f6f1685a3

push

circleci

web-flow
build: bump prettier to 3.3.3 and other deps, refactor notify use @if (#3152)

5502 of 6726 branches covered (81.8%)

Branch coverage included in aggregate %.

112 of 116 new or added lines in 57 files covered. (96.55%)

59 existing lines in 15 files now uncovered.

13254 of 14005 relevant lines covered (94.64%)

997.41 hits per line

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

90.36
/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
    ChangeDetectorRef,
6
    Component,
7
    ElementRef,
8
    EventEmitter,
9
    Input,
10
    NgZone,
11
    OnChanges,
12
    OnInit,
13
    Output,
14
    PLATFORM_ID,
1✔
15
    TemplateRef,
16
    ViewChild,
153✔
17
    inject
153✔
18
} from '@angular/core';
6✔
19

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

67!
29
/**
30
 * @private
31
 */
153✔
32
@Component({
153✔
33
    template: ``,
153✔
34
    standalone: true,
153✔
35
    host: {
153✔
36
        '[attr.tabindex]': `tabIndex`,
153✔
37
        '(focus)': 'onFocus($event)',
153✔
38
        '(blur)': 'onBlur($event)'
153✔
39
    }
153✔
40
})
153✔
41
export class BasePicker extends AbstractPickerComponent implements OnInit, OnChanges {
153✔
42
    showWeek = false;
153✔
43

153✔
44
    panelMode: ThyPanelMode | ThyPanelMode[];
45

46
    initialized: boolean;
153✔
47

153✔
48
    private innerPreviousDate: string;
153✔
49

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

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

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

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

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

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

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

81
    private _showTime: object | boolean;
8✔
82

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

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

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

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

121✔
111
    takeUntilDestroyed = takeUntilDestroyed();
112

121✔
113
    thyClickDispatcher = inject(ThyClickDispatcher);
114

115
    platformId = inject(PLATFORM_ID);
116

117
    ngZone = inject(NgZone);
61✔
118

10✔
119
    constructor(
120
        cdr: ChangeDetectorRef,
121
        protected element: ElementRef
122
    ) {
123
        super(cdr);
10!
124
    }
15✔
125

10✔
126
    ngOnInit(): void {
127
        super.ngOnInit();
128
        this.setDefaultTimePickerState(this._panelMode);
129
        this.initialized = true;
66✔
130

131
        if (isPlatformBrowser(this.platformId)) {
132
            this.thyClickDispatcher
5!
UNCOV
133
                .clicked(0)
×
UNCOV
134
                .pipe(this.takeUntilDestroyed)
×
UNCOV
135
                .subscribe((event: Event) => {
×
136
                    if (
137
                        !this.element.nativeElement.contains(event.target) &&
138
                        !this.thyPicker?.overlayContainer?.nativeElement.contains(event.target as Node) &&
×
139
                        this.realOpenState
140
                    ) {
141
                        this.ngZone.run(() => {
142
                            this.closeOverlay();
5!
143
                            this.cdr.markForCheck();
5✔
144
                        });
145
                    }
UNCOV
146
                });
×
147
        }
148
    }
149

5✔
150
    onValueChange(value: CompatibleValue | RangeAdvancedValue): void {
151
        this.thyPicker.entering = false;
152
        this.restoreTimePickerState(value as CompatibleValue);
177✔
153
        super.onValueChange(value);
177✔
154
        if (!this.flexible) {
55✔
155
            this.closeOverlay();
156
        }
157
        this.innerPreviousDate = this.thyPicker.getReadableValue(this.thyValue);
158
    }
1✔
159

160
    onInputValueChange(formatDate: string | null | Array<null>) {
161
        if (!formatDate || !formatDate.length) {
162
            const compatibleValue = formatDate ? (formatDate as CompatibleValue) : null;
5✔
163
            this.restoreTimePickerState(compatibleValue);
1✔
164
            super.onValueChange(compatibleValue);
165
            return;
4✔
166
        }
167
        let value = formatDate as string;
168
        const valueValid = isValidStringDate(value);
13!
169
        const valueLimitValid = valueValid ? this.isValidDateLimit(parseStringDate(value)) : false;
13✔
170
        if (valueValid && valueLimitValid) {
4✔
171
            this.innerPreviousDate = value;
172
        } else {
13✔
173
            value = this.innerPreviousDate;
174
        }
175
        const tinyDate = value ? (this.thyShowTime ? parseStringDate(value) : parseStringDate(value).startOfDay()) : null;
176
        this.restoreTimePickerState(tinyDate);
8✔
177
        super.onValueChange(tinyDate);
8!
UNCOV
178
    }
×
179

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

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

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

216
    onShowTimePickerChange(show: boolean): void {
217
        this.withTime = show;
218
    }
219

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

238
    onOpenChange(open: boolean): void {
239
        this.thyOpenChange.emit(open);
240
        if (!open) {
241
            this.onTouchedFn();
242
        }
243
    }
244

245
    onFocus(event: Event) {
246
        this.picker.focus();
247
    }
248

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

257
    onInputDate(value: string) {
258
        if (value && isValidStringDate(value)) {
259
            if (this.thyShowTime) {
260
                this.withTime = hasTimeInStringDate(value);
261
            }
262
            this.thyValue = parseStringDate(value);
263
        }
264
    }
265

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