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

atinc / ngx-tethys / 8473d90a-36df-438e-9cee-0c520c37d1f9

16 Nov 2023 01:57PM UTC coverage: 90.193%. Remained the same
8473d90a-36df-438e-9cee-0c520c37d1f9

Pull #2903

circleci

su4g
fix(date-picker): fix date picker input blur event
Pull Request #2903: fix(date-picker): fix date picker input blur event

5274 of 6511 branches covered (0.0%)

Branch coverage included in aggregate %.

1 of 1 new or added line in 1 file covered. (100.0%)

2 existing lines in 1 file now uncovered.

13184 of 13954 relevant lines covered (94.48%)

977.51 hits per line

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

94.12
/src/date-picker/picker.component.ts
1
import { getFlexiblePositions, ThyPlacement } from 'ngx-tethys/core';
2
import { TinyDate } from 'ngx-tethys/util';
3

4
import { CdkConnectedOverlay, CdkOverlayOrigin, ConnectedOverlayPositionChange } from '@angular/cdk/overlay';
5
import {
6
    AfterViewInit,
7
    ChangeDetectionStrategy,
8
    ChangeDetectorRef,
9
    Component,
10
    ElementRef,
11
    EventEmitter,
12
    Input,
13
    Output,
14
    ViewChild
15
} from '@angular/core';
16

1✔
17
import { AsyncPipe, NgClass, NgIf, NgTemplateOutlet } from '@angular/common';
18
import { ThyIconComponent } from 'ngx-tethys/icon';
×
19
import { ThyInputDirective } from 'ngx-tethys/input';
20
import { DateHelperService } from './date-helper.service';
21
import { CompatibleValue, RangePartType } from './inner-types';
144✔
22
import { getFlexibleAdvancedReadableValue } from './picker.util';
144✔
23
import { ThyDateGranularity } from './standard-types';
24
import { ThyEnterDirective } from 'ngx-tethys/shared';
25

×
26
/**
27
 * @private
28
 */
