-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(esl-carousel): complete support of the navigation plugins for ES…
…LCarousel
- Loading branch information
Showing
5 changed files
with
492 additions
and
13 deletions.
There are no files selected for viewing
72 changes: 72 additions & 0 deletions
72
src/modules/esl-carousel/plugin/nav/esl-carousel.nav.arrows.less
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
111 changes: 111 additions & 0 deletions
111
src/modules/esl-carousel/plugin/nav/esl-carousel.nav.dots.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
75
src/modules/esl-carousel/plugin/nav/esl-carousel.nav.mixin.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
Oops, something went wrong.