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

atinc / ngx-tethys / cd64db52-e563-41a3-85f3-a0adb87ce135

30 Oct 2024 08:03AM UTC coverage: 90.402% (-0.04%) from 90.438%
cd64db52-e563-41a3-85f3-a0adb87ce135

push

circleci

web-flow
refactor: refactor constructor to the inject function (#3222)

5503 of 6730 branches covered (81.77%)

Branch coverage included in aggregate %.

422 of 429 new or added lines in 170 files covered. (98.37%)

344 existing lines in 81 files now uncovered.

13184 of 13941 relevant lines covered (94.57%)

997.19 hits per line

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

90.3
/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';
153✔
19

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

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

67!
44
    showWeek = false;
45

46
    panelMode: ThyPanelMode | ThyPanelMode[];
153✔
47

153✔
48
    initialized: boolean;
153✔
49

153!
50
    private innerPreviousDate: string;
153✔
51

52
    @ViewChild('thyPicker', { static: true }) thyPicker: ThyPicker;
53

54
    @Input() thyDateRender: FunctionProp<TemplateRef<Date> | string>;
224✔
55

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

63
    get thyMode() {
64
        return this._panelMode;
65
    }
66

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

47✔
73
    /**
74
     * @type EventEmitter<ThyPanelMode | ThyPanelMode[]>
75
     */
14✔
76
    @Output() readonly thyOnPanelChange = new EventEmitter<ThyPanelMode | ThyPanelMode[]>();
6✔
77

6✔
78
    /**
6✔
79
     * @type EventEmitter<Date[]>
6✔
80
     */
81
    @Output() readonly thyOnCalendarChange = new EventEmitter<Date[]>();
8✔
82

8✔
83
    private _showTime: object | boolean;
8!
84

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

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

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

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

121✔
113
    takeUntilDestroyed = takeUntilDestroyed();
114

115
    thyClickDispatcher = inject(ThyClickDispatcher);
116

117
    platformId = inject(PLATFORM_ID);
61✔
118

10✔
119
    ngZone = inject(NgZone);
120

121
    ngOnInit(): void {
122
        super.ngOnInit();
123
        this.setDefaultTimePickerState(this._panelMode);
10!
124
        this.initialized = true;
15✔
125

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

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

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

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

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

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

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

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

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

240
    onFocus(event: Event) {
241
        this.picker.focus();
242
    }
243

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

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

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