• 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

6.9
/src/popover/popover-container.component.ts
1
import { ThyAbstractOverlayContainer, ThyClickDispatcher, ThyPortalOutlet } from 'ngx-tethys/core';
2
import { from, Observable, timer } from 'rxjs';
3
import { filter, take, takeUntil } from 'rxjs/operators';
4

5
import { AnimationEvent } from '@angular/animations';
6
import { ContentObserver } from '@angular/cdk/observers';
7
import { PortalModule } from '@angular/cdk/portal';
8
import {
9
    AfterViewInit,
10
    ChangeDetectorRef,
11
    Component,
12
    ElementRef,
13
    EventEmitter,
14
    HostListener,
15
    NgZone,
1✔
16
    OnDestroy,
17
    ViewChild,
UNCOV
18
    inject
×
UNCOV
19
} from '@angular/core';
×
UNCOV
20

×
UNCOV
21
import { ThyPopoverConfig } from './popover.config';
×
UNCOV
22
import { popoverAbstractOverlayOptions } from './popover.options';
×
UNCOV
23
import { scaleMotion, scaleXMotion, scaleYMotion } from 'ngx-tethys/core';
×
UNCOV
24

×
25
/**
UNCOV
26
 * @internal
×
27
 */
UNCOV
28
@Component({
×
UNCOV
29
    selector: 'thy-popover-container',
×
UNCOV
30
    templateUrl: './popover-container.component.html',
×
UNCOV
31
    animations: [scaleXMotion, scaleYMotion, scaleMotion],
×
UNCOV
32
    host: {
×
UNCOV
33
        class: 'thy-popover-container',
×
34
        tabindex: '-1',
UNCOV
35
        '[attr.role]': `'popover'`,
×
UNCOV
36
        '[attr.id]': 'id',
×
37
        '[@.disabled]': '!!config.animationDisabled',
38
        '[@scaleXMotion]': '(config.placement === "left" || config.placement === "right") ? animationState : "void"',
39
        '(@scaleXMotion.start)': 'onAnimationStart($event)',
UNCOV
40
        '(@scaleXMotion.done)': 'onAnimationDone($event)',
×
UNCOV
41
        '[@scaleYMotion]': '(config.placement === "top" || config.placement === "bottom") ? animationState : "void"',
×
UNCOV
42
        '(@scaleYMotion.start)': 'onAnimationStart($event)',
×
43
        '(@scaleYMotion.done)': 'onAnimationDone($event)',
44
        '[@scaleMotion]':
45
            '(config.placement !== "left" && config.placement !== "right" && config.placement !== "top" && config.placement !== "bottom") ? animationState : "void"',
UNCOV
46
        '(@scaleMotion.start)': 'onAnimationStart($event)',
×
UNCOV
47
        '(@scaleMotion.done)': 'onAnimationDone($event)'
×
UNCOV
48
    },
×
49
    imports: [PortalModule, ThyPortalOutlet]
50
})
51
export class ThyPopoverContainer<TData = unknown> extends ThyAbstractOverlayContainer<TData> implements AfterViewInit, OnDestroy {
52
    private elementRef = inject(ElementRef);
53
    config = inject<ThyPopoverConfig<TData>>(ThyPopoverConfig);
UNCOV
54
    private thyClickDispatcher = inject(ThyClickDispatcher);
×
UNCOV
55
    private contentObserver = inject(ContentObserver);
×
UNCOV
56
    private ngZone = inject(NgZone);
×
UNCOV
57

×
UNCOV
58
    @ViewChild(ThyPortalOutlet, { static: true })
×
59
    portalOutlet: ThyPortalOutlet;
60

61
    /** State of the popover animation. */
UNCOV
62
    animationState: 'void' | 'enter' | 'exit' = 'enter';
×
63

64
    /** Emits when an animation state changes. */
65
    animationStateChanged = new EventEmitter<AnimationEvent>();
66

67
    animationOpeningDone: Observable<AnimationEvent>;
68
    animationClosingDone: Observable<AnimationEvent>;
69

70
    insideClicked = new EventEmitter();
71

72
    updatePosition = new EventEmitter();
73

74
    outsideClicked = new EventEmitter();
UNCOV
75

×
76
    beforeAttachPortal(): void {}
77

78
    constructor() {
UNCOV
79
        const changeDetectorRef = inject(ChangeDetectorRef);
×
80

81
        super(popoverAbstractOverlayOptions, changeDetectorRef);
UNCOV
82

×
83
        this.animationOpeningDone = this.animationStateChanged.pipe(
84
            filter((event: AnimationEvent) => {
UNCOV
85
                return event.phaseName === 'done' && event.toState === 'enter';
×
86
            })
87
        );
UNCOV
88
        this.animationClosingDone = this.animationStateChanged.pipe(
×
UNCOV
89
            filter((event: AnimationEvent) => {
×
90
                return event.phaseName === 'done' && event.toState === 'exit';
91
            })
92
        );
UNCOV
93
    }
×
UNCOV
94

×
UNCOV
95
    ngAfterViewInit() {
×
UNCOV
96
        if (this.config.outsideClosable && !this.config.hasBackdrop) {
×
97
            timer(0).subscribe(() => {
98
                this.thyClickDispatcher
1✔
99
                    .clicked()
1✔
100
                    .pipe(takeUntil(this.animationClosingDone))
101
                    .subscribe((event: MouseEvent) => {
102
                        if (!this.elementRef.nativeElement.contains(event.target as HTMLElement)) {
103
                            this.ngZone.run(() => {
104
                                this.outsideClicked.emit();
1✔
105
                            });
106
                        }
107
                    });
108
            });
109
        }
110

111
        if (this.config.autoAdaptive) {
112
            const onStable$ = this.ngZone.isStable ? from(Promise.resolve()) : this.ngZone.onStable.pipe(take(1));
113
            this.ngZone.runOutsideAngular(() => {
114
                onStable$.pipe(takeUntil(this.containerDestroy)).subscribe(() => {
115
                    this.contentObserver
116
                        .observe(this.elementRef)
117
                        .pipe(takeUntil(this.containerDestroy))
118
                        .subscribe(() => {
119
                            this.updatePosition.emit();
120
                        });
121
                });
122
            });
123
        }
124
    }
125

126
    /** Callback, invoked whenever an animation on the host completes. */
127
    onAnimationDone(event: AnimationEvent) {
128
        // if (event.toState === 'void') {
129
        //     this.trapFocus();
130
        // } else if (event.toState === 'exit') {
131
        //     this.restoreFocus();
132
        // }
133
        this.animationStateChanged.emit(event);
134
    }
135

136
    /** Callback, invoked when an animation on the host starts. */
137
    onAnimationStart(event: AnimationEvent) {
138
        this.animationStateChanged.emit(event);
139
    }
140

141
    startExitAnimation(): void {
142
        this.animationState = 'exit';
143

144
        // Mark the container for check so it can react if the
145
        // view container is using OnPush change detection.
146
        this.changeDetectorRef.markForCheck();
147
    }
148

149
    @HostListener('click', [])
150
    onInsideClick() {
151
        if (this.config.insideClosable) {
152
            this.insideClicked.emit();
153
        }
154
    }
155

156
    ngOnDestroy() {
157
        super.destroy();
158
        this.insideClicked.complete();
159
        this.updatePosition.complete();
160
        this.outsideClicked.complete();
161
    }
162
}
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