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

atinc / ngx-tethys / #86

11 Aug 2025 06:20AM UTC coverage: 90.315% (+0.01%) from 90.302%
#86

push

web-flow
Merge 934a56b76 into 6d22be5bb

5532 of 6819 branches covered (81.13%)

Branch coverage included in aggregate %.

351 of 379 new or added lines in 20 files covered. (92.61%)

62 existing lines in 12 files now uncovered.

13957 of 14760 relevant lines covered (94.56%)

904.16 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
} from '@angular/core';
34✔
22
import { AbstractPickerComponent } from './abstract-picker.component';
34✔
23
import { DatePopup } from './lib/popups/date-popup.component';
34✔
24
import { ThyDateChangeEvent, ThyPanelMode } from './standard-types';
34✔
25
import { CompatibleValue } from './inner-types';
34✔
26
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
27

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

35
    cdr = inject(ChangeDetectorRef);
15!
36

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

15✔
39
    readonly thyDateRender = input<FunctionProp<TemplateRef<Date> | string>>();
40

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

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

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

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

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

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

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

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

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

15✔
96
    private valueChangeSubscription: OutputRefSubscription;
10✔
97

98
    private calendarChangeSubscription: OutputRefSubscription;
27✔
99

2✔
100
    private showTimePickerChangeSubscription: OutputRefSubscription;
101

27✔
102
    private dateValueChangeSubscription: OutputRefSubscription;
103

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

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

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

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

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

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

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

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

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

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

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

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

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

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