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

atinc / ngx-tethys / 44babf5a-ba67-4c9a-b2f0-5ed6c0ed4f4d

22 Dec 2023 06:36AM UTC coverage: 90.345% (+0.01%) from 90.332%
44babf5a-ba67-4c9a-b2f0-5ed6c0ed4f4d

Pull #2985

circleci

minlovehua
fix(date-picker): when the date picker component's thyHasBackdrop value is false, clicking outside should close #INFR-11053
Pull Request #2985: fix(date-picker): when the date picker directive's and component's thyHasBackdrop value is false, clicking outside should close #INFR-11053

5338 of 6569 branches covered (0.0%)

Branch coverage included in aggregate %.

15 of 19 new or added lines in 7 files covered. (78.95%)

5 existing lines in 3 files now uncovered.

13284 of 14043 relevant lines covered (94.6%)

975.88 hits per line

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

94.16
/src/date-picker/picker.component.ts
1
import { getFlexiblePositions, InputBoolean, 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';
148✔
22
import { getFlexibleAdvancedReadableValue } from './picker.util';
148✔
23
import { ThyDateGranularity } from './standard-types';
24
import { ThyEnterDirective } from 'ngx-tethys/shared';
25

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

146✔
67
    @ViewChild('origin', { static: true }) origin: CdkOverlayOrigin;
1✔
68
    @ViewChild(CdkConnectedOverlay, { static: true }) cdkConnectedOverlay: CdkConnectedOverlay;
69
    @ViewChild('pickerInput', { static: true }) pickerInput: ElementRef;
70
    @ViewChild('overlayContainer', { static: false }) overlayContainer: ElementRef<HTMLElement>;
71

56✔
72
    @Input()
73
    get format() {
74
        return this.innerFormat;
5✔
75
    }
5!
76

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

17✔
82
    @Input()
17✔
83
    get flexibleDateGranularity() {
17✔
84
        return this.innerflexibleDateGranularity;
85
    }
86

8!
UNCOV
87
    set flexibleDateGranularity(granularity: ThyDateGranularity) {
×
88
        this.innerflexibleDateGranularity = granularity;
89
        this.updateReadableDate(this.innerValue);
8!
90
    }
8✔
91

92
    @Input()
93
    get value() {
115✔
94
        return this.innerValue;
114✔
95
    }
114!
96

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

104
    private innerflexibleDateGranularity: ThyDateGranularity;
105

106
    private innerFormat: string;
107

105✔
108
    private innerValue: TinyDate | TinyDate[] | null;
54✔
109

54✔
110
    entering = false;
48✔
111

112
    prefixCls = 'thy-calendar';
54✔
113

54✔
114
    animationOpenState = false;
115

116
    overlayOpen = false; // Available when "open"=undefined
117

118✔
118
    overlayPositions = getFlexiblePositions(this.placement, 4);
115✔
119

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

125
    get readonlyState(): boolean {
49✔
126
        return this.isRange || this.readonly || this.mode !== 'date';
127
    }
128

222✔
129
    constructor(private changeDetector: ChangeDetectorRef, private dateHelper: DateHelperService) {}
130

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

×
138
    focus(): void {
139
        this.pickerInput.nativeElement.focus();
140
    }
663✔
141

273✔
142
    onBlur(event: FocusEvent) {
143
        this.blur.emit(event);
390✔
144
        if (this.entering) {
264✔
145
            this.valueChange.emit(this.pickerInput.nativeElement.value);
146
        }
147
        this.entering = false;
126✔
148
    }
149

150
    onInput(event: InputEvent) {
151
        this.entering = true;
152
        const inputValue = (event.target as HTMLElement)['value'];
1,775✔
153
        this.inputChange.emit(inputValue);
154
    }
155

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

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

587✔
179
    hideOverlay(): void {
40✔
180
        if (this.realOpenState) {
181
            this.overlayOpen = false;
547✔
182
            if (!this.realOpenState) {
183
                this.animationOpenState = false;
1✔
184
            }
185
            this.openChange.emit(this.overlayOpen);
186
            this.focus();
187
        }
1✔
188
    }
189

190
    onClickInputBox(): void {
191
        if (!this.disabled && !this.readonly && !this.isOpenHandledByUser()) {
192
            this.showOverlay();
193
        }
194
    }
195

196
    onClickBackdrop(): void {
197
        this.hideOverlay();
198
    }
199

200
    onOverlayDetach(): void {
201
        this.hideOverlay();
202
    }
203

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

208
    onClickClear(event: MouseEvent): void {
209
        event.preventDefault();
210
        event.stopPropagation();
211

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

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

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

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

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

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

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

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