Skip to content

Commit

Permalink
fix(esl-carousel): rework carousel slide change event with complete a…
Browse files Browse the repository at this point in the history
…nd more clear information
  • Loading branch information
ala-n committed Jul 18, 2024
1 parent 366daec commit 0b5983f
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 33 deletions.
34 changes: 22 additions & 12 deletions src/modules/esl-carousel/core/esl-carousel.events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import type {ESLCarouselDirection, ESLCarouselStaticState} from './nav/esl-carou

/** {@link ESLCarouselSlideEvent} init object */
export interface ESLCarouselSlideEventInit {
/** Current slide index */
current: number;
/** Related slide index (target on pre-event, current on post-event) */
related: number;
/** A list of indexes of slides that were active before the change */
indexesBefore: number[];
/** A list of indexes of slides that are active after the change */
indexesAfter: number[];
/** Direction of slide animation */
direction: ESLCarouselDirection;
/** Auxiliary request attribute that represents object that initiates slide change */
Expand All @@ -21,8 +21,8 @@ export class ESLCarouselSlideEvent extends Event implements ESLCarouselSlideEven
public static readonly AFTER = 'esl:slide-change';

public override readonly target: ESLCarousel;
public readonly current: number;
public readonly related: number;
public readonly indexesBefore: number[];
public readonly indexesAfter: number[];
public readonly direction: ESLCarouselDirection;
public readonly activator?: any;

Expand All @@ -38,14 +38,24 @@ export class ESLCarouselSlideEvent extends Event implements ESLCarouselSlideEven
Object.assign(this, init);
}

