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

atinc / ngx-tethys / 68ef226c-f83e-44c1-b8ed-e420a83c5d84

28 May 2025 10:31AM UTC coverage: 10.352% (-80.0%) from 90.316%
68ef226c-f83e-44c1-b8ed-e420a83c5d84

Pull #3460

circleci

pubuzhixing8
chore: xxx
Pull Request #3460: refactor(icon): migrate signal input #TINFR-1476

132 of 6823 branches covered (1.93%)

Branch coverage included in aggregate %.

10 of 14 new or added lines in 1 file covered. (71.43%)

11648 existing lines in 344 files now uncovered.

2078 of 14525 relevant lines covered (14.31%)

6.69 hits per line

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

1.54
/src/core/overlay/overlay.directive.ts
1
import { FocusMonitor } from '@angular/cdk/a11y';
2
import { OverlayRef } from '@angular/cdk/overlay';
3
import { Platform, normalizePassiveListenerOptions } from '@angular/cdk/platform';
4
import { ChangeDetectorRef, ElementRef, NgZone } from '@angular/core';
5
import { SafeAny } from 'ngx-tethys/types';
1✔
6
import { isNumber } from 'ngx-tethys/util';
1✔
7
import { Subject, fromEvent } from 'rxjs';
8
import { take, takeUntil } from 'rxjs/operators';
UNCOV
9

×
10
export type ThyOverlayTrigger = 'hover' | 'focus' | 'click';
11

UNCOV
12
const passiveEventListenerOptions = normalizePassiveListenerOptions({ passive: true });
×
13

UNCOV
14
const longPressTime = 500;
×
UNCOV
15

