Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions projects/carousel/src/lib/carousel/carousel.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { CarouselScrollDirective } from '../carousel-scroll.directive';
@Component({
standalone: true,
template: `
<app-carousel [display]="1" [index]="0">
<app-carousel [display]="1" [indexValue]="0">
<ng-template appCarouselItem>Item 1</ng-template>
<ng-template appCarouselItem>Item 2</ng-template>
<ng-template appCarouselItem>Item 3</ng-template>
Expand Down Expand Up @@ -53,7 +53,7 @@ describe('CarouselComponent', () => {
});

it('should wrap around when reaching the end', () => {
component.index.set(2);
component.indexValue = 2;
fixture.detectChanges();
component.next();
expect(component.index()).toBe(0);
Expand Down
16 changes: 13 additions & 3 deletions projects/carousel/src/lib/carousel/carousel.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
ContentChildren,
HostBinding,
Inject,
Input,
input,
Signal,
signal,
Expand Down Expand Up @@ -34,7 +35,16 @@ import {CarouselDirective} from './carousel.directive';
})
export class CarouselComponent {
readonly display = input<number>(1);
readonly index = input<number>(0);
private readonly _index = signal<number>(0);

readonly index = this._index.asReadonly();

@Input()
set indexValue(value: number) {
if (value !== undefined && value !== null) {
this._index.set(value);
}
}

@ContentChildren(CAROUSEL_ITEM)
readonly items: QueryList<CarouseItem> = new QueryList<CarouseItem>();
Expand Down Expand Up @@ -78,10 +88,10 @@ export class CarouselComponent {
private updateIndex(index: number) {
const screens = Math.ceil(this.items.length / this.display());
if (screens - 1 === this.index()) {
this.index.set(0);
this._index.set(0);
return;
}
this.index.set(clamp(index, 0, screens - 1));
this._index.set(clamp(index, 0, screens - 1));
this.changeDetectorRef.markForCheck();
}

Expand Down
35 changes: 28 additions & 7 deletions projects/carousel/src/lib/carousel/carousel.directive.spec.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,40 @@
import { ElementRef } from '@angular/core';
import { fakeAsync, tick } from '@angular/core/testing';
import { Component, ElementRef } from '@angular/core';
import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { CarouselDirective } from './carousel.directive';

@Component({
standalone: true,
template: `<app-carousel [duration]="50"></app-carousel>`,
imports: [CarouselDirective]
})
class TestComponent { }

describe('CarouselDirective', () => {
let fixture: ComponentFixture<TestComponent>;
let directive: CarouselDirective;
let element: HTMLElement;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [TestComponent]
}).compileComponents();

fixture = TestBed.createComponent(TestComponent);
directive = fixture.debugElement.query(By.directive(CarouselDirective)).injector.get(CarouselDirective);
element = fixture.debugElement.query(By.directive(CarouselDirective)).nativeElement;
fixture.detectChanges();
});

it('emits periodically when enabled', fakeAsync(() => {
const div = document.createElement('div');
const directive = new CarouselDirective(new ElementRef(div));
const values: number[] = [];
directive.duration = 50;
directive.duration = 50; // Set duration via the setter
directive.subscribe(() => values.push(values.length));
div.dispatchEvent(new Event('mouseleave'));
element.dispatchEvent(new Event('mouseleave'));
tick(120);
expect(values.length).toBeGreaterThan(0);

div.dispatchEvent(new Event('mouseenter'));
element.dispatchEvent(new Event('mouseenter'));
const prev = values.length;
tick(60);
expect(values.length).toBe(prev);
Expand Down
32 changes: 16 additions & 16 deletions projects/carousel/src/lib/carousel/carousel.directive.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Directive, ElementRef, Inject, Input, signal, computed} from '@angular/core';
import {Directive, ElementRef, Input, signal, computed, inject} from '@angular/core';
import { toObservable } from '@angular/core/rxjs-interop';
import {
EMPTY,
Expand All @@ -16,33 +16,33 @@ import {
})
export class CarouselDirective extends Observable<unknown> {

private readonly duration = signal(0);
private readonly durationSignal = signal(0);
private readonly running = signal(false);

private readonly output$ = toObservable(
computed(() => ({duration: this.duration(), running: this.running()}))
).pipe(
switchMap(({duration, running}) =>
duration && running ? interval(duration) : EMPTY,
),
);
private readonly elementRef = inject(ElementRef<HTMLElement>);

@Input()
set duration(duration: number) {
this.duration.set(duration);
this.durationSignal.set(duration);
}

constructor(
@Inject(ElementRef) private readonly elementRef: ElementRef<HTMLElement>,
) {
constructor() {
// Create output$ in constructor where injection context is available
const output$ = toObservable(
computed(() => ({duration: this.durationSignal(), running: this.running()}))
).pipe(
switchMap(({duration, running}) =>
duration && running ? interval(duration) : EMPTY,
),
);

super(subscriber => output$.subscribe(subscriber));

merge(
fromEvent(this.elementRef.nativeElement, 'mouseenter').pipe(mapTo(false)),
fromEvent(this.elementRef.nativeElement, 'touchstart').pipe(mapTo(false)),
fromEvent(this.elementRef.nativeElement, 'touchend').pipe(mapTo(true)),
fromEvent(this.elementRef.nativeElement, 'mouseleave').pipe(mapTo(true)),
).subscribe(v => this.running.set(v));

super(subscriber => this.output$.subscribe(subscriber));
}

}