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

atinc / ngx-tethys / 25319d34-36d5-4bd2-8c15-eb64ad20200c

18 Dec 2023 04:00AM UTC coverage: 90.368% (+0.009%) from 90.359%
25319d34-36d5-4bd2-8c15-eb64ad20200c

push

circleci

minlovehua
feat(date-picker): animations

5350 of 6580 branches covered (0.0%)

Branch coverage included in aggregate %.

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

22 existing lines in 2 files now uncovered.

13330 of 14091 relevant lines covered (94.6%)

984.92 hits per line

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

95.06
/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
    OnChanges,
14
    Output,
15
    SimpleChanges,
16
    ViewChild
17
} from '@angular/core';
1✔
18

UNCOV
19
import { AsyncPipe, NgClass, NgIf, NgTemplateOutlet } from '@angular/common';
×
20
import { ThyIconComponent } from 'ngx-tethys/icon';
21
import { ThyInputDirective } from 'ngx-tethys/input';
22
import { DateHelperService } from './date-helper.service';
146✔
23
import { CompatibleValue, RangePartType } from './inner-types';
146✔
24
import { getFlexibleAdvancedReadableValue } from './picker.util';
25
import { ThyDateGranularity } from './standard-types';
UNCOV
26
import { ThyEnterDirective } from 'ngx-tethys/shared';
×
27
import { scaleMotion, scaleXMotion, scaleYMotion } from 'ngx-tethys/core';
28

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

5✔
70
    @ViewChild('origin', { static: true }) origin: CdkOverlayOrigin;
71
    @ViewChild(CdkConnectedOverlay, { static: true }) cdkConnectedOverlay: CdkConnectedOverlay;
72
    @ViewChild('pickerInput', { static: true }) pickerInput: ElementRef;
3✔
73

74
    @Input()
75
    get format() {
76
        return this.innerFormat;
77
    }
144✔
78

144✔
79
    set format(value: string) {
1✔
80
        this.innerFormat = value;
81
        this.updateReadableDate(this.innerValue);
82
    }
83

54✔
84
    @Input()
85
    get flexibleDateGranularity() {
86
        return this.innerflexibleDateGranularity;
5✔
87
    }
5!
88

5✔
89
    set flexibleDateGranularity(granularity: ThyDateGranularity) {
90
        this.innerflexibleDateGranularity = granularity;
5✔
91
        this.updateReadableDate(this.innerValue);
92
    }
93

17✔
94
    @Input()
17✔
95
    get value() {
17✔
96
        return this.innerValue;
97
    }
98

8!
UNCOV
99
    set value(value: TinyDate | TinyDate[] | null) {
×
100
        this.innerValue = value;
101
        if (!this.entering) {
8!
102
            this.updateReadableDate(this.innerValue);
8✔
103
        }
104
    }
105

113✔
106
    private innerflexibleDateGranularity: ThyDateGranularity;
112✔
107

112✔
108
    private innerFormat: string;
112✔
109

112✔
110
    private innerValue: TinyDate | TinyDate[] | null;
112!
111

112✔
112
    entering = false;
113

114
    prefixCls = 'thy-calendar';
115

116
    isShowDatePopup = false;
117

101✔
118
    overlayOpen = false; // Available when "open"=undefined
52✔
119

52✔
120
    overlayPositions = getFlexiblePositions(this.placement, 4);
52✔
121

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

117✔
127
    get readonlyState(): boolean {
128
        return this.isRange || this.readonly || this.mode !== 'date';
129
    }
130

55✔
131
    constructor(private changeDetector: ChangeDetectorRef, private dateHelper: DateHelperService) {}
55✔
132

55✔
133
    ngOnChanges(changes: SimpleChanges): void {
134
        // open by user
135
        if (changes.open && changes.open.currentValue !== undefined) {
136
            if (changes.open.currentValue) {
116✔
137
                this.showDatePopup();
113✔
138
            } else {
139
                this.closeDatePopup();
140
            }
141
        }
10✔
142
    }
143

144
    ngAfterViewInit(): void {
47✔
145
        this.overlayPositions = getFlexiblePositions(this.placement, 4);
146
        if (this.autoFocus) {
147
            this.focus();
218✔
148
        }
149
    }
150

5✔
151
    focus(): void {
5✔
152
        this.pickerInput.nativeElement.focus();
5✔
153
    }
5✔
154

155
    onBlur(event: FocusEvent) {
UNCOV
156
        this.blur.emit(event);
×
157
        if (this.entering) {
158
            this.valueChange.emit(this.pickerInput.nativeElement.value);
159
        }
695✔
160
        this.entering = false;
271✔
161
    }
162

