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

atinc / ngx-tethys / 18d04333-bffc-4369-a760-736c9b3f2b43

22 Dec 2023 09:34AM UTC coverage: 90.34% (+0.008%) from 90.332%
18d04333-bffc-4369-a760-736c9b3f2b43

push

circleci

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

5338 of 6569 branches covered (0.0%)

Branch coverage included in aggregate %.

11 of 16 new or added lines in 3 files covered. (68.75%)

4 existing lines in 3 files now uncovered.

13272 of 14031 relevant lines covered (94.59%)

976.69 hits per line

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

86.79
/src/date-picker/abstract-picker.directive.ts
1
import { InputBoolean, InputNumber, ThyPlacement } from 'ngx-tethys/core';
2
import { ThyPopover, ThyPopoverConfig } from 'ngx-tethys/popover';
3
import { coerceBooleanProperty, FunctionProp, warnDeprecation } from 'ngx-tethys/util';
4
import { fromEvent, Observable, Subject } from 'rxjs';
5
import { debounceTime, mapTo, takeUntil, tap } from 'rxjs/operators';
6

7
import { coerceArray } from '@angular/cdk/coercion';
8
import {
9
    AfterViewInit,
10
    ChangeDetectorRef,
11
    Directive,
12
    ElementRef,
13
    EventEmitter,
14
    Input,
15
    OnChanges,
1✔
16
    OnDestroy,
17
    OnInit,
26✔
18
    Output,
19
    SimpleChange,
20
    TemplateRef
13!
21
} from '@angular/core';
22

23
import { AbstractPickerComponent } from './abstract-picker.component';
13!
24
import { DatePopupComponent } from './lib/popups/date-popup.component';
13✔
25
import { ThyDateChangeEvent, ThyPanelMode, ThyShortcutValueChange } from './standard-types';
26
import { CompatibleValue } from './inner-types';
13✔
27
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
28

29
/**
13!
30
 * @private
13✔
31
 */
