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

atinc / ngx-tethys / d9ae709b-3c27-4b69-b125-b8b80b54f90b

pending completion
d9ae709b-3c27-4b69-b125-b8b80b54f90b

Pull #2757

circleci

mengshuicmq
fix: fix code review
Pull Request #2757: feat(color-picker): color-picker support disabled (#INFR-8645)

98 of 6315 branches covered (1.55%)

Branch coverage included in aggregate %.

1 of 1 new or added line in 1 file covered. (100.0%)

2392 of 13661 relevant lines covered (17.51%)

83.12 hits per line

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

5.0
/src/carousel/carousel.component.ts
1
import {
2
    AfterViewInit,
3
    ChangeDetectionStrategy,
4
    ChangeDetectorRef,
5
    Component,
6
    ContentChildren,
7
    ElementRef,
8
    EventEmitter,
9
    Input,
10
    OnInit,
11
    Output,
12
    QueryList,
13
    Renderer2,
14
    ViewChild,
15
    TemplateRef,
16
    ViewEncapsulation,
17
    AfterContentInit,
18
    OnChanges,
1✔
19
    SimpleChanges,
20
    NgZone,
×
21
    OnDestroy
×
22
} from '@angular/core';
×
23
import { Platform } from '@angular/cdk/platform';
×
24
import { InputBoolean, InputNumber } from 'ngx-tethys/core';
×
25
import { ThyCarouselItemDirective } from './carousel-item.directive';
×
26
import {
×
27
    ThyCarouselEngine,
×
28
    ThyDistanceVector,
×
29
    ThyCarouselSwitchData,
×
30
    ThyCarouselEffect,
×
31
    ThyCarouselTrigger,
×
32
    ThyCarouselPause
×
33
} from './typings';
×
34
import { ThyCarouselSlideEngine, ThyCarouselNoopEngine, ThyCarouselFadeEngine } from './engine';
×
35
import { fromEvent, Subject } from 'rxjs';
×
36
import { debounceTime, takeUntil } from 'rxjs/operators';
×
37
import { ThyCarouselService } from './carousel.service';
×
38
import { ThyIconComponent } from 'ngx-tethys/icon';
×
39
import { ThyDotComponent } from 'ngx-tethys/dot';
×
40
import { NgIf, NgTemplateOutlet, NgFor } from '@angular/common';
×
41

×
42
/**
×
43
 * 走马灯组件
×
44
 * @name thy-carousel
45
 */
