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

atinc / ngx-tethys / #98

12 Aug 2025 07:22AM UTC coverage: 90.345% (+0.004%) from 90.341%
#98

push

web-flow
Merge 3392bd199 into bdfadbbcf

5531 of 6813 branches covered (81.18%)

Branch coverage included in aggregate %.

4 of 4 new or added lines in 2 files covered. (100.0%)

2 existing lines in 1 file now uncovered.

13970 of 14772 relevant lines covered (94.57%)

904.11 hits per line

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

92.16
/src/date-picker/abstract-picker.directive.ts
1
import { ThyPlacement } from 'ngx-tethys/core';
2
import { ThyPopover, ThyPopoverConfig } from 'ngx-tethys/popover';
3
import { FunctionProp, warnDeprecation } from 'ngx-tethys/util';
4
import { fromEvent, Observable } from 'rxjs';
5
import { debounceTime, mapTo, tap } from 'rxjs/operators';
6
import { coerceArray, coerceBooleanProperty } from '@angular/cdk/coercion';
7
import {
8
    AfterViewInit,
9
    ChangeDetectorRef,
10
    Directive,
11
    ElementRef,
12
    OnChanges,
13
    OnInit,
14
    output,
15
    SimpleChange,
1✔
16
    TemplateRef,
17
    numberAttribute,
34✔
18
    inject,
34✔
19
    OutputRefSubscription,
34✔
20
    input,
34✔
21
    OnDestroy
34✔
22
} from '@angular/core';
34✔
23
import { AbstractPickerComponent } from './abstract-picker.component';
34✔
24
import { DatePopup } from './lib/popups/date-popup.component';
34✔
25
import { ThyDateChangeEvent, ThyPanelMode } from './standard-types';
34✔
26
import { CompatibleValue } from './inner-types';
27
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
15!
28

15✔
29
/**
30
 * @private
15✔
31
 */