32
@Directive()
13✔
33
export abstract class PickerDirective extends AbstractPickerComponent implements OnInit, AfterViewInit, OnDestroy, OnChanges {
34
    showWeek = false;
35

32✔
36
    @Input() thyDateRender: FunctionProp<TemplateRef<Date> | string>;
37

38
    panelMode: ThyPanelMode | ThyPanelMode[];
58!
39

58✔
40
    /**
58✔
41
     * @type EventEmitter<ThyPanelMode | ThyPanelMode[]>
31!
42
     */
43
    @Output() readonly thyOnPanelChange = new EventEmitter<ThyPanelMode | ThyPanelMode[]>();
44

27✔
45
    /**
46
     * @type EventEmitter<Date[]>
58✔
47
     */
48
    @Output() readonly thyOnCalendarChange = new EventEmitter<Date[]>();
49

26✔
50
    private _showTime: object | boolean;
26✔
51
    @Input() get thyShowTime(): object | boolean {
52
        return this._showTime;
53
    }
54
    set thyShowTime(value: object | boolean) {
55
        this._showTime = typeof value === 'object' ? value : coerceBooleanProperty(value);
56
    }
57

58
    /**
59
     * 是否展示时间(时、分)
60
     * @default false
61
     */
62
    @Input() @InputBoolean() thyMustShowTime = false;
63

64
    /**
65
     * 弹出位置
66
     * @type top | topLeft | topRight | bottom | bottomLeft | bottomRight | left | leftTop | leftBottom | right | rightTop | rightBottom
67
     */
68
    @Input() thyPlacement: ThyPlacement = 'bottom';
69

70
    private offset = 4;
71

72
    /**
73
     * 弹出 DatePicker 的偏移量
74
     * @default 4
75
     */
76
    @Input()
77
    @InputNumber()
78
    set thyOffset(value: number) {
79
        if (typeof ngDevMode === 'undefined' || ngDevMode) {
26!
80
            warnDeprecation(`thyOffset parameter will be deprecated, please use thyPopoverOptions instead.`);
26✔
81
        }
26✔
82
        this.offset = value;
26✔
83
    }
15✔
84

10✔
85
    private hasBackdrop = true;
86

26✔
87
    /**
UNCOV
88
     * 是否有幕布
×
89
     * @default true
90
     */
26✔
91
    @Input()
26✔
92
    @InputBoolean()
2✔
93
    set thyHasBackdrop(value: boolean) {
94
        if (typeof ngDevMode === 'undefined' || ngDevMode) {
26✔
95
            warnDeprecation(`thyHasBackdrop parameter will be deprecated, please use thyPopoverOptions instead.`);
7✔
96
        }
97
        this.hasBackdrop = value;
26✔
98
    }
99

100
    /**
21✔
101
     * popover 的其它参数
26✔
102
     */
103
    @Input() thyPopoverOptions: ThyPopoverConfig;
104

8✔
105
    /**
106
     * 是否阻止冒泡
107
     */
108
    @Input() @InputBoolean() thyStopPropagation = true;
7✔
109

110
    private destroy$ = new Subject<void>();
111
    private el: HTMLElement = this.elementRef.nativeElement;
32✔
112
    readonly $click: Observable<boolean> = fromEvent(this.el, 'click').pipe(
26!
113
        tap(e => {
26✔
114
            if (this.thyStopPropagation) {
115
                e.stopPropagation();
116
            }
117
        }),
118
        mapTo(true)
33✔
119
    );
33✔
120

33✔
121
    takeUntilDestroyed = takeUntilDestroyed();
33✔
122

33✔
123
    ngOnInit() {
33✔
124
        this.getInitialState();
33✔
125
    }
33✔
126

33✔
127
    private getInitialState() {
33✔
128
        this.thyMode = this.thyMode || 'date';
33✔
129
        this.flexible = this.thyMode === 'flexible';
33✔
130

33✔
131
        if (this.isRange) {
33✔
132
            this.panelMode = this.flexible ? ['date', 'date'] : [this.thyMode, this.thyMode];
33✔
133
        } else {
28✔
134
            this.panelMode = this.thyMode;
27✔
135
        }
136
        this.showWeek = this.thyMode === 'week';
137
    }
33✔
138

139
    private openOverlay(): void {
140
        this.getInitialState();
32✔
141
        const popoverRef = this.thyPopover.open(
32✔
142
            DatePopupComponent,
143
            Object.assign(
144
                {
33✔
145
                    origin: this.el,
33✔
146
                    hasBackdrop: this.hasBackdrop,
147
                    backdropClass: 'thy-overlay-transparent-backdrop',
148
                    offset: this.offset,
7✔
149
                    outsideClosable: true,
7✔
150
                    initialState: {
7!
151
                        isRange: this.isRange,
7✔
152
                        panelMode: this.panelMode,
153
                        showWeek: this.showWeek,
154
                        value: this.thyValue,
155
                        showTime: this.thyShowTime,
156
                        mustShowTime: this.withTime,
32✔
157
                        format: this.thyFormat,
158
                        dateRender: this.thyDateRender,
159
                        disabledDate: this.thyDisabledDate,
160
                        placeholder: this.thyPlaceHolder,
7!
161
                        className: this.thyPanelClassName,
×
162
                        defaultPickerValue: this.thyDefaultPickerValue,
163
                        minDate: this.thyMinDate,
164
                        maxDate: this.thyMaxDate,
165
                        showShortcut: this.thyShowShortcut,
×
166
                        shortcutPresets: this.shortcutPresets,
167
                        shortcutPosition: this.shortcutPosition,
1✔
168
                        flexible: this.flexible,
169
                        flexibleDateGranularity: this.flexibleDateGranularity
170
                    },
171
                    placement: this.thyPlacement
172
                },
1✔
173
                this.thyPopoverOptions
174
            )
175
        );
176
        if (popoverRef) {
177
            const componentInstance = popoverRef.componentInstance;
178
            componentInstance.valueChange.pipe(takeUntil(this.destroy$)).subscribe((event: CompatibleValue) => this.onValueChange(event));
179
            componentInstance.calendarChange.pipe(takeUntil(this.destroy$)).subscribe((event: CompatibleValue) => {
180
                const rangeValue = coerceArray(event).map(x => x.nativeDate);
181
                this.thyOnCalendarChange.emit(rangeValue);
182
            });
183
            componentInstance.showTimePickerChange
184
                .pipe(takeUntil(this.destroy$))
185
                .subscribe((event: boolean) => this.onShowTimePickerChange(event));
1✔
186
            // eslint-disable-next-line max-len
187
            componentInstance.ngOnChanges({ value: {} as SimpleChange }); // dynamically created components don't call ngOnChanges, manual call
188
            componentInstance.shortcutValueChange?.pipe(takeUntil(this.destroy$)).subscribe((event: ThyShortcutValueChange) => {
189
                this.thyShortcutValueChange.emit(event);
1✔
190
            });
191
            componentInstance.dateValueChange?.pipe(this.takeUntilDestroyed).subscribe((event: ThyDateChangeEvent) => {
192
                this.thyDateChange.emit(event);
193
            });
194
            popoverRef
1✔
195
                .afterOpened()
196
                .pipe(takeUntil(this.destroy$))
197
                .subscribe(() => this.thyOpenChange.emit(true));
198
            popoverRef
199
                .afterClosed()
1✔
200
                .pipe(takeUntil(this.destroy$))
201
                .subscribe(() => this.thyOpenChange.emit(false));
202
        }
203
    }
1✔
204

205
    closeOverlay(): void {
206
        this.thyPopover.close();
207
    }
208

209
    initActionSubscribe(): void {
210
        this.$click.pipe(debounceTime(50), takeUntil(this.destroy$)).subscribe(() => {
211
            if (!this.thyDisabled && !this.thyReadonly) {
212
                this.openOverlay();
213
            }
214
        });
215
    }
216

217
    constructor(public elementRef: ElementRef, public cdr: ChangeDetectorRef, private thyPopover: ThyPopover) {
218
        super(cdr);
219
    }
220

221
    ngAfterViewInit(): void {
222
        this.setDefaultTimePickerState();
223
        this.initActionSubscribe();
224
    }
225

226
    ngOnDestroy(): void {
227
        this.destroy$.next();
228
        this.destroy$.complete();
229
    }
230

231
    onValueChange(value: CompatibleValue): void {
232
        this.restoreTimePickerState(value);
233
        super.onValueChange(value);
234
        if (!this.flexible) {
235
            this.closeOverlay();
236
        }
237
    }
238

239
    // Displays the time directly when the time must be displayed by default
240
    setDefaultTimePickerState() {
241
        this.withTime = this.thyMustShowTime;
242
    }
243

244
    // Restore after clearing time to select whether the original picker time is displayed or not
245
    restoreTimePickerState(value: CompatibleValue | null) {
246
        if (!value) {
247
            this.withTime = this.thyMustShowTime || this.originWithTime;
248
        }
249
    }
250
    onShowTimePickerChange(show: boolean): void {
251
        this.withTime = show;
252
    }
253
}
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