Skip to content

Commit

Permalink
Merge pull request #2481 from exadel-inc/feat/esl-carousel-updates
Browse files Browse the repository at this point in the history
feat(esl-carousel + esl-event-listener): Bunch of updates for carousel API, wheel plugin and core wheel target
  • Loading branch information
ala-n authored Jul 1, 2024
2 parents 8162b43 + acbfff2 commit 090bd4c
Show file tree
Hide file tree
Showing 9 changed files with 116 additions and 19 deletions.
10 changes: 10 additions & 0 deletions src/modules/esl-carousel/core/esl-carousel.events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ 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 related slide element */
public get $relatedSlide(): HTMLElement | null {
return this.target.slideAt(this.related);
}

public static create(type: 'BEFORE' | 'AFTER', init: ESLCarouselSlideEventInit): ESLCarouselSlideEvent {
return new ESLCarouselSlideEvent(ESLCarouselSlideEvent[type], init);
}
Expand Down
22 changes: 12 additions & 10 deletions src/modules/esl-carousel/core/esl-carousel.less
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,6 @@ esl-carousel {
/* stylelint-disable-next-line */
overflow: clip;

[esl-carousel-slides] {
position: relative;
width: 100%;
margin: 0;
display: flex;
gap: 20px;
z-index: 0;
transform: translate3d(0px, 0px, 0px);
}

&.esl-carousel-vertical [esl-carousel-slides] {
flex-direction: column;
touch-action: pan-x;
Expand All @@ -43,6 +33,18 @@ esl-carousel {
}
}

/* Default carousel area styles */
[esl-carousel-slides] {
position: relative;
width: 100%;
margin: 0;
display: flex;
gap: 20px;
z-index: 0;
transform: translate3d(0px, 0px, 0px);
}

/* Default carousel slide styles */
[esl-carousel-slide] {
flex: 0 0 auto;
margin: 0;
Expand Down
1 change: 1 addition & 0 deletions src/modules/esl-carousel/core/esl-carousel.slide.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export class ESLCarouselSlide extends ESLMixinElement {

@ready
protected override connectedCallback(): void {
if (!this.$carousel) return;
this.$carousel?.addSlide && this.$carousel.addSlide(this.$host);
super.connectedCallback();
this.updateA11y();
Expand Down
4 changes: 3 additions & 1 deletion src/modules/esl-carousel/core/esl-carousel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ export class ESLCarousel extends ESLBaseElement {
if (!slide) return;
slide.setAttribute(this.slideAttrName, '');
if (slide.parentNode === this.$slidesArea) return this.update();
console.debug('[ESL]: ESLCarousel moves slide to correct location', slide);
if (slide.parentNode) slide.remove();
Promise.resolve().then(() => this.$slidesArea.appendChild(slide));
}
Expand Down Expand Up @@ -277,7 +278,8 @@ export class ESLCarousel extends ESLBaseElement {
}

/** Goes to the target according to passed params */
public goTo(target: ESLCarouselSlideTarget, params: ESLCarouselActionParams = {}): Promise<void> {
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';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {ESLCarouselPlugin} from '../esl-carousel.plugin';
import {ESLCarouselSlideEvent} from '../../core/esl-carousel.events';

/**
* {@link ESLCarousel} auto-play (auto-advance) plugin mixin
* {@link ESLCarousel} autoplay (auto-advance) plugin mixin
* Automatically switch slides by timeout
*
* @author Alexey Stsefanovich (ala'n)
Expand Down
2 changes: 1 addition & 1 deletion src/modules/esl-carousel/plugin/esl-carousel.plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export abstract class ESLCarouselPlugin extends ESLMixinElement {
return true;
} else {
const {is} = this.constructor as typeof ESLCarouselPlugin;
console.error('[ESL]: %o is not correct target for %o', $host, is);
console.warn('[ESL]: ESLCarousel %s plugin rejected for non correct target %o', is, $host);
this.$host.removeAttribute(is);
return false;
}
Expand Down
51 changes: 47 additions & 4 deletions src/modules/esl-carousel/plugin/wheel/esl-carousel.wheel.mixin.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {ExportNs} from '../../../esl-utils/environment/export-ns';
import {throttle} from '../../../esl-utils/async/throttle';
import {attr, decorate, listen} from '../../../esl-utils/decorators';
import {parseBoolean} from '../../../esl-utils/misc/format';
import {attr, bind, decorate, listen} from '../../../esl-utils/decorators';
import {ESLWheelEvent, ESLWheelTarget} from '../../../esl-event-listener/core';

import {ESLCarouselPlugin} from '../esl-carousel.plugin';
Expand All @@ -18,15 +19,57 @@ export class ESLCarouselWheelMixin extends ESLCarouselPlugin {
/** Prefix to request next/prev navigation */
@attr({name: ESLCarouselWheelMixin.is}) public type: 'slide' | 'group';

/** CSS selector to ignore wheel event from */
@attr({
name: ESLCarouselWheelMixin.is + '-ignore',
defaultValue: '[contenteditable]'
})
public ignore: string;

/**
* Restricts wheel direction.
* Values:
* - 'auto' - depends on the carousel orientation (default)
* - 'x' - horizontal only
* - 'y' - vertical only
*/
@attr({name: ESLCarouselWheelMixin.is + '-direction'}) public direction: string;

/** Prevent default action for wheel event */
@attr({
name: ESLCarouselWheelMixin.is + '-prevent-default',
defaultValue: true,
parser: parseBoolean
}) public preventDefault: boolean;

/** @returns true if the plugin should track vertical wheel */
protected get isVertical(): boolean {
if (this.direction === 'x') return false;
if (this.direction === 'y') return true;
return this.$host.state.vertical;
}

/** @returns true if the plugin should track passed event */
@bind
protected isEventIgnored(e: WheelEvent & {target: Element}): boolean {
if (e.shiftKey === this.isVertical) return true;
return !!this.ignore && !!e.target.closest(this.ignore);
}

/** Handles auxiliary events to pause/resume timer */
@listen({
event: ESLWheelEvent.type,
target: (plugin: ESLCarouselWheelMixin) => ESLWheelTarget.for(plugin.$host, {distance: 1})
event: ESLWheelEvent.TYPE,
target: (plugin: ESLCarouselWheelMixin) => ESLWheelTarget.for(plugin.$host, {
distance: 10,
preventDefault: plugin.preventDefault,
ignore: plugin.isEventIgnored
})
})
@decorate(throttle, 400)
protected _onWheel(e: ESLWheelEvent): void {
if (!this.$host || this.$host.animating) return;
const direction = e.deltaY > 0 ? 'next' : 'prev';
const delta = this.isVertical ? e.deltaY : e.deltaX;
const direction = delta > 0 ? 'next' : 'prev';
this.$host?.goTo(`${this.type || 'slide'}:${direction}`);
}
}
Expand Down
17 changes: 15 additions & 2 deletions src/modules/esl-event-listener/core/targets/wheel.target.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {ESLEventListener} from '../listener';
import {ESLWheelEvent} from './wheel.target.event';

import type {ESLWheelEventInfo} from './wheel.target.event';
import type {Predicate} from '../../../esl-utils/misc/functions';
import type {ESLDomElementTarget} from '../../../esl-utils/abstract/dom-target';
import type {ElementScrollOffset} from '../../../esl-utils/dom/scroll';

Expand All @@ -24,6 +25,10 @@ export interface ESLWheelTargetSetting {
distance?: number;
/** The maximum duration of the wheel events to consider it inertial */
timeout?: number;
/** Predicate to ignore wheel events */
ignore?: Predicate<WheelEvent>;
/** Prevent default action for wheel event */
preventDefault?: boolean;
}

/**
Expand All @@ -33,7 +38,9 @@ export class ESLWheelTarget extends SyntheticEventTarget {
protected static defaultConfig: Required<ESLWheelTargetSetting> = {
skipOnScroll: true,
distance: 400,
timeout: 100
timeout: 100,
preventDefault: false,
ignore: () => false
};

protected readonly config: Required<ESLWheelTargetSetting>;
Expand Down Expand Up @@ -70,10 +77,12 @@ export class ESLWheelTarget extends SyntheticEventTarget {
/** Handles wheel events */
@bind
protected _onWheel(event: WheelEvent): void {
if (this.config.ignore(event)) return;
if (this.config.skipOnScroll) {
const offsets = getParentScrollOffsets(event.target as Element, this.target);
this.scrollData = this.scrollData.concat(offsets);
}
if (this.config.preventDefault) event.preventDefault();
this.aggregateWheel(event);
}

Expand Down Expand Up @@ -143,7 +152,11 @@ export class ESLWheelTarget extends SyntheticEventTarget {
super.addEventListener(event, callback);

if (this.getEventListeners().length > 1) return;
ESLEventListener.subscribe(this, this._onWheel, {event: 'wheel', capture: false, target: this.target});
ESLEventListener.subscribe(this, this._onWheel, {
event: 'wheel',
passive: !this.config.preventDefault,
target: this.target
});
}

/** Unsubscribes from the observed target {@link Element} wheel events */
Expand Down
26 changes: 26 additions & 0 deletions src/modules/esl-event-listener/test/targets/wheel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,32 @@ describe('ESLWheelTarget', () => {
});
});

describe('ESLWheelTarget instance ignore predicate support', () => {
const $el = document.createElement('div');
const target = ESLWheelTarget.for($el, {
distance: 50,
timeout: 10,
ignore: (e) => e.deltaX === 0
});
const listener = jest.fn();

beforeAll(() => target.addEventListener('longwheel', listener));
afterAll(() => target.removeEventListener('longwheel', listener));
beforeEach(() => listener.mockReset());

test('ESLWheelTarget ignores vertical scroll when predicate filter deltaX amount', () => {
$el.dispatchEvent(Object.assign(new Event('wheel'), {deltaX: 0, deltaY: 100}));
jest.advanceTimersByTime(100);
expect(listener).not.toHaveBeenCalled();
});

test('ESLWheelTarget doesn\'t ignore horizontal scroll when predicate filter deltaX amount', () => {
$el.dispatchEvent(Object.assign(new Event('wheel'), {deltaX: 100, deltaY: 0}));
jest.advanceTimersByTime(100);
expect(listener).toHaveBeenCalled();
});
});

describe('ESLWheelTarget ignores "short" scroll events', () => {
const $el = document.createElement('div');
const target = ESLWheelTarget.for($el, {timeout: 50, distance: 101});
Expand Down

0 comments on commit 090bd4c

Please sign in to comment.