46
@Component({
×
47
    selector: 'thy-carousel',
×
48
    templateUrl: './carousel.component.html',
×
49
    changeDetection: ChangeDetectionStrategy.OnPush,
×
50
    encapsulation: ViewEncapsulation.None,
×
51
    preserveWhitespaces: false,
×
52
    host: {
×
53
        class: 'thy-carousel'
×
54
    },
×
55
    standalone: true,
×
56
    imports: [NgIf, NgTemplateOutlet, NgFor, ThyDotComponent, ThyIconComponent]
×
57
})
×
58
export class ThyCarouselComponent implements OnInit, AfterViewInit, AfterContentInit, OnChanges, OnDestroy {
59
    /**
×
60
     * @private
61
     */
×
62
    @ContentChildren(ThyCarouselItemDirective) carouselItems!: QueryList<ThyCarouselItemDirective>;
63

64
    /**
65
     * @private
×
66
     */
67
    @ViewChild('carouselWrapper', { static: true }) carouselWrapper: ElementRef<HTMLElement>;
×
68

×
69
    /**
70
     * 是否自动切换,默认 false
×
71
     */
×
72
    @Input() @InputBoolean() thyAutoPlay: boolean = false;
73

×
74
    /**
75
     * 自动切换时间间隔(毫秒)
76
     */
77
    @Input() @InputNumber() thyAutoPlayInterval: number = 3000;
×
78

×
79
    /**
×
80
     * 切换动画样式
81
     * @type slide | fade | noop
×
82
     */
83
    @Input() thyEffect: ThyCarouselEffect = 'slide';
84

×
85
    /**
×
86
     * 是否显示切换指示器
87
     */
88
    @Input() @InputBoolean() thyIndicators = true;
89

×
90
    /**
×
91
     * 指示器 Item 的渲染模板
×
92
     */
×
93
    @Input() thyIndicatorRender?: TemplateRef<{ $implicit: boolean }>;
94

95
    /**
96
     * 是否显示左右切换
97
     */
×
98
    @Input() @InputBoolean() thyControls = true;
×
99

×
100
    /**
101
     * 上一个控制器渲染模板
102
     */
103
    @Input() thyControlPrev?: TemplateRef<any>;
×
104

×
105
    /**
106
     * 下一个控制器渲染模板
×
107
     */
×
108
    @Input() thyControlNext?: TemplateRef<any>;
×
109

×
110
    /**
×
111
     * 是否支持手势滑动
×
112
     */
×
113
    @Input() @InputBoolean() thyTouchable = true;
114

×
115
    /**
×
116
     * 指示点切换的触发条件
×
117
     * @type click | hover
118
     */
119
    @Input() thyTrigger: ThyCarouselTrigger = 'click';
120

×
121
    /**
122
     * 鼠标移动到指示器时是否暂停播放
×
123
     * @type false | hover
124
     */
125
    @Input() thyPause: ThyCarouselPause = 'hover';
×
126

127
    /**
128
     * 触发切换帧之前,返回 `{from: number, to: number}`
×
129
     */
×
130
    @Output() readonly thyBeforeChange = new EventEmitter<ThyCarouselSwitchData>();
131

132
    /**
133
     * 切换帧之后的回调,返回当前帧索引
134
     */
×
135
    @Output() readonly thyAfterChange = new EventEmitter<number>();
×
136

137
    private isDragging = false;
138

139
    private isTransitioning = false;
×
140

×
141
    private pointerVector: ThyDistanceVector = { x: 0, y: 0 };
×
142

143
    private engine: ThyCarouselEngine;
×
144

×
145
    private _trigger$ = new Subject<number>();
146

147
    private _destroy$ = new Subject<void>();
148

×
149
    wrapperDomRect: DOMRect;
×
150

×
151
    activeIndex = 0;
152

153
    wrapperEl: HTMLElement;
154

×
155
    transitionTimer: any = null;
156

157
    playTime: number = 400;
×
158

159
    isPause: boolean = false;
160

×
161
    constructor(
×
162
        protected renderer: Renderer2,
×
163
        private cdr: ChangeDetectorRef,
164
        private ngZone: NgZone,
165
        private readonly carouselService: ThyCarouselService,
×
166
        private readonly platform: Platform
167
    ) {}
168

169
    private moveTo(index: number): void {
170
        if (this.carouselItems && this.carouselItems.length && !this.isTransitioning) {
×
171
            this.setInitialValue();
×
172
            const len = this.carouselItems.length;
×
173
            const from = this.activeIndex;
×
174
            const to = (index + len) % len;
×
175
            this.thyBeforeChange.emit({ from, to });
176
            this.isTransitioning = true;
×
177
            this.engine?.switch(index, this.activeIndex).subscribe(
×
178
                () => {
179
                    this.activeIndex = to;
×
180
                    this.markContentActive(this.activeIndex);
×
181
                    this.scheduleNextTransition();
182
                    this.thyAfterChange.emit(this.activeIndex);
183
                },
×
184
                () => {},
185
                () => {
186
                    this.isTransitioning = false;
187
                }
×
188
            );
×
189
            this.cdr.markForCheck();
×
190
        }
191
    }
×
192

×
193
    private switchEngine(): void {
×
194
        switch (this.thyEffect) {
×
195
            case 'slide':
×
196
                this.engine = new ThyCarouselSlideEngine(this, this.cdr, this.renderer, this.platform);
197
                break;
198
            case 'fade':
199
                this.engine = new ThyCarouselFadeEngine(this, this.cdr, this.renderer, this.platform);
×
200
                break;
×
201
            default:
×
202
                this.engine = new ThyCarouselNoopEngine(this, this.cdr, this.renderer, this.platform);
203
        }
204
    }
205

206
    private markContentActive(index: number) {
×
207
        this.activeIndex = index;
×
208
        this.carouselItems.forEach((carouselContent: ThyCarouselItemDirective, i: number) => {
×
209
            carouselContent.isActive = index === i;
×
210
        });
×
211
        this.cdr.detectChanges();
212
    }
1✔
213

214
    private setInitialValue(): void {
215
        if (this.engine) {
216
            this.engine.initializeCarouselContents(this.carouselItems);
217
        }
218
    }
219

1✔
220
    private scheduleNextTransition(): void {
221
        this.clearScheduledTransition();
222
        if (this.thyAutoPlay && !this.isPause) {
223
            this.transitionTimer = setTimeout(() => {
224
                this.moveTo(this.activeIndex + 1);
225
            }, this.thyAutoPlayInterval);
226
        }
227
    }
228

229
    private clearScheduledTransition(): void {
230
        if (this.transitionTimer) {
231
            clearTimeout(this.transitionTimer);
232
            this.transitionTimer = null;
233
        }
234
    }
235

236
    onDrag(event: TouchEvent | MouseEvent): void {
237
        if (!this.isDragging && !this.isTransitioning && this.thyTouchable) {
1✔
238
            const mouseDownTime = new Date().getTime();
239
            let mouseUpTime: number;
240
            this.clearScheduledTransition();
241
            this.wrapperDomRect = this.wrapperEl.getBoundingClientRect();
1✔
242
            this.carouselService.registerDrag(event).subscribe(
243
                pointerVector => {
244
                    this.renderer.setStyle(this.wrapperEl, 'cursor', 'grabbing');
245
                    this.pointerVector = pointerVector;
1✔
246
                    this.isDragging = true;
247
                    this.engine?.dragging(this.pointerVector, this.wrapperDomRect);
248
                },
249
                () => {},
1✔
250
                () => {
251
                    if (this.isDragging) {
252
                        mouseUpTime = new Date().getTime();
253
                        const holdDownTime = mouseUpTime - mouseDownTime;
1✔
254
                        // Fast enough to switch to the next frame
255
                        // or
256
                        // If the pointerVector is more than one third switch to the next frame
257
                        if (
1✔
258
                            Math.abs(this.pointerVector.x) > this.wrapperDomRect.width / 3 ||
259
                            Math.abs(this.pointerVector.x) / holdDownTime >= 1
260
                        ) {
261
                            this.moveTo(this.pointerVector.x > 0 ? this.activeIndex - 1 : this.activeIndex + 1);
262
                        } else {
263
                            this.moveTo(this.activeIndex);
264
                        }
265
                    }
266
                    this.isDragging = false;
267
                    this.renderer.setStyle(this.wrapperEl, 'cursor', 'grab');
268
                }
269
            );
270
        }
271
    }
272

273
    indicatorHandleClick(index: number): void {
274
        if (this.thyTrigger === 'click') {
275
            this.moveTo(index);
276
        }
277
    }
278

279
    indicatorHandleTrigger(index: number): void {
280
        if (this.thyPause === 'hover') {
281
            this.isPause = true;
282
            this.clearScheduledTransition();
283
        }
284
        if (this.thyTrigger === 'hover') {
285
            this._trigger$.next(index);
286
        }
287
    }
288

289
    indicatorHandleLeave() {
290
        if (this.thyPause === 'hover') {
291
            this.isPause = false;
292
            this.scheduleNextTransition();
293
        }
294
    }
295

296
    next(): void {
297
        this.moveTo(this.activeIndex + 1);
298
    }
299

300
    pre(): void {
301
        this.moveTo(this.activeIndex - 1);
302
    }
303

304
    ngOnInit(): void {
305
        this.wrapperEl = this.carouselWrapper!.nativeElement;
306
        this.ngZone.runOutsideAngular(() => {
307
            fromEvent(window, 'resize')
308
                .pipe(takeUntil(this._destroy$), debounceTime(100))
309
                .subscribe(() => {
310
                    this.engine?.correctionOffset();
311
                });
312
        });
313
    }
314
    ngOnChanges(changes: SimpleChanges) {
315
        const { thyEffect, thyTouchable } = changes;
316
        if (thyEffect && !thyEffect.isFirstChange()) {
317
            this.switchEngine();
318
            this.markContentActive(0);
319
            this.setInitialValue();
320
        }
321

322
        if (thyTouchable && !thyTouchable.isFirstChange()) {
323
            this.renderer.setStyle(this.wrapperEl, 'cursor', thyTouchable.currentValue ? 'grab' : 'default');
324
        }
325

326
        if (!this.thyAutoPlay || !this.thyAutoPlayInterval) {
327
            this.clearScheduledTransition();
328
        } else {
329
            this.scheduleNextTransition();
330
        }
331
    }
332

333
    ngAfterViewInit(): void {
334
        this.carouselItems.changes.subscribe(() => {
335
            this.markContentActive(0);
336
            this.setInitialValue();
337
        });
338
        this.switchEngine();
339
        this.markContentActive(0);
340
        this.setInitialValue();
341

342
        if (!this.thyTouchable) {
343
            this.renderer.setStyle(this.wrapperEl, 'cursor', 'default');
344
        }
345
    }
346

347
    ngAfterContentInit() {
348
        this._trigger$.pipe(takeUntil(this._destroy$), debounceTime(this.playTime)).subscribe(index => {
349
            if (!isNaN(index)) {
350
                this.moveTo(index);
351
            }
352
        });
353
    }
354

355
    ngOnDestroy() {
356
        this.clearScheduledTransition();
357
        this._trigger$.next();
358
        this._trigger$.complete();
359
        this._destroy$.next();
360
        this._destroy$.complete();
361
    }
362
}
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