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

atinc / ngx-tethys / f1a286ce-ff11-484c-ae71-950dd547bf85

20 May 2025 06:07AM UTC coverage: 90.216% (-0.007%) from 90.223%
f1a286ce-ff11-484c-ae71-950dd547bf85

Pull #3449

circleci

why520crazy
refactor: update @Output to output
Pull Request #3449: refactor(upload): migrate inputs to signal #TINFR-1787

5568 of 6832 branches covered (81.5%)

Branch coverage included in aggregate %.

35 of 36 new or added lines in 3 files covered. (97.22%)

4 existing lines in 3 files now uncovered.

13592 of 14406 relevant lines covered (94.35%)

907.0 hits per line

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

95.31
/src/back-top/back-top.component.ts
1
import {
2
    Component,
3
    OnInit,
4
    ChangeDetectionStrategy,
5
    ViewEncapsulation,
6
    Input,
7
    TemplateRef,
8
    EventEmitter,
9
    Output,
10
    HostBinding,
11
    NgZone,
12
    ChangeDetectorRef,
13
    OnDestroy,
14
    OnChanges,
1✔
15
    ViewChild,
16
    ElementRef,
18✔
17
    numberAttribute,
18
    inject
19
} from '@angular/core';
16✔
20
import { Subject, fromEvent, BehaviorSubject, EMPTY, Observable } from 'rxjs';
16✔
21
import { Platform } from '@angular/cdk/platform';
16✔
22
import { throttleTime, takeUntil, switchMap } from 'rxjs/operators';
16✔
23
import { DOCUMENT, NgTemplateOutlet } from '@angular/common';
16✔
24
import { fadeMotion, ThyScrollService } from 'ngx-tethys/core';
16✔
25
import { ThyIcon } from 'ngx-tethys/icon';
16✔
26

16✔
27
/**
16✔
28
 * 回到顶部组件
16✔
29
 * @name thy-back-top
30
 */
31
@Component({
32
    selector: 'thy-back-top,[thyBackTop]',
33
    templateUrl: './back-top.component.html',
34
    changeDetection: ChangeDetectionStrategy.OnPush,
35
    encapsulation: ViewEncapsulation.None,
16✔
36
    animations: [fadeMotion],
16✔
37
    imports: [ThyIcon, NgTemplateOutlet]
16✔
38
})
16✔
39
export class ThyBackTop implements OnInit, OnDestroy, OnChanges {
16✔
40
    private doc = inject(DOCUMENT);
16✔
41
    private thyScrollService = inject(ThyScrollService);
34✔
42
    private platform = inject(Platform);
10✔
43
    private cdr = inject(ChangeDetectorRef);
44
    private zone = inject(NgZone);
45

4✔
46
    @HostBinding('class.thy-back-top-container') classNames = true;
4✔
47

2✔
48
    /**
49
     * 自定义按钮显示模板
50
     */
51
    @Input() thyTemplate?: TemplateRef<void>;
52

15✔
53
    /**
54
     * 指定对哪个 DOM 元素返回顶部
55
     * @type string | HTMLElement
65✔
56
     * @default window
57
     */
58
    @Input() thyContainer?: string | HTMLElement;
32✔
59

21✔
60
    /**
61
     * 滚动高度达到此参数值才出现 thy-back-top
11✔
62
     * @type number
11✔
63
     */
11✔
64
    @Input({ transform: numberAttribute }) thyVisibilityHeight = 400;
2✔
65

2✔
66
    /**
67
     * 点击按钮的回调函数
68
     */
69
    @Output() readonly thyClick: EventEmitter<boolean> = new EventEmitter();
70

29!
UNCOV
71
    /**
×
72
     * 监听按钮显示状态的回调函数
73
     */
29✔
74
    @Output() public visibleChange: EventEmitter<boolean> = new EventEmitter();
29✔
75

29✔
76
    /** The native `<div class="thy-back-top"></div>` element. */
29✔
77
    @ViewChild('backTop', { static: false })
78
    set backTop(backTop: ElementRef<HTMLElement> | undefined) {
3✔
79
        this.backTop$.next(backTop);
80
    }
81

82
    public visible = false;
16✔
83

16✔
84
    /**
85
     * The subject used to store the native `<div class="thy-back-top"></div>` since
86
     * it's located within the `ngIf` directive. It might be set asynchronously whenever the condition
15✔
87
     * is met. Having subject makes the code reactive and cancellable (e.g. event listeners will be
15✔
88
     * automatically removed and re-added through the `switchMap` below).
14!
89
     */
14✔
90
    private backTop$ = new BehaviorSubject<ElementRef<HTMLElement> | undefined>(undefined);
91

92
    private destroy$ = new Subject<void>();
1✔
93
    private scrollListenerDestroy$ = new Subject<void>();
1✔
94

95
    private target: HTMLElement | null = null;
96

97
    constructor() {
98
        const zone = this.zone;
99

100
        this.backTop$
101
            .pipe(
102
                switchMap(backTop =>
103
                    backTop
1✔
104
                        ? new Observable(subscriber =>
105
                              zone.runOutsideAngular(() => fromEvent(backTop.nativeElement, 'click').subscribe(subscriber))
106
                          )
107
                        : EMPTY
108
                ),
109
                takeUntil(this.destroy$)
110
            )
111
            .subscribe(() => {
112
                this.thyScrollService.scrollTo(this.getTarget(), 0);
113
                if (this.thyClick.observers.length) {
114
                    zone.run(() => this.thyClick.emit(true));
115
                }
116
            });
117
    }
118

119
    ngOnInit(): void {
120
        this.registerScrollEvent();
121
    }
122

123
    private getTarget(): HTMLElement | Window {
124
        return this.target || window;
125
    }
126

127
    private handleScroll(): void {
128
        if (this.visible === this.thyScrollService.getScroll(this.getTarget()) > this.thyVisibilityHeight) {
129
            return;
130
        }
131
        this.visible = !this.visible;
132
        this.cdr.detectChanges();
133
        if (this.visibleChange.observers.length > 0) {
134
            this.zone.run(() => {
135
                this.visibleChange.emit(this.visible);
136
            });
137
        }
138
    }
139

140
    private registerScrollEvent(): void {
141
        if (!this.platform.isBrowser) {
142
            return;
143
        }
144
        this.scrollListenerDestroy$.next();
145
        this.handleScroll();
146
        this.zone.runOutsideAngular(() => {
147
            fromEvent(this.getTarget(), 'scroll', { passive: true })
148
                .pipe(throttleTime(50), takeUntil(this.scrollListenerDestroy$))
149
                .subscribe(() => this.handleScroll());
150
        });
151
    }
152

153
    ngOnDestroy(): void {
154
        this.destroy$.next();
155
        this.scrollListenerDestroy$.next();
156
    }
157

158
    ngOnChanges(changes: any): void {
159
        const { thyContainer } = changes;
160
        if (thyContainer) {
161
            this.target = typeof this.thyContainer === 'string' ? this.doc.querySelector(this.thyContainer) : this.thyContainer;
162
            this.registerScrollEvent();
163
        }
164
    }
165
}
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