32
@Directive()
33
export abstract class PickerDirective extends AbstractPickerComponent implements OnInit, OnDestroy, AfterViewInit, OnChanges {
34✔
34
    elementRef = inject(ElementRef);
35

15!
36
    cdr = inject(ChangeDetectorRef);
15✔
37

38
    private thyPopover = inject(ThyPopover);
15✔
39

40
    readonly thyDateRender = input<FunctionProp<TemplateRef<Date> | string>>();
41

34✔
42
    readonly thyOnPanelChange = output<ThyPanelMode | ThyPanelMode[]>();
34✔
43

34✔
44
    readonly thyOnCalendarChange = output<Date[]>();
34✔
45

29✔
46
    /**
28✔
47
     * 弹出位置
48
     * @type top | topLeft | topRight | bottom | bottomLeft | bottomRight | left | leftTop | leftBottom | right | rightTop | rightBottom
49
     */
50
    readonly thyPlacement = input<ThyPlacement>('bottom');
51

33✔
52
    /**
53
     * 弹出 DatePicker 的偏移量
54
     */
27✔
55
    readonly thyOffset = input(4, {
27✔
56
        transform: (value: number) => {
57
            if (typeof ngDevMode === 'undefined' || ngDevMode) {
58
                warnDeprecation(`thyOffset parameter will be deprecated, please use thyPopoverOptions instead.`);
59
            }
60
            return numberAttribute(value);
61
        }
62
    });
63

64
    /**
65
     * 是否有幕布
66
     */
67
    readonly thyHasBackdrop = input(true, {
68
        transform: (value: boolean) => {
69
            if (typeof ngDevMode === 'undefined' || ngDevMode) {
70
                warnDeprecation(`thyOffset parameter will be deprecated, please use thyPopoverOptions instead.`);
71
            }
72
            return coerceBooleanProperty(value);
73
        }
74
    });
75

76
    /**
77
     * popover 的其它参数
78
     */
79
    readonly thyPopoverOptions = input<ThyPopoverConfig>();
80

81
    /**
82
     * 是否阻止冒泡
83
     */
84
    readonly thyStopPropagation = input(true, { transform: coerceBooleanProperty });
85

27!
86
    private el: HTMLElement = this.elementRef.nativeElement;
27✔
87

27✔
88
    readonly $click: Observable<boolean> = fromEvent(this.el, 'click').pipe(
2✔
89
        tap(e => {
90
            if (this.thyStopPropagation()) {
27✔
91
                e.stopPropagation();
27✔
92
            }
2✔
93
        }),
94
        mapTo(true)
27✔
95
    );
15✔
96

10✔
97
    private valueChangeSubscription: OutputRefSubscription;
98

27✔
99
    private calendarChangeSubscription: OutputRefSubscription;
2✔
100

101
    private showTimePickerChangeSubscription: OutputRefSubscription;
27✔
102

103
    private dateValueChangeSubscription: OutputRefSubscription;
27✔
104

27!
UNCOV
105
    ngOnInit() {
×
106
        this.setPanelMode();
107
    }
27✔
108

9✔
109
    private openOverlay(): void {
110
        this.setPanelMode();
27✔
111
        const popoverRef = this.thyPopover.open(
112
            DatePopup,
113
            Object.assign(
15✔
114
                {
27✔
115
                    origin: this.el,
116
                    hasBackdrop: this.thyHasBackdrop(),
117
                    backdropClass: 'thy-overlay-transparent-backdrop',
7✔
118
                    offset: this.thyOffset(),
119
                    outsideClosable: true,
120
                    initialState: {
121
                        isRange: this.isRange,
9✔
122
                        panelMode: this.panelMode,
123
                        showWeek: this.showWeek(),
124
                        value: this.thyValue,
33✔
125
                        showTime: this.thyShowTime(),
27!
126
                        mustShowTime: this.withTime,
27✔
127
                        format: this.thyFormat(),
128
                        dateRender: this.thyDateRender(),
129
                        disabledDate: this.thyDisabledDate(),
130
                        placeholder: this.placeholder(),
131
                        className: this.thyPanelClassName(),
33✔
132
                        defaultPickerValue: this.thyDefaultPickerValue(),
133
                        minDate: this.thyMinDate(),
134
                        maxDate: this.thyMaxDate(),
34✔
135
                        showShortcut: this.thyShowShortcut(),
25✔
136
                        shortcutPresets: this.thyShortcutPresets(),
137
                        shortcutPosition: this.thyShortcutPosition(),
34✔
138
                        flexible: this.flexible(),
25✔
139
                        flexibleDateGranularity: this.flexibleDateGranularity,
140
                        timestampPrecision: this.thyTimestampPrecision()
34✔
141
                    },
25✔
142
                    placement: this.thyPlacement()
143
                },
34✔
144
                this.thyPopoverOptions()
22✔
145
            )
146
        );
147
        if (popoverRef) {
148
            const componentInstance = popoverRef.componentInstance;
9✔
149

9✔
150
            if (this.valueChangeSubscription) {
9!
151
                this.valueChangeSubscription.unsubscribe();
9✔
152
            }
153
            this.valueChangeSubscription = componentInstance.valueChange?.subscribe((event: CompatibleValue) => this.onValueChange(event));
154

UNCOV
155
            if (this.calendarChangeSubscription) {
×
156
                this.calendarChangeSubscription.unsubscribe();
157
            }
1✔
158
            this.calendarChangeSubscription = componentInstance.calendarChange?.subscribe((event: CompatibleValue) => {
159
                const rangeValue = coerceArray(event).map(x => x.nativeDate);
160
                this.thyOnCalendarChange.emit(rangeValue);
161
            });
162

163
            if (this.showTimePickerChangeSubscription) {
164
                this.showTimePickerChangeSubscription.unsubscribe();
165
            }
166
            this.showTimePickerChangeSubscription = componentInstance.showTimePickerChange?.subscribe((event: boolean) =>
167
                this.onShowTimePickerChange(event)
168
            );
1✔
169

170
            // eslint-disable-next-line max-len
171
            componentInstance.ngOnChanges({ value: {} as SimpleChange }); // dynamically created components don't call ngOnChanges, manual call
172

173
            if (this.dateValueChangeSubscription) {
174
                this.dateValueChangeSubscription.unsubscribe();
175
            }
176
            this.dateValueChangeSubscription = componentInstance.dateValueChange?.subscribe((event: ThyDateChangeEvent) => {
177
                this.thyDateChange.emit(event);
178
            });
179

180
            popoverRef
181
                .afterOpened()
182
                .pipe(takeUntilDestroyed(this.destroyRef))
183
                .subscribe(() => this.thyOpenChange.emit(true));
184
            popoverRef
185
                .afterClosed()
186
                .pipe(takeUntilDestroyed(this.destroyRef))
187
                .subscribe(() => this.thyOpenChange.emit(false));
188
        }
189
    }
190

191
    closeOverlay(): void {
192
        this.thyPopover.close();
193
    }
194

195
    initActionSubscribe(): void {
196
        this.$click.pipe(debounceTime(50), takeUntilDestroyed(this.destroyRef)).subscribe(() => {
197
            if (!this.thyDisabled && !this.thyReadonly()) {
198
                this.openOverlay();
199
            }
200
        });
201
    }
202

203
    ngAfterViewInit(): void {
204
        this.initActionSubscribe();
205
    }
206

207
    ngOnDestroy(): void {
208
        if (this.valueChangeSubscription) {
209
            this.valueChangeSubscription.unsubscribe();
210
        }
211
        if (this.calendarChangeSubscription) {
212
            this.calendarChangeSubscription.unsubscribe();
213
        }
214
        if (this.showTimePickerChangeSubscription) {
215
            this.showTimePickerChangeSubscription.unsubscribe();
216
        }
217
        if (this.dateValueChangeSubscription) {
218
            this.dateValueChangeSubscription.unsubscribe();
219
        }
220
    }
221

222
    onValueChange(value: CompatibleValue): void {
223
        this.restoreTimePickerState(value);
224
        super.onValueChange(value);
225
        if (!this.flexible()) {
226
            this.closeOverlay();
227
        }
228
    }
229

230
    onShowTimePickerChange(show: boolean): void {
231
        this.withTime = show;
232
    }
233
}
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