×
UNCOV
16
export abstract class ThyOverlayDirectiveBase {
×
17
    protected elementRef: ElementRef;
18
    private initialized = false;
19
    /** Trigger Overlay */
UNCOV
20
    protected _trigger: ThyOverlayTrigger = 'click';
×
UNCOV
21
    public get trigger() {
×
22
        return this._trigger;
UNCOV
23
    }
×
UNCOV
24
    public set trigger(value: ThyOverlayTrigger) {
×
25
        this._trigger = value;
26
        // Trigger reinitialize when trigger changed which can't contain first
UNCOV
27
        if (this.initialized) {
×
28
            this.clearEventListeners();
×
29
            this.initialize();
UNCOV
30
        }
×
UNCOV
31
    }
×
32

UNCOV
33
    protected overlayRef: OverlayRef;
×
UNCOV
34
    protected manualListeners = new Map<string, EventListenerOrEventListenerObject>();
×
35
    protected ngUnsubscribe$ = new Subject<void>();
36
    protected focusMonitor: FocusMonitor;
37
    protected platform: Platform;
UNCOV
38
    protected ngZone: NgZone;
×
39
    protected showDelay? = 100;
UNCOV
40
    protected hideDelay? = 100;
×
UNCOV
41
    protected touchendHideDelay? = 0;
×
UNCOV
42
    protected disabled = false;
×
UNCOV
43
    protected showTimeoutId: number | null | SafeAny;
×
UNCOV
44
    protected hideTimeoutId: number | null | SafeAny;
×
UNCOV
45
    protected changeDetectorRef: ChangeDetectorRef;
×
UNCOV
46
    protected isAutoCloseOnMobileTouch: boolean = false;
×
UNCOV
47

×
UNCOV
48
    /**
×
UNCOV
49
     * The overlay keep opened when the mouse moves to the overlay container
×
UNCOV
50
     */
×
UNCOV
51
    protected overlayPin: boolean;
×
UNCOV
52

×
UNCOV
53
    /** create overlay, you can use popover service or overlay*/
×
UNCOV
54
    // abstract createOverlay(): OverlayRef;
×
55

56
    abstract show(delay?: number): void;
UNCOV
57
    abstract hide(delay?: number): void;
×
UNCOV
58

×
UNCOV
59
    private clearEventListeners() {
×
UNCOV
60
        this.manualListeners.forEach((listener, event) => {
×
UNCOV
61
            this.elementRef.nativeElement.removeEventListener(event, listener);
×
62
        });
UNCOV
63
        this.manualListeners.clear();
×
64
        this.focusMonitor.stopMonitoring(this.elementRef);
65
    }
66

UNCOV
67
    private clearTimer() {
×
UNCOV
68
        if (this.showTimeoutId) {
×
UNCOV
69
            clearTimeout(this.showTimeoutId);
×
UNCOV
70
        }
×
71
        if (this.hideTimeoutId) {
72
            clearTimeout(this.hideTimeoutId);
UNCOV
73
        }
×
UNCOV
74

×
75
        if (this.longPressTimeoutId) {
76
            clearTimeout(this.longPressTimeoutId);
UNCOV
77
        }
×
78
    }
79

80
    private touchStartTime: number;
81

UNCOV
82
    private longPressTimeoutId: number | null | SafeAny;
×
UNCOV
83

×
84
    private isTouchMoving: boolean = false;
85

86
    constructor(
UNCOV
87
        elementRef: ElementRef,
×
UNCOV
88
        platform: Platform,
×
89
        focusMonitor: FocusMonitor,
90
        ngZone: NgZone,
91
        overlayPin?: boolean,
92
        changeDetectorRef?: ChangeDetectorRef
93
    ) {
×
94
        this.elementRef = elementRef;
×
95
        this.platform = platform;
96
        this.focusMonitor = focusMonitor;
97
        this.ngZone = ngZone;
×
98
        this.overlayPin = overlayPin;
99
        this.changeDetectorRef = changeDetectorRef;
100
    }
101

102
    initialize() {
UNCOV
103
        this.initialized = true;
×
UNCOV
104
        const element: HTMLElement = this.elementRef.nativeElement;
×
UNCOV
105
        if (!this.platform.IOS && !this.platform.ANDROID) {
×
106
            if (this.trigger === 'hover') {
107
                this.manualListeners
108
                    .set('mouseenter', () => {
×
109
                        this.show();
×
110
                    })
111
                    .set('mouseleave', (event: MouseEvent) => {
112
                        // Delay 100ms to avoid the overlay being closed immediately when the cursor is moved to the overlay container
UNCOV
113
                        this.hide();
×
UNCOV
114
                        const overlayElement: HTMLElement = this.overlayRef && this.overlayRef.overlayElement;
×
UNCOV
115
                        if (overlayElement && this.overlayPin) {
×
UNCOV
116
                            fromEvent(overlayElement, 'mouseenter')
×
117
                                .pipe(take(1))
UNCOV
118
                                .subscribe(() => {
×
119
                                    this.clearTimer();
UNCOV
120
                                    fromEvent(overlayElement, 'mouseleave')
×
UNCOV
121
                                        .pipe(take(1))
×
122
                                        .subscribe(() => {
123
                                            this.hide();
UNCOV
124
                                        });
×
125
                                });
126
                        }
127
                        // if showDelay is too long and mouseleave immediately, overlayRef is not exist, we should clearTimeout
UNCOV
128
                        if (!this.overlayRef) {
×
UNCOV
129
                            this.clearTimer();
×
130
                        }
131
                    });
UNCOV
132
            } else if (this.trigger === 'focus') {
×
UNCOV
133
                this.focusMonitor
×
134
                    .monitor(this.elementRef)
UNCOV
135
                    .pipe(takeUntil(this.ngUnsubscribe$))
×
136
                    .subscribe(origin => {
UNCOV
137
                        // Note that the focus monitor runs outside the Angular zone.
×
UNCOV
138
                        if (!origin) {
×
139
                            this.ngZone.run(() => this.hide(0));
140
                        } else {
141
                            this.ngZone.run(() => this.show());
142
                        }
UNCOV
143
                    });
×
144
                // this.manualListeners.set('focus', () => this.show());
145
                // this.manualListeners.set('blur', () => this.hide());
146
            } else if (this.trigger === 'click') {
UNCOV
147
                this.manualListeners.set('click', () => {
×
148
                    this.show();
149
                });
UNCOV
150
            } else if (typeof ngDevMode === 'undefined' || ngDevMode) {
×
UNCOV
151
                throw new Error(`${this.trigger} is not supporteed, possible values are: hover | focus | click.`);
×
UNCOV
152
            }
×
UNCOV
153
        } else {
×
154
            const touchendListener = () => {
×
155
                const touchEndTime = Date.now();
156
                const touchDuration = touchEndTime - this.touchStartTime;
157

158
                if (touchDuration < longPressTime && !this.isTouchMoving) {
159
                    // tap
160
                    this.handleTouch();
161
                }
162

UNCOV
163
                clearTimeout(this.longPressTimeoutId);
×
164
                this.isTouchMoving = false;
165
            };
UNCOV
166
            // Reserve extensions for mobile in the future
×
UNCOV
167
            this.manualListeners
×
UNCOV
168
                .set('touchend', touchendListener)
×
UNCOV
169
                .set('touchcancel', touchendListener)
×
170
                .set('touchmove', () => {
UNCOV
171
                    this.isTouchMoving = true;
×
UNCOV
172
                    clearTimeout(this.longPressTimeoutId);
×
173
                })
174
                .set('touchstart', () => {
175
                    this.touchStartTime = Date.now();
176
                    this.isTouchMoving = false;
177

178
                    // 设置一个定时器,如果在一定时间内没有触发 touchend 事件,则判断为长按
179
                    this.longPressTimeoutId = setTimeout(() => {
180
                        // long press
181
                        if (!this.isTouchMoving) {
182
                            this.handleTouch();
183
                        }
184
                    }, longPressTime);
185
                });
186
        }
187

188
        this.manualListeners.forEach((listener, event) =>
189
            // Note: since Chrome 56 defaults document level `touchstart` listener to passive.
190
            // Element touch listeners are not passive by default.
191
            // We never call `preventDefault()` on events, so we're safe making them passive.
192
            element.addEventListener(event, listener, passiveEventListenerOptions)
193
        );
194
    }
195

196
    private handleTouch() {
197
        this.show();
198

199
        if (this.isAutoCloseOnMobileTouch) {
200
            setTimeout(
201
                () => {
202
                    this.hide(0);
203
                },
204
                this.touchendHideDelay + (isNumber(this.showDelay) ? this.showDelay : 0)
205
            );
206
        }
207
    }
208

209
    /**
210
     * Marks that the overlay needs to be checked in the next change detection run.
211
     * Mainly used for rendering before positioning a overlay, which
212
     * can be problematic in components with OnPush change detection.
213
     */
214
    markForCheck() {
215
        this.changeDetectorRef?.markForCheck();
216
    }
217

218
    dispose(): void {
219
        this.ngUnsubscribe$.next();
220
        this.ngUnsubscribe$.complete();
221
        if (this.overlayRef) {
222
            this.overlayRef.dispose();
223
        }
224
        this.clearEventListeners();
225
        this.clearTimer();
226
    }
227
}
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