Skip to content

Commit

Permalink
feat(esl-carousel): complete support of the navigation plugins for ES…
Browse files Browse the repository at this point in the history
…LCarousel
  • Loading branch information
ala-n committed May 18, 2023
1 parent a6917ca commit 19bd241
Show file tree
Hide file tree
Showing 5 changed files with 492 additions and 13 deletions.
72 changes: 72 additions & 0 deletions src/modules/esl-carousel/plugin/nav/esl-carousel.nav.arrows.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/** Arrow navigation styles */
[esl-carousel-container] {
.esl-carousel-arrow {
display: flex;

position: absolute;
z-index: 3;
top: 50%;
transform: translateY(-50%);

background: none;
border: none;
cursor: pointer;
appearance: none;

&[disabled] {
opacity: .25;
}
}

// Positioning
.esl-carousel-arrow {
width: 3rem;
height: 3rem;
left: -2.5rem;

@media @xs-sm {
left: -2rem;
height: 100%;
top: 0;
transform: none;
}

&.inner {
left: 0;
}
}
esl-carousel + .esl-carousel-arrow {
left: auto;
right: -2.5rem;

@media @xs-sm {
left: auto;
right: -2rem;
}

&.inner {
left: auto;
right: 0;
}
}

.icon-arrow-next::before,
.icon-arrow-prev::before {
content: '';
display: block;
width: 100%;
height: 100%;
margin: auto;
opacity: .75;
background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDUwIDUwIiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCA1MCA1MCIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cG9seWdvbiBwb2ludHM9IjMxIDQwIDE2IDI1IDMxIDEwIDMzIDEyIDIwIDI1IDMzIDM4Ii8+PC9zdmc+) no-repeat;

filter: drop-shadow(0 0 4px #fff);

@media @xs-sm {
display: none;
}
}
.icon-arrow-next::before {
transform: scaleX(-1);
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
// TODO: refactor
// DOTS navigation
.esl-carousel {
esl-carousel-dots {
position: absolute;
width: 100%;
height: auto;
bottom: 30px;
line-height: 0;
text-align: center;
opacity: 1;
transition: opacity .5s ease-out;
cursor: default;
}
.esl-carousel-dots {
display: block;
text-align: center;
opacity: 1;
transition: opacity .5s ease-out;
cursor: default;

.carousel-dot {
display: inline-block;
Expand All @@ -22,7 +17,6 @@
border: none;
border-radius: 24px;
cursor: pointer;
outline: none;
}

.active-dot {
Expand Down
111 changes: 111 additions & 0 deletions src/modules/esl-carousel/plugin/nav/esl-carousel.nav.dots.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// TODO: rework, simplify and refactor
import {ExportNs} from '../../../esl-utils/environment/export-ns';
import {attr, listen, memoize} from '../../../esl-utils/decorators';
import {ARROW_LEFT, ARROW_RIGHT} from '../../../esl-utils/dom/keys';
import {findNextLooped, findPrevLooped} from '../../../esl-utils/dom/traversing';
import {ESLBaseElement} from '../../../esl-base-element/core';
import {ESLTraversingQuery} from '../../../esl-traversing-query/core';

import {indexToGroup} from '../../core/nav/esl-carousel.nav.utils';

import type {ESLCarousel} from '../../core/esl-carousel';

/**
* {@link ESLCarousel} Dots navigation element
* @author Julia Murashko
*
* Example:
* ```
* <esl-carousel-dots></esl-carousel-dots>
* ```
*/
@ExportNs('Carousel.Dots')
export class ESLCarouselNavDots extends ESLBaseElement {
public static override is = 'esl-carousel-dots';

/** {@link ESLTraversingQuery} string to find {@link ESLCarousel} instance */
@attr({
defaultValue: '::parent([esl-carousel-container])::find(esl-carousel)'
})
public carousel: string;

/** @returns ESLCarousel instance; based on {@link carousel} attribute */
@memoize()
public get $carousel(): ESLCarousel | null {
return ESLTraversingQuery.first(this.carousel, this) as ESLCarousel;
}

public override async connectedCallback(): Promise<void> {
this.$$attr('disabled', true);
if (!this.$carousel) return;
await customElements.whenDefined(this.$carousel.tagName.toLowerCase());
super.connectedCallback();
this.rerender();
}

public override disconnectedCallback(): void {
super.disconnectedCallback();
memoize.clear(this, '$carousel');
}

/** Renders dots according to the carousel state. */
public rerender(): void {
if (!this.$carousel) return;
const {firstIndex, count, size} = this.$carousel;
this.$$attr('disabled', size < 2);
let html = '';
const activeDot = indexToGroup(firstIndex, count, size);
for (let i = 0; i < Math.ceil(size / count); ++i) {
html += this.buildDot(i, i === activeDot);
}
this.innerHTML = html;
}

/** Builds content of dots. */
public buildDot(index: number, isActive: boolean): string {
return `<button role="button"
data-nav-target="group:${index}"
class="carousel-dot ${isActive ? 'active-dot' : ''}"
aria-current="${isActive ? 'true' : 'false'}"></button>`;
}

@listen({
event: 'esl:slide:changed',
target: ($el: ESLCarouselNavDots) => $el.$carousel
})
protected _onSlideChange(e: Event): void {
if (this.$carousel !== e.target) return;
this.rerender();
}

@listen('click')
protected _onClick(event: PointerEvent): void {
if (!this.$carousel || typeof this.$carousel.goTo !== 'function') return;
const $target = (event.target as Element).closest('[data-nav-target]');
if (!$target) return;
this.$carousel.goTo($target.getAttribute('data-nav-target')!);
}

/** Handles `keydown` event. */
@listen('keydown')
protected _onKeydown(event: KeyboardEvent): void {
const $eventTarget: HTMLElement = event.target as HTMLElement;
if (ARROW_LEFT === event.key) {
const $nextDot = findPrevLooped($eventTarget, '.carousel-dot') as HTMLElement;
$nextDot.click();
}
if (ARROW_RIGHT === event.key) {
const $nextDot = findNextLooped($eventTarget, '.carousel-dot') as HTMLElement;
$nextDot.click();
}
}
}

declare global {
export interface ESLCarouselNS {
Dots: typeof ESLCarouselNavDots;
}
export interface HTMLElementTagNameMap {
'esl-carousel-dots': ESLCarouselNavDots;
}
}
75 changes: 75 additions & 0 deletions src/modules/esl-carousel/plugin/nav/esl-carousel.nav.mixin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import {ExportNs} from '../../../esl-utils/environment/export-ns';
import {attr, listen, memoize} from '../../../esl-utils/decorators';
import {ESLMixinElement} from '../../../esl-mixin-element/core';
import {ESLTraversingQuery} from '../../../esl-traversing-query/core';

import type {ESLCarousel} from '../../core/esl-carousel';
import type {ESLCarouselSlideTarget} from '../../core/nav/esl-carousel.nav.types';

/**
* ESLCarousel navigation helper to define triggers for carousel navigation.
*
* Example:
* ```
* <div esl-carousel-container>
* <button esl-carousel-nav="group:prev">Prev</button>
* <esl-carousel>...</esl-carousel>
* <button esl-carousel-nav="group:next">Next</button>
* </div>
* ```
*/
@ExportNs('Carousel.Nav')
export class ESLCarouselNavMixin extends ESLMixinElement {
static override is = 'esl-carousel-nav';

/** {@link ESLCarouselSlideTarget} target to navigate in carousel */
@attr({name: ESLCarouselNavMixin.is}) public target: ESLCarouselSlideTarget;

/** {@link ESLTraversingQuery} string to find {@link ESLCarousel} instance */
@attr({
name: ESLCarouselNavMixin.is + '-target',
defaultValue: '::parent([esl-carousel-container])::find(esl-carousel)'
})
public carousel: string;

/** @returns ESLCarousel instance; based on {@link carousel} attribute */
@memoize()
public get $carousel(): ESLCarousel | null {
return ESLTraversingQuery.first(this.carousel, this.$host) as ESLCarousel;
}

public override async connectedCallback(): Promise<void> {
this.$$attr('disabled', true);
if (!this.$carousel) return;
await customElements.whenDefined(this.$carousel.tagName.toLowerCase());
super.connectedCallback();
this._onSlideChange();
}

public override disconnectedCallback(): void {
super.disconnectedCallback();
memoize.clear(this, '$carousel');
}

@listen({
event: 'esl:slide:changed',
target: ($nav: ESLCarouselNavMixin) => $nav.$carousel
})
protected _onSlideChange(): void {
const canNavigate = this.$carousel && this.$carousel.canNavigate(this.target);
this.$$attr('disabled', !canNavigate);
}

@listen('click')
protected _onClick(e: PointerEvent): void {
if (!this.$carousel || typeof this.$carousel.goTo !== 'function') return;
this.$carousel.goTo(this.target);
e.preventDefault();
}
}

declare global {
export interface ESLCarouselNS {
Nav: typeof ESLCarouselNavMixin;
}
}
Loading

0 comments on commit 19bd241

Please sign in to comment.