424✔
163
    onInput(event: InputEvent) {
280✔
164
        this.entering = true;
165
        const inputValue = (event.target as HTMLElement)['value'];
166
        this.inputChange.emit(inputValue);
144✔
167
    }
168

169
    onEnter() {
170
        if (this.readonlyState) {
171
            return;
1,720✔
172
        }
173
        this.valueChange.emit(this.pickerInput.nativeElement.value || this.getReadableValue(new TinyDate(new Date())));
174
        this.entering = false;
175
    }
628✔
176

262✔
177
    showOverlay(): void {
46✔
178
        if (!this.realOpenState) {
179
            this.overlayOpen = true;
180
            this.showDatePopup();
216✔
181

216✔
182
            this.openChange.emit(this.overlayOpen);
216✔
183
            setTimeout(() => {
184
                if (this.cdkConnectedOverlay && this.cdkConnectedOverlay.overlayRef) {
185
                    this.cdkConnectedOverlay.overlayRef.updatePosition();
186
                }
366✔
187
            });
366✔
188
        }
189
    }
190

191
    hideOverlay(): void {
695✔
192
        if (this.realOpenState) {
193
            this.overlayOpen = false;
194
            this.closeDatePopup();
195

196
            this.openChange.emit(this.overlayOpen);
581✔
197
            this.focus();
581✔
198
        }
40✔
199
    }
200

541✔
201
    showDatePopup() {
202
        this.isShowDatePopup = true;
1✔
203
        this.changeDetector.markForCheck();
204
    }
205

206
    closeDatePopup() {
1✔
207
        // Delay 200ms before destroying the date-popup, otherwise you will not see the closing animation.
208
        setTimeout(() => {
209
            this.isShowDatePopup = false;
210
            this.changeDetector.markForCheck();
211
        }, 200);
212
    }
213

214
    onClickInputBox(): void {
215
        if (!this.disabled && !this.readonly && !this.isOpenHandledByUser()) {
216
            this.showOverlay();
217
        }
218
    }
219

220
    onClickBackdrop(): void {
221
        this.hideOverlay();
222
    }
223

224
    onOverlayDetach(): void {
225
        this.hideOverlay();
226
    }
227

228
    onPositionChange(position: ConnectedOverlayPositionChange): void {
229
        this.changeDetector.detectChanges();
230
    }
231

232
    onClickClear(event: MouseEvent): void {
1✔
233
        event.preventDefault();
234
        event.stopPropagation();
235

236
        this.innerValue = this.isRange ? [] : null;
237
        this.valueChange.emit(this.innerValue);
238
    }
239

240
    getPartTypeIndex(partType: RangePartType): number {
241
        return { left: 0, right: 1 }[partType];
242
    }
243

244
    isEmptyValue(value: CompatibleValue | null): boolean {
245
        if (value === null) {
246
            return true;
247
        } else if (this.isRange) {
248
            return !value || !Array.isArray(value) || value.every(val => !val);
249
        } else {
250
            return !value;
251
        }
252
    }
253

254
    // Whether open state is permanently controlled by user himself
255
    isOpenHandledByUser(): boolean {
256
        return this.open !== undefined;
257
    }
258

259
    getReadableValue(tinyDate: TinyDate | TinyDate[]): string | null {
260
        let value: TinyDate;
261
        if (this.isRange) {
262
            if (this.flexible && this.innerflexibleDateGranularity !== 'day') {
263
                return getFlexibleAdvancedReadableValue(tinyDate as TinyDate[], this.innerflexibleDateGranularity);
264
            } else {
265
                const start = tinyDate[0] ? this.dateHelper.format(tinyDate[0].nativeDate, this.innerFormat) : '';
266
                const end = tinyDate[1] ? this.dateHelper.format(tinyDate[1].nativeDate, this.innerFormat) : '';
267
                return start && end ? `${start} ~ ${end}` : null;
268
            }
269
        } else {
270
            value = tinyDate as TinyDate;
271
            return value ? this.dateHelper.format(value.nativeDate, this.innerFormat) : null;
272
        }
273
    }
274

275
    getPlaceholder(): string {
276
        return this.isRange && this.placeholder && Array.isArray(this.placeholder)
277
            ? (this.placeholder as string[]).join(' ~ ')
278
            : (this.placeholder as string);
279
    }
280

281
    private updateReadableDate(setValue: TinyDate | TinyDate[] | null) {
282
        const readableValue = this.getReadableValue(setValue);
283
        if (readableValue === this.pickerInput.nativeElement['value']) {
284
            return;
285
        }
286

287
        this.pickerInput.nativeElement.value = readableValue;
288
    }
289
}
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