• 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

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';
147✔
22
import { getFlexibleAdvancedReadableValue } from './picker.util';
147✔
23
import { ThyDateGranularity } from './standard-types';
24
import { ThyEnterDirective } from 'ngx-tethys/shared';
25

×
26
/**
27
 * @private
28
 */
151✔
29
@Component({
151✔
30
    selector: 'thy-picker',
31
    exportAs: 'thyPicker',
32
    templateUrl: './picker.component.html',
662✔
33
    changeDetection: ChangeDetectionStrategy.OnPush,
34
    standalone: true,
35
    imports: [
294✔
36
        CdkOverlayOrigin,
294✔
37
        ThyInputDirective,
286✔
38
        ThyEnterDirective,
39
        AsyncPipe,
40
        NgTemplateOutlet,
41
        NgIf,
42
        ThyIconComponent,
1,640✔
43
        NgClass,
44
        CdkConnectedOverlay
45
    ]
671✔
46
})
47
export class ThyPickerComponent implements AfterViewInit {
48
    @Input() isRange = false;
145✔
49
    @Input() open: boolean | undefined = undefined;
145✔
50
    @Input() disabled: boolean;
145✔
51
    @Input() placeholder: string | string[];
145✔
52
    @Input() readonly: boolean;
145✔
53
    @Input() allowClear: boolean;
145✔
54
    @Input() autoFocus: boolean;
145✔
55
    @Input() className: string;
145✔
56
    @Input() size: 'sm' | 'xs' | 'lg' | 'md' | 'default';
145✔
57
    @Input() suffixIcon: string;
145✔
58
    @Input() placement: ThyPlacement = 'bottomLeft';
145✔
59
    @Input() flexible: boolean = false;
145✔
60
    @Input() mode: string;
145✔
61
    @Input() @InputBoolean() hasBackdrop: boolean;
145✔
62
    @Output() blur = new EventEmitter<Event>();
145✔
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>();
145✔
66

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

71
    @Input()
55✔
72
    get format() {
73
        return this.innerFormat;
74
    }
5✔
75

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

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

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

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

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

103
    private innerflexibleDateGranularity: ThyDateGranularity;
104

105
    private innerFormat: string;
106

107
    private innerValue: TinyDate | TinyDate[] | null;
103✔
108

53✔
109
    entering = false;
53✔
110

47✔
111
    prefixCls = 'thy-calendar';
112

53✔
113
    animationOpenState = false;
53✔
114

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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