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

atinc / ngx-tethys / 3b40a702-4b4d-4ddb-81a7-a96baae6d682

08 Nov 2024 05:40AM UTC coverage: 90.395% (-0.04%) from 90.431%
3b40a702-4b4d-4ddb-81a7-a96baae6d682

push

circleci

why520crazy
Merge branch 'master' into feat-theme

5503 of 6730 branches covered (81.77%)

Branch coverage included in aggregate %.

424 of 431 new or added lines in 171 files covered. (98.38%)

344 existing lines in 81 files now uncovered.

13150 of 13905 relevant lines covered (94.57%)

999.86 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
    standalone: true,
16✔
38
    imports: [ThyIcon, NgTemplateOutlet]
16✔
39
})
16✔
40
export class ThyBackTop implements OnInit, OnDestroy, OnChanges {
16✔
41
    private doc = inject(DOCUMENT);
34✔
42
    private thyScrollService = inject(ThyScrollService);
10✔
43
    private platform = inject(Platform);
44
    private cdr = inject(ChangeDetectorRef);
45
    private zone = inject(NgZone);
4✔
46

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

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

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

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

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

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

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

16✔
83
    public visible = false;
16✔
84

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

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

96
    private target: HTMLElement | null = null;
97

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

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

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

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

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

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

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

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

© 2026 Coveralls, Inc