/** @returns current slide element */
public get $currentSlide(): HTMLElement | null {
return this.target.slideAt(this.current);
/** @returns current slide index */
public get current(): number {
return this.indexesAfter[0];
}

/** @returns related slide element */
public get $relatedSlide(): HTMLElement | null {
return this.target.slideAt(this.related);
/** @returns related slide index */
public get related(): number {
return this.indexesBefore[0];
}

/** @returns list of slides that are active before the change */
public get $slidesBefore(): HTMLElement[] {
return this.indexesBefore.map((index) => this.target.slideAt(index));
}

/** @returns list of slides that are active after the change */
public get $slidesAfter(): HTMLElement[] {
return this.indexesAfter.map((index) => this.target.slideAt(index));
}

public static create(type: 'BEFORE' | 'AFTER', init: ESLCarouselSlideEventInit): ESLCarouselSlideEvent {
Expand Down
12 changes: 8 additions & 4 deletions src/modules/esl-carousel/core/esl-carousel.renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {memoize} from '../../esl-utils/decorators';
import {isEqual} from '../../esl-utils/misc/object';
import {SyntheticEventTarget} from '../../esl-utils/dom';
import {ESLCarouselSlideEvent} from './esl-carousel.events';
import {calcDirection, normalize} from './nav/esl-carousel.nav.utils';
import {calcDirection, normalize, sequence} from './nav/esl-carousel.nav.utils';

import type {ESLCarousel, ESLCarouselActionParams} from './esl-carousel';
import type {ESLCarouselConfig, ESLCarouselDirection} from './nav/esl-carousel.nav.types';
Expand Down Expand Up @@ -99,8 +99,8 @@ export abstract class ESLCarouselRenderer implements ESLCarouselConfig {
if (!this.$carousel.dispatchEvent(ESLCarouselSlideEvent.create('BEFORE', {
direction,
activator,
current: activeIndex,
related: index
indexesBefore: activeIndexes,
indexesAfter: sequence(index, this.count, this.size)
}))) return;

this.setPreActive(index);
Expand Down Expand Up @@ -131,11 +131,15 @@ export abstract class ESLCarouselRenderer implements ESLCarouselConfig {
/** Sets active slides from passed index **/
public setActive(current: number, event?: Partial<ESLCarouselSlideEventInit>): void {
const related = this.$carousel.activeIndex;
const indexesBefore = this.$carousel.activeIndexes;
const count = Math.min(this.count, this.size);
const indexesAfter = [];

for (let i = 0; i < this.size; i++) {
const position = normalize(i + current, this.size);
const $slide = this.$slides[position];
if (i < count) indexesAfter.push(position);

$slide.toggleAttribute('active', i < count);
$slide.toggleAttribute('pre-active', false);
$slide.toggleAttribute('next', i === count && (this.loop || position !== 0));
Expand All @@ -144,7 +148,7 @@ export abstract class ESLCarouselRenderer implements ESLCarouselConfig {

if (event && typeof event === 'object') {
const direction = event.direction || calcDirection(related, current, this.size);
const details = {...event, direction, current, related};
const details = {...event, direction, indexesBefore, indexesAfter};
this.$carousel.dispatchEvent(ESLCarouselSlideEvent.create('AFTER', details));
}
}
Expand Down
11 changes: 10 additions & 1 deletion src/modules/esl-carousel/core/nav/esl-carousel.nav.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,20 @@ export function normalize(index: number, size: number): number {
return (size + (index % size)) % size;
}

/** @returns normalize slide index according to the carousel mode */
/** @returns normalize first slide index according to the carousel mode */
export function normalizeIndex(index: number, {size, count, loop}: ESLCarouselStaticState): number {
return loop ? normalize(index, size) : Math.max(0, Math.min(size - count, index));
}

/** @returns normalized sequence of slides starting from the current index */
export function sequence(current: number, count: number, size: number): number[] {
const result = [];
for (let i = 0; i < count; i++) {
result.push(normalize(current + i, size));
}
return result;
}

/** Gets count of slides between active and passed considering given direction. */
export function getDistance(from: number, direction: ESLCarouselDirection, {activeIndex, size}: ESLCarouselState): number {
if (direction === 'prev') return normalize(activeIndex - from, size);
Expand Down
23 changes: 23 additions & 0 deletions src/modules/esl-carousel/test/common/esl-carousel.dummy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {ESLCarousel} from '../../core/esl-carousel';
import {ESLCarouselDummyRenderer} from './esl-carousel.dummy.renderer';

export function createDummyCarousel(size: number): {$carousel: ESLCarousel, $slides: HTMLElement[]} {
ESLCarousel.register();
ESLCarouselDummyRenderer.register();

const $carousel = ESLCarousel.create();
const $slides = Array.from({length: size}, () => {
const $el = document.createElement('div');
$el.setAttribute('esl-carousel-slide', '');
return $el;
});

beforeAll(async () => {
document.body.appendChild($carousel);
await ESLCarousel.registered;
$slides.forEach(($slide) => $carousel.addSlide($slide));
});
afterAll(() => document.body.removeChild($carousel));

return {$carousel, $slides};
}
19 changes: 3 additions & 16 deletions src/modules/esl-carousel/test/core/esl-carousel.renderer.test.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,15 @@
import {ESLCarousel} from '../../core/esl-carousel';
import {createDummyCarousel} from '../common/esl-carousel.dummy';
import {ESLCarouselDummyRenderer} from '../common/esl-carousel.dummy.renderer';


jest.mock('../../../esl-utils/dom/ready', () => ({
onDocumentReady: (cb: any) => cb()
}));

describe('ESLCarouselRenderer: base class tests', () => {
ESLCarousel.register();
ESLCarouselDummyRenderer.register();

describe('Slide markers defined correctly (`setActive` method of the base class)', () => {
const $carousel = ESLCarousel.create();
const $slides = Array.from({length: 5}, () => {
const $el = document.createElement('div');
$el.setAttribute('esl-carousel-slide', '');
return $el;
});

beforeAll(async () => {
document.body.appendChild($carousel);
await ESLCarousel.registered;
$slides.forEach(($slide) => $carousel.addSlide($slide));
});
afterAll(() => document.body.removeChild($carousel));
const {$carousel} = createDummyCarousel(5);

test('Carousel size defined correctly', () => {
expect($carousel.size).toBe(5);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import {createDummyCarousel} from '../common/esl-carousel.dummy';
import {ESLCarouselSlideEvent} from '../../core/esl-carousel.events';

jest.mock('../../../esl-utils/dom/ready', () => ({
onDocumentReady: (cb: any) => cb()
}));

describe('ESLCarouselRenderer: Slide change events created correctly', () => {
const beforeEventTrap = jest.fn();
const afterEventTrap = jest.fn();
document.addEventListener(ESLCarouselSlideEvent.BEFORE, beforeEventTrap);
document.addEventListener(ESLCarouselSlideEvent.AFTER, afterEventTrap);

const {$carousel} = createDummyCarousel(5);

describe('3 slides visible, no loop', () => {
beforeEach(() => {
beforeEventTrap.mockReset();
afterEventTrap.mockReset();
});

beforeAll(async () => {
$carousel.count = '3';
$carousel.loop = 'false';
await Promise.resolve();
});

test('ESLCarouselSlideEvent: Initial slide triggered correct events', async () => {
const request = $carousel.renderer.navigate(0, 'next', {activator: 'user'});
expect(beforeEventTrap).toHaveBeenCalledTimes(1);
expect(afterEventTrap).toHaveBeenCalledTimes(0);
expect(beforeEventTrap).toHaveBeenLastCalledWith(expect.objectContaining({
type: ESLCarouselSlideEvent.BEFORE,
indexesAfter: [0, 1, 2],
direction: 'next',
activator: 'user'
}));

await request;
expect(afterEventTrap).toHaveBeenCalledTimes(1);
expect(afterEventTrap).toHaveBeenLastCalledWith(expect.objectContaining({
type: ESLCarouselSlideEvent.AFTER,
indexesAfter: [0, 1, 2],
direction: 'next',
activator: 'user'
}));
});

test('ESLCarouselSlideEvent: correct events triggered in the middle state', async () => {
const request = $carousel.renderer.navigate(1, 'next', {activator: 'user'});
expect(beforeEventTrap).toHaveBeenCalledTimes(1);
expect(afterEventTrap).toHaveBeenCalledTimes(0);
expect(beforeEventTrap).toHaveBeenLastCalledWith(expect.objectContaining({
type: ESLCarouselSlideEvent.BEFORE,
indexesBefore: [0, 1, 2],
indexesAfter: [1, 2, 3],
direction: 'next',
activator: 'user'
}));

await request;
expect(afterEventTrap).toHaveBeenCalledTimes(1);
expect(afterEventTrap).toHaveBeenLastCalledWith(expect.objectContaining({
type: ESLCarouselSlideEvent.AFTER,
indexesBefore: [0, 1, 2],
indexesAfter: [1, 2, 3],
direction: 'next',
activator: 'user'
}));
});

test('ESLCarouselSlideEvent: Last slide triggered correct events', async () => {
const request = $carousel.renderer.navigate(2, 'next', {activator: 'user'});
expect(beforeEventTrap).toHaveBeenCalledTimes(1);
expect(afterEventTrap).toHaveBeenCalledTimes(0);
expect(beforeEventTrap).toHaveBeenLastCalledWith(expect.objectContaining({
type: ESLCarouselSlideEvent.BEFORE,
indexesBefore: [1, 2, 3],
indexesAfter: [2, 3, 4],
direction: 'next',
activator: 'user'
}));

await request;
expect(afterEventTrap).toHaveBeenCalledTimes(1);
expect(afterEventTrap).toHaveBeenLastCalledWith(expect.objectContaining({
type: ESLCarouselSlideEvent.AFTER,
indexesBefore: [1, 2, 3],
indexesAfter: [2, 3, 4],
direction: 'next',
activator: 'user'
}));
});
});
});

0 comments on commit 0b5983f

Please sign in to comment.