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

atinc / ngx-tethys / feb6d8cd-0318-4549-939b-afa9a9243fc2

16 Nov 2023 05:40AM UTC coverage: 90.202% (+0.001%) from 90.201%
feb6d8cd-0318-4549-939b-afa9a9243fc2

Pull #2900

circleci

su4g
fix(date-picker): adjust updateReadableDate function
Pull Request #2900: fix(date-picker): adjust updateReadableDate function

5269 of 6504 branches covered (0.0%)

Branch coverage included in aggregate %.

13181 of 13950 relevant lines covered (94.49%)

977.59 hits per line

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

95.09
/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

17
import { AsyncPipe, NgClass, NgIf, NgTemplateOutlet } from '@angular/common';
1✔
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';
22
import { getFlexibleAdvancedReadableValue } from './picker.util';
144✔
23
import { ThyDateGranularity } from './standard-types';
144✔
24
import { ThyEnterDirective } from 'ngx-tethys/shared';
25
import { BehaviorSubject } from 'rxjs';
26

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

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

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

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

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

14✔
86
    set flexibleDateGranularity(granularity: ThyDateGranularity) {
14✔
87
        this.innerflexibleDateGranularity = granularity;
14✔
88
        this.updateReadableDate(this.innerValue);
89
    }
90

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

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

110✔
103
    private innerflexibleDateGranularity: ThyDateGranularity;
110✔
104

110!
105
    private innerFormat: string;
110✔
106

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

109
    entering = false;
110

111
    entered = false;
101✔
112

52✔
113
    readableValue$ = new BehaviorSubject<string | null>(null);
52✔
114

46✔
115
    prefixCls = 'thy-calendar';
116

52✔
117
    animationOpenState = false;
52✔
118

119
    overlayOpen = false; // Available when "open"=undefined
120

121
    overlayPositions = getFlexiblePositions(this.placement, 4);
114✔
122

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

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

132
    constructor(private changeDetector: ChangeDetectorRef, private dateHelper: DateHelperService) {}
214✔
133

134
    ngAfterViewInit(): void {
135
        this.overlayPositions = getFlexiblePositions(this.placement, 4);
5✔
136
        if (this.autoFocus) {
5✔
137
            this.focus();
5✔
138
        }
5✔
139
    }
140

141
    focus(): void {
×
142
        this.pickerInput.nativeElement.focus();
143
    }
144

642✔
145
    onBlur(event: FocusEvent) {
255✔
146
        this.blur.emit(event);
147
        if (this.entering) {
387✔
148
            this.valueChange.emit(this.pickerInput.nativeElement.value);
264✔
149
        }
150
        this.entering = false;
151
    }
123✔
152

153
    onInput(event: InputEvent) {
154
        this.entering = true;
155
        this.entered = true;
156
        const inputValue = (event.target as HTMLElement)['value'];
2,348✔
157
        this.inputChange.emit(inputValue);
158
    }
159

160
    onEnter() {
619✔
161
        if (this.readonlyState) {
262✔
162
            return;
46✔
163
        }
164
        this.valueChange.emit(this.pickerInput.nativeElement.value || this.getReadableValue(new TinyDate(new Date())));
165
        this.entering = false;
216✔
166
    }
216✔
167

216✔
168
    showOverlay(): void {
169
        if (!this.realOpenState) {
170
            this.overlayOpen = true;
171
            if (this.realOpenState) {
357✔
172
                this.animationOpenState = true;
357✔
173
            }
174
            this.openChange.emit(this.overlayOpen);
175
            setTimeout(() => {
176
                if (this.cdkConnectedOverlay && this.cdkConnectedOverlay.overlayRef) {
642✔
177
                    this.cdkConnectedOverlay.overlayRef.updatePosition();
178
                }
179
            });
180
        }
181
    }
571✔
182

571✔
183
    hideOverlay(): void {
37✔
184
        if (this.realOpenState) {
185
            this.overlayOpen = false;
534✔
186
            if (!this.realOpenState) {
4✔
187
                this.animationOpenState = false;
4✔
188
            }
4✔
189
            this.openChange.emit(this.overlayOpen);
4✔
190
            this.focus();
191
        }
192
    }
193

530✔
194
    onClickInputBox(): void {
195
        if (!this.disabled && !this.readonly && !this.isOpenHandledByUser()) {
196
            this.showOverlay();
1✔
197
        }
198
    }
199

200
    onClickBackdrop(): void {
1✔
201
        this.hideOverlay();
202
    }
203

204
    onOverlayDetach(): void {
205
        this.hideOverlay();
206
    }
207

208
    onPositionChange(position: ConnectedOverlayPositionChange): void {
209
        this.changeDetector.detectChanges();
210
    }
211

212
    onClickClear(event: MouseEvent): void {
213
        event.preventDefault();
214
        event.stopPropagation();
215

216
        this.innerValue = this.isRange ? [] : null;
217
        this.valueChange.emit(this.innerValue);
218
    }
219

220
    getPartTypeIndex(partType: RangePartType): number {
221
        return { left: 0, right: 1 }[partType];
222
    }
223

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

234
    // Whether open state is permanently controlled by user himself
235
    isOpenHandledByUser(): boolean {
236
        return this.open !== undefined;
237
    }
238

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

255
    getPlaceholder(): string {
256
        return this.isRange && this.placeholder && Array.isArray(this.placeholder)
257
            ? (this.placeholder as string[]).join(' ~ ')
258
            : (this.placeholder as string);
259
    }
260

261
    private updateReadableDate(setValue: TinyDate | TinyDate[] | null) {
262
        const readableValue = this.getReadableValue(setValue);
263
        if (readableValue === this.pickerInput.nativeElement['value']) {
264
            return;
265
        }
266

267
        if (this.entered) {
268
            this.readableValue$.next(null);
269
            setTimeout(() => {
270
                this.readableValue$.next(readableValue);
271
                this.entered = false;
272
            }, 0);
273
        } else {
274
            this.readableValue$.next(readableValue);
275
        }
276
    }
277
}
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