148✔
29
@Component({
148✔
30
    selector: 'thy-picker',
31
    exportAs: 'thyPicker',
32
    templateUrl: './picker.component.html',
641✔
33
    changeDetection: ChangeDetectionStrategy.OnPush,
34
    standalone: true,
35
    imports: [
287✔
36
        CdkOverlayOrigin,
287✔
37
        ThyInputDirective,
279✔
38
        ThyEnterDirective,
39
        AsyncPipe,
40
        NgTemplateOutlet,
41
        NgIf,
42
        ThyIconComponent,
1,591✔
43
        NgClass,
44
        CdkConnectedOverlay
45
    ]
647✔
46
})
47
export class ThyPickerComponent implements AfterViewInit {
48
    @Input() isRange = false;
142✔
49
    @Input() open: boolean | undefined = undefined;
142✔
50
    @Input() disabled: boolean;
142✔
51
    @Input() placeholder: string | string[];
142✔
52
    @Input() readonly: boolean;
142✔
53
    @Input() allowClear: boolean;
142✔
54
    @Input() autoFocus: boolean;
142✔
55
    @Input() className: string;
142✔
56
    @Input() size: 'sm' | 'xs' | 'lg' | 'md' | 'default';
142✔
57
    @Input() suffixIcon: string;
142✔
58
    @Input() placement: ThyPlacement = 'bottomLeft';
142✔
59
    @Input() flexible: boolean = false;
142✔
60
    @Input() mode: string;
142✔
61
    @Output() blur = new EventEmitter<Event>();
142✔
62
    @Output() readonly valueChange = new EventEmitter<TinyDate | TinyDate[] | null>();
142✔
63
    @Output() readonly openChange = new EventEmitter<boolean>(); // Emitted when overlay's open state change
64
    @Output() readonly inputChange = new EventEmitter<string>();
65

142✔
66
    @ViewChild('origin', { static: true }) origin: CdkOverlayOrigin;
142✔
67
    @ViewChild(CdkConnectedOverlay, { static: true }) cdkConnectedOverlay: CdkConnectedOverlay;
1✔
68
    @ViewChild('pickerInput', { static: true }) pickerInput: ElementRef;
69

70
    @Input()
71
    get format() {
54✔
72
        return this.innerFormat;
73
    }
74

5✔
75
    set format(value: string) {
5!
76
        this.innerFormat = value;
5✔
77
        this.updateReadableDate(this.innerValue);
78
    }
5✔
79

80
    @Input()
81
    get flexibleDateGranularity() {
14✔
82
        return this.innerflexibleDateGranularity;
14✔
83
    }
14✔
84

85
    set flexibleDateGranularity(granularity: ThyDateGranularity) {
86
        this.innerflexibleDateGranularity = granularity;
5!
UNCOV
87
        this.updateReadableDate(this.innerValue);
×
88
    }
89

5!
90
    @Input()
5✔
91
    get value() {
92
        return this.innerValue;
93
    }
111✔
94

110✔
95
    set value(value: TinyDate | TinyDate[] | null) {
110!
96
        this.innerValue = value;
110✔
97
        if (!this.entering) {
98
            this.updateReadableDate(this.innerValue);
110✔
99
        }
110✔
100
    }
110!
101

110✔
102
    private innerflexibleDateGranularity: ThyDateGranularity;
103

104
    private innerFormat: string;
105

106
    private innerValue: TinyDate | TinyDate[] | null;
107

101✔
108
    entering = false;
52✔
109

52✔
110
    prefixCls = 'thy-calendar';
46✔
111

112
    animationOpenState = false;
52✔
113

52✔
114
    overlayOpen = false; // Available when "open"=undefined
115

116
    overlayPositions = getFlexiblePositions(this.placement, 4);
117

114✔
118
    get realOpenState(): boolean {
111✔
119
        // The value that really decide the open state of overlay
120
        return this.isOpenHandledByUser() ? !!this.open : this.overlayOpen;
121
    }
122

10✔
123
    get readonlyState(): boolean {
124
        return this.isRange || this.readonly || this.mode !== 'date';
125
    }
47✔
126

127
    constructor(private changeDetector: ChangeDetectorRef, private dateHelper: DateHelperService) {}
128

214✔
129
    ngAfterViewInit(): void {
130
        this.overlayPositions = getFlexiblePositions(this.placement, 4);
131
        if (this.autoFocus) {
5✔
132
            this.focus();
5✔
133
        }
5✔
134
    }
5✔
135

136
    focus(): void {
UNCOV
137
        this.pickerInput.nativeElement.focus();
×
138
    }
139

140
    onBlur(event: FocusEvent) {
641✔
141
        this.blur.emit(event);
255✔
142
        if (this.entering) {
143
            this.valueChange.emit(this.pickerInput.nativeElement.value);
386✔
144
        }
264✔
145
        this.entering = false;
146
    }
147

122✔
148
    onInput(event: InputEvent) {
149
        this.entering = true;
150
        const inputValue = (event.target as HTMLElement)['value'];
151
        this.inputChange.emit(inputValue);
152
    }
2,346✔
153

154
    onEnter() {
155
        if (this.readonlyState) {
156
            return;
618✔
157
        }
262✔
158
        this.valueChange.emit(this.pickerInput.nativeElement.value || this.getReadableValue(new TinyDate(new Date())));
46✔
159
        this.entering = false;
160
    }
161

216✔
162
    showOverlay(): void {
216✔
163
        if (!this.realOpenState) {
216✔
164
            this.overlayOpen = true;
165
            if (this.realOpenState) {
166
                this.animationOpenState = true;
167
            }
356✔
168
            this.openChange.emit(this.overlayOpen);
356✔
169
            setTimeout(() => {
170
                if (this.cdkConnectedOverlay && this.cdkConnectedOverlay.overlayRef) {
171
                    this.cdkConnectedOverlay.overlayRef.updatePosition();
172
                }
641✔
173
            });
174
        }
175
    }
176

177
    hideOverlay(): void {
571✔
178
        if (this.realOpenState) {
571✔
179
            this.overlayOpen = false;
39✔
180
            if (!this.realOpenState) {
181
                this.animationOpenState = false;
532✔
182
            }
183
            this.openChange.emit(this.overlayOpen);
1✔
184
            this.focus();
185
        }
186
    }
187

1✔
188
    onClickInputBox(): void {
189
        if (!this.disabled && !this.readonly && !this.isOpenHandledByUser()) {
190
            this.showOverlay();
191
        }
192
    }
193

194
    onClickBackdrop(): void {
195
        this.hideOverlay();
196
    }
197

198
    onOverlayDetach(): void {
199
        this.hideOverlay();
200
    }
201

202
    onPositionChange(position: ConnectedOverlayPositionChange): void {
203
        this.changeDetector.detectChanges();
204
    }
205

206
    onClickClear(event: MouseEvent): void {
207
        event.preventDefault();
208
        event.stopPropagation();
209

210
        this.innerValue = this.isRange ? [] : null;
211
        this.valueChange.emit(this.innerValue);
212
    }
213

1✔
214
    getPartTypeIndex(partType: RangePartType): number {
215
        return { left: 0, right: 1 }[partType];
216
    }
217

218
    isEmptyValue(value: CompatibleValue | null): boolean {
219
        if (value === null) {
220
            return true;
221
        } else if (this.isRange) {
222
            return !value || !Array.isArray(value) || value.every(val => !val);
223
        } else {
224
            return !value;
225
        }
226
    }
227

228
    // Whether open state is permanently controlled by user himself
229
    isOpenHandledByUser(): boolean {
230
        return this.open !== undefined;
231
    }
232

233
    getReadableValue(tinyDate: TinyDate | TinyDate[]): string | null {
234
        let value: TinyDate;
235
        if (this.isRange) {
236
            if (this.flexible && this.innerflexibleDateGranularity !== 'day') {
237
                return getFlexibleAdvancedReadableValue(tinyDate as TinyDate[], this.innerflexibleDateGranularity);
238
            } else {
239
                const start = tinyDate[0] ? this.dateHelper.format(tinyDate[0].nativeDate, this.innerFormat) : '';
240
                const end = tinyDate[1] ? this.dateHelper.format(tinyDate[1].nativeDate, this.innerFormat) : '';
241
                return start && end ? `${start} ~ ${end}` : null;
242
            }
243
        } else {
244
            value = tinyDate as TinyDate;
245
            return value ? this.dateHelper.format(value.nativeDate, this.innerFormat) : null;
246
        }
247
    }
248

249
    getPlaceholder(): string {
250
        return this.isRange && this.placeholder && Array.isArray(this.placeholder)
251
            ? (this.placeholder as string[]).join(' ~ ')
252
            : (this.placeholder as string);
253
    }
254

255
    private updateReadableDate(setValue: TinyDate | TinyDate[] | null) {
256
        const readableValue = this.getReadableValue(setValue);
257
        if (readableValue === this.pickerInput.nativeElement['value']) {
258
            return;
259
        }
260

261
        this.pickerInput.nativeElement.value = readableValue;
262
    }
263
}
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