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

atinc / ngx-tethys / d897cc48-e9de-4137-9b5a-30014687a334

21 Dec 2023 06:05AM UTC coverage: 90.337% (+0.001%) from 90.336%
d897cc48-e9de-4137-9b5a-30014687a334

push

circleci

web-flow
feat(date-picker): support thyHasBackdrop for thy-date-picker component #INFR-10885 (#2978) (#2981)

5332 of 6562 branches covered (0.0%)

Branch coverage included in aggregate %.

7 of 7 new or added lines in 3 files covered. (100.0%)

7 existing lines in 2 files now uncovered.

13263 of 14022 relevant lines covered (94.59%)

977.21 hits per line

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

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

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

17
import { AbstractPickerComponent } from './abstract-picker.component';
18
import { CompatibleValue, RangeAdvancedValue } from './inner-types';
19
import { CompatibleDate, ThyPanelMode } from './standard-types';
734✔
20
import { ThyPickerComponent } from './picker.component';
21
import { hasTimeInStringDate, isValidStringDate, parseStringDate, transformDateValue } from './picker.util';
22

480✔
23
/**
24
 * @private
25
 */
64!
26
@Component({
27
    template: ``,
28
    standalone: true,
141✔
29
    host: {
141✔
30
        '[attr.tabindex]': `tabIndex`,
141✔
31
        '(focus)': 'onFocus($event)',
141✔
32
        '(blur)': 'onBlur($event)'
141✔
33
    }
141✔
34
})
141✔
35
export class BasePickerComponent extends AbstractPickerComponent implements OnInit, OnChanges {
141✔
36
    showWeek = false;
141✔
37

38
    panelMode: ThyPanelMode | ThyPanelMode[];
39

141✔
40
    initialized: boolean;
141✔
41

141✔
42
    private innerPreviousDate: string;
43

44
    @ViewChild('thyPicker', { static: true }) thyPicker: ThyPickerComponent;
47✔
45

47✔
46
    @Input() thyDateRender: FunctionProp<TemplateRef<Date> | string>;
47✔
47

47✔
48
    @Input() set thyMode(value: ThyPanelMode) {
39✔
49
        this._panelMode = value ?? 'date';
50
        if (this.initialized) {
47✔
51
            this.setDefaultTimePickerState(this._panelMode);
52
        }
53
    }
13✔
54

5✔
55
    get thyMode() {
5✔
56
        return this._panelMode;
5✔
57
    }
5✔
58

59
    /**
8✔
60
     * 是否有幕布
8✔
61
     * @default true
8!
62
     */
8✔
63
    @Input() @InputBoolean() thyHasBackdrop = true;
5✔
64

65
    /**
66
     * @type EventEmitter<ThyPanelMode | ThyPanelMode[]>
3✔
67
     */
68
    @Output() readonly thyOnPanelChange = new EventEmitter<ThyPanelMode | ThyPanelMode[]>();
8✔
69

8✔
70
    /**
8✔
71
     * @type EventEmitter<Date[]>
72
     */
73
    @Output() readonly thyOnCalendarChange = new EventEmitter<Date[]>();
74

146✔
75
    private _showTime: object | boolean;
146✔
76

58✔
77
    /**
78
     * 增加时间选择功能
79
     * @default false
88✔
80
     */
81
    @Input() get thyShowTime(): object | boolean {
146✔
82
        return this._showTime;
146✔
83
    }
119✔
84
    set thyShowTime(value: object | boolean) {
85
        this._showTime = typeof value === 'object' ? value : coerceBooleanProperty(value);
86
    }
87

119✔
88
    /**
89
     * 是否展示时间(时、分)
119✔
90
     * @default false
91
     */
92
    @Input() @InputBoolean() thyMustShowTime = false;
93

94
    /**
60✔
95
     * 弹出位置
9✔
96
     * @type top | topLeft | topRight | bottom | bottomLeft | bottomRight | left | leftTop | leftBottom | right | rightTop | rightBottom
97
     */
98
    @Input() thyPlacement: ThyPlacement = 'bottomLeft';
99

100
    /**
10!
101
     * @type EventEmitter<CompatibleDate | null>
15✔
102
     */
10✔
103
    @Output() readonly thyOnOk = new EventEmitter<CompatibleDate | null>();
104

105
    constructor(cdr: ChangeDetectorRef, protected element: ElementRef) {
106
        super(cdr);
62✔
107
    }
108

109
    ngOnInit(): void {
5!
UNCOV
110
        super.ngOnInit();
×
UNCOV
111
        this.setDefaultTimePickerState(this._panelMode);
×
UNCOV
112
        this.initialized = true;
×
113
    }
114

115
    onValueChange(value: CompatibleValue | RangeAdvancedValue): void {
×
116
        this.thyPicker.entering = false;
117
        this.restoreTimePickerState(value as CompatibleValue);
118
        super.onValueChange(value);
119
        if (!this.flexible) {
5!
120
            this.closeOverlay();
5✔
121
        }
122
        this.innerPreviousDate = this.thyPicker.getReadableValue(this.thyValue);
UNCOV
123
    }
×
124

125
    onInputValueChange(formatDate: string | null | Array<null>) {
126
        if (!formatDate || !formatDate.length) {
5✔
127
            const compatibleValue = formatDate ? (formatDate as CompatibleValue) : null;
128
            this.restoreTimePickerState(compatibleValue);
129
            super.onValueChange(compatibleValue);
167✔
130
            return;
167✔
131
        }
53✔
132
        let value = formatDate as string;
133
        const valueValid = isValidStringDate(value);
134
        const valueLimitValid = valueValid ? this.isValidDateLimit(parseStringDate(value)) : false;
135
        if (valueValid && valueLimitValid) {
1✔
136
            this.innerPreviousDate = value;
137
        } else {
138
            value = this.innerPreviousDate;
139
        }
5✔
140
        const tinyDate = value ? (this.thyShowTime ? parseStringDate(value) : parseStringDate(value).startOfDay()) : null;
1✔
141
        this.restoreTimePickerState(tinyDate);
142
        super.onValueChange(tinyDate);
4✔
143
    }
144

145
    // Displays the time directly when the time must be displayed by default
13!
146
    setDefaultTimePickerState(value: ThyPanelMode) {
13✔
147
        this.withTime = this.thyMustShowTime;
4✔
148
        if (this.isRange) {
149
            this.panelMode = this.flexible ? ['date', 'date'] : [value, value];
13✔
150
        } else {
151
            this.panelMode = value;
152
        }
153
        this.showWeek = value === 'week';
8✔
154
        if (!this.thyFormat) {
8!
UNCOV
155
            const inputFormats: { [key in ThyPanelMode]?: string } = {
×
156
                year: 'yyyy',
157
                month: 'yyyy-MM',
8✔
158
                week: 'yyyy-ww周',
8✔
159
                date: this.thyShowTime ? 'yyyy-MM-dd HH:mm' : 'yyyy-MM-dd'
8✔
160
            };
161
            this.thyFormat = this.flexible ? inputFormats['date'] : inputFormats[value];
162
        }
163
    }
1✔
164

165
    // Restore after clearing time to select whether the original picker time is displayed or not
166
    restoreTimePickerState(value: CompatibleValue | null) {
167
        if (!value) {
1✔
168
            this.withTime = this.thyMustShowTime || this.originWithTime;
169
        }
170
    }
171

172
    // Emit thyOnCalendarChange when select date by thy-range-picker
173
    onCalendarChange(value: TinyDate[]): void {
174
        if (this.isRange) {
175
            const rangeValue = value.map(x => x.nativeDate);
176
            this.thyOnCalendarChange.emit(rangeValue);
177
        }
178
    }
179

180
    onShowTimePickerChange(show: boolean): void {
1✔
181
        this.withTime = show;
182
    }
183

184
    onResultOk(): void {
1✔
185
        if (this.isRange) {
186
            const value = this.thyValue as TinyDate[];
187
            if (value.length) {
188
                this.thyOnOk.emit([value[0].nativeDate, value[1].nativeDate]);
1✔
189
            } else {
190
                this.thyOnOk.emit([]);
191
            }
192
        } else {
193
            if (this.thyValue) {
194
                this.thyOnOk.emit((this.thyValue as TinyDate).nativeDate);
195
            } else {
196
                this.thyOnOk.emit(null);
197
            }
198
        }
199
        this.closeOverlay();
200
    }
201

202
    onOpenChange(open: boolean): void {
203
        this.thyOpenChange.emit(open);
204
        if (!open) {
205
            this.onTouchedFn();
206
        }
207
    }
208

209
    onFocus(event: Event) {
210
        this.picker.focus();
211
    }
212

213
    onBlur(event?: FocusEvent) {
214
        // Tab 聚焦后自动聚焦到 input 输入框,此分支下直接返回,无需触发 onTouchedFn
215
        if (elementMatchClosest(event?.relatedTarget as HTMLElement, ['date-popup', 'thy-picker'])) {
216
            return;
217
        }
218
        this.onTouchedFn();
219
    }
220

221
    onInputDate(value: string) {
222
        if (value && isValidStringDate(value)) {
223
            if (this.thyShowTime) {
224
                this.withTime = hasTimeInStringDate(value);
225
            }
226
            this.thyValue = parseStringDate(value);
227
        }
228
    }
229

230
    private isValidDateLimit(date: TinyDate): boolean {
231
        let disable = false;
232
        if (this.thyDisabledDate !== undefined) {
233
            disable = this.thyDisabledDate(date.nativeDate);
234
        }
235
        const minDate = this.thyMinDate ? new TinyDate(transformDateValue(this.thyMinDate).value as Date) : null;
236
        const maxDate = this.thyMaxDate ? new TinyDate(transformDateValue(this.thyMaxDate).value as Date) : null;
237
        return (
238
            (!minDate || date.startOfDay().nativeDate >= minDate.startOfDay().nativeDate) &&
239
            (!maxDate || date.startOfDay().nativeDate <= maxDate.startOfDay().nativeDate) &&
240
            !disable
241
        );
242
    }
243
}
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