Skip to content

Commit

Permalink
fix(esl-carousel): direction related issues, cleanup internal nav API
Browse files Browse the repository at this point in the history
  • Loading branch information
ala-n committed Jul 31, 2024
1 parent 878400d commit 30cef21
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 44 deletions.
2 changes: 1 addition & 1 deletion src/modules/esl-carousel/core/esl-carousel.events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export interface ESLCarouselSlideEventInit {
/** A list of indexes of slides that are active after the change */
indexesAfter: number[];
/** Direction of slide animation */
direction: ESLCarouselDirection;
direction: ESLCarouselDirection | null;
/** Auxiliary request attribute that represents object that initiates slide change */
activator?: any;
}
Expand Down
4 changes: 2 additions & 2 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, sequence} from './nav/esl-carousel.nav.utils';
import {normalize, sequence, indexToDirection} 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 @@ -155,7 +155,7 @@ export abstract class ESLCarouselRenderer implements ESLCarouselConfig {
}

if (event && typeof event === 'object') {
const direction = event.direction || calcDirection(related, current, this.size);
const direction = event.direction || indexToDirection(related, this.$carousel.state);
const details = {...event, direction, indexesBefore, indexesAfter};
this.$carousel.dispatchEvent(ESLCarouselSlideEvent.create('AFTER', details));
}
Expand Down
6 changes: 3 additions & 3 deletions src/modules/esl-carousel/core/esl-carousel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,9 +301,9 @@ export class ESLCarousel extends ESLBaseElement {
public goTo(target: HTMLElement | ESLCarouselSlideTarget, params: ESLCarouselActionParams = {}): Promise<void> {
if (target instanceof HTMLElement) return this.goTo(this.indexOf(target), params);
if (!this.renderer) return Promise.reject();
const {index, dir} = toIndex(target, this.state);
const direction = params.direction || dir || 'next';
return this.renderer.navigate(index, direction, params);
const nav = toIndex(target, this.state);
const direction = (this.loop ? params.direction : null) || nav.direction || 'next';
return this.renderer.navigate(nav.index, direction, params);
}

/** @returns slide by index (supports not normalized indexes) */
Expand Down
14 changes: 14 additions & 0 deletions src/modules/esl-carousel/core/nav/esl-carousel.nav.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,31 @@ export type ESLCarouselSlideTarget = string | ESLCarouselNavIndex | `slide:${ESL

/** Object describing static carousel configuration */
export type ESLCarouselStaticState = {
/** Total slide count */
size: number;
/** Visible slide count per view */
count: number;
/** Cyclic carousel rendering mode */
loop: boolean;
/** Vertical carousel rendering mode */
vertical: boolean;
};

export type ESLCarouselConfig = ESLCarouselStaticState & {
/** Renderer type name */
type: string;
};

/** Object describing carousel current configuration (contains active slide data) */
export type ESLCarouselState = ESLCarouselStaticState & {
/** First active slide index */
activeIndex: number;
};

/** Object describing carousel navigation target details */
export type ESLCarouselNavInfo = {
/** Target index */
index: number;
/** Direction to reach the index */
direction: ESLCarouselDirection | null;
};
45 changes: 23 additions & 22 deletions src/modules/esl-carousel/core/nav/esl-carousel.nav.utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type {
ESLCarouselDirection,
ESLCarouselNavIndex,
ESLCarouselNavInfo,
ESLCarouselSlideTarget,
ESLCarouselState,
ESLCarouselStaticState
Expand Down Expand Up @@ -33,7 +34,7 @@ export function getDistance(from: number, direction: ESLCarouselDirection, {acti
}

/** @returns closest direction to move from slide `from` to slide `to` */
export function calcDirection(from: number, to: number, size: number): ESLCarouselDirection {
function calcDirection(from: number, to: number, size: number): ESLCarouselDirection {
const abs = Math.abs(from - to) % size;
if (to > from) {
return abs > size - abs ? 'prev' : 'next';
Expand Down Expand Up @@ -83,52 +84,52 @@ function splitTarget(target: string): {index: string, type: string} {
}

/** Parses index value defining its value and type (absolute|relative) */
function parseIndex(index: string | ESLCarouselNavIndex): {value: number, isRelative: boolean, dir?: ESLCarouselDirection} {
function parseIndex(index: string | ESLCarouselNavIndex): {value: number, isRelative: boolean, direction?: ESLCarouselDirection} {
if (typeof index === 'number') return {value: index, isRelative: false};
index = index.trim();
if (index === 'next') return {value: 1, isRelative: true, dir: 'next'};
if (index === 'prev') return {value: -1, isRelative: true, dir: 'prev'};
if (index[0] === '+') return {value: +index, isRelative: true, dir: 'next'};
if (index[0] === '-') return {value: +index, isRelative: true, dir: 'prev'};
if (index === 'next') return {value: 1, isRelative: true, direction: 'next'};
if (index === 'prev') return {value: -1, isRelative: true, direction: 'prev'};
if (index[0] === '+') return {value: +index, isRelative: true, direction: 'next'};
if (index[0] === '-') return {value: +index, isRelative: true, direction: 'prev'};
return {value: +index, isRelative: false};
}

/** @returns normalized numeric index from string with absolute or relative index */
function resolveSlideIndex(indexStr: string | ESLCarouselNavIndex, cfg: ESLCarouselState): {index: number, dir: ESLCarouselDirection | null} {
const {value, isRelative, dir} = parseIndex(indexStr);
function resolveSlideIndex(indexStr: string | ESLCarouselNavIndex, cfg: ESLCarouselState): ESLCarouselNavInfo {
const {value, isRelative, direction} = parseIndex(indexStr);
const target = value + (isRelative ? cfg.activeIndex : 0);
const index = normalizeIndex(target, cfg);
return {index, dir: dir || indexToDirection(index, cfg)};
return {index, direction: direction || indexToDirection(index, cfg)};
}

/** @returns normalized numeric index from string with absolute or relative group index */
function resolveGroupIndex(indexStr: string | ESLCarouselNavIndex, cfg: ESLCarouselState): {index: number, dir: ESLCarouselDirection | null} {
const {value, isRelative, dir} = parseIndex(indexStr);
function resolveGroupIndex(indexStr: string | ESLCarouselNavIndex, cfg: ESLCarouselState): ESLCarouselNavInfo {
const {value, isRelative, direction} = parseIndex(indexStr);
if (!isRelative) {
const index = groupToIndex(value, cfg.count, cfg.size);
return {index, dir: indexToDirection(index, cfg)};
return {index, direction: indexToDirection(index, cfg)};
}
// TODO: extend navigation boundaries
if (value === -1 && cfg.activeIndex < cfg.count && cfg.activeIndex > 0) {
return {index: 0, dir: dir || 'prev'};
return {index: 0, direction: direction || 'prev'};
}
if (value === 1 && normalize(cfg.activeIndex + cfg.count, cfg.size) > cfg.size - cfg.count) {
return {index: cfg.size - cfg.count, dir: dir || 'next'};
return {index: cfg.size - cfg.count, direction: direction || 'next'};
}
const index = normalize(cfg.activeIndex + value * cfg.count, cfg.size);
return {index, dir: dir || indexToDirection(index, cfg)};
return {index, direction: direction || indexToDirection(index, cfg)};
}

/** @returns normalized index from target definition and current state */
export function toIndex(target: ESLCarouselSlideTarget, cfg: ESLCarouselState): {index: number, dir: ESLCarouselDirection | null} {
export function toIndex(target: ESLCarouselSlideTarget, cfg: ESLCarouselState): ESLCarouselNavInfo {
if (typeof target === 'number') {
const index = normalizeIndex(target, cfg);
return {index, dir: indexToDirection(index, cfg)};
return {index, direction: indexToDirection(index, cfg)};
}
const {type, index} = splitTarget(target);
if (type === 'group') return resolveGroupIndex(index, cfg);
if (type === 'slide') return resolveSlideIndex(index, cfg);
return {index: cfg.activeIndex, dir: null};
return {index: cfg.activeIndex, direction: null};
}

/**
Expand All @@ -137,8 +138,8 @@ export function toIndex(target: ESLCarouselSlideTarget, cfg: ESLCarouselState):
*/
export function canNavigate(target: ESLCarouselSlideTarget, cfg: ESLCarouselState): boolean {
if (cfg.size <= cfg.count) return false;
const {dir, index} = toIndex(target, cfg);
if (!cfg.loop && index > cfg.activeIndex && dir === 'prev') return false;
if (!cfg.loop && index < cfg.activeIndex && dir === 'next') return false;
return !!dir && index !== cfg.activeIndex;
const {direction, index} = toIndex(target, cfg);
if (!cfg.loop && index > cfg.activeIndex && direction === 'prev') return false;
if (!cfg.loop && index < cfg.activeIndex && direction === 'next') return false;
return !!direction && index !== cfg.activeIndex;
}
36 changes: 20 additions & 16 deletions src/modules/esl-carousel/test/core/esl-carousel.nav.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {
normalize,
calcDirection,
groupToIndex,
indexToGroup,
indexToDirection,
toIndex
} from '../../core/nav/esl-carousel.nav.utils';

Expand Down Expand Up @@ -30,21 +30,6 @@ describe('ESLCarousel: Nav Utils', () => {
);
});

describe('calcDirection', () => {
test.each([
// [from index, to index, size, result]
[0, 1, 5, 'next'],
[1, 3, 5, 'next'],
[3, 2, 5, 'prev'],
[1, 4, 5, 'prev'],
[1, 7, 5, 'next'],
[7, 1, 5, 'prev']
])(
'(from = %d, to = %d, size = %d) => %s',
(from: number, to: number, count: number, result: ESLCarouselDirection) => expect(calcDirection(from, to, count)).toBe(result)
);
});

describe('groupToIndex', () => {
test.each([
// [group index, active count, size, result]
Expand Down Expand Up @@ -158,6 +143,25 @@ describe('ESLCarousel: Nav Utils', () => {
);
});

describe('indexToDirection', () => {
test.each([
// [to, {activeIndex, size, loop}, result]
[1, {activeIndex: 0, size: 5, loop: false}, 'next'],
[4, {activeIndex: 0, size: 5, loop: false}, 'next'],
[4, {activeIndex: 3, size: 5, loop: false}, 'next'],
[0, {activeIndex: 1, size: 5, loop: false}, 'prev'],
[0, {activeIndex: 4, size: 5, loop: false}, 'prev'],

[1, {activeIndex: 0, size: 5, loop: true}, 'next'],
[3, {activeIndex: 1, size: 5, loop: true}, 'next'],
[2, {activeIndex: 3, size: 5, loop: true}, 'prev'],
[4, {activeIndex: 1, size: 5, loop: true}, 'prev']
])(
'(to = %d, state = %p) => %s',
(to: number, state: ESLCarouselState, result: ESLCarouselDirection) => expect(indexToDirection(to, state)).toBe(result)
);
});

describe('toIndex', () => {
describe('numeric input', () => {
test.each([
Expand Down

0 comments on commit 30cef21

Please sign in to comment.