UNPKG

@exadel/esl

Version:

Exadel Smart Library (ESL) is the lightweight custom elements library that provide a set of super-flexible components

210 lines (209 loc) 8.75 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var ESLCarouselNavDots_1; import { ExportNs } from '../../../esl-utils/environment/export-ns'; import { format } from '../../../esl-utils/misc/format'; import { ARROW_LEFT, ARROW_RIGHT } from '../../../esl-utils/dom/keys'; import { attr, listen, memoize, prop, ready } from '../../../esl-utils/decorators'; import { ESLBaseElement } from '../../../esl-base-element/core'; import { ESLTraversingQuery } from '../../../esl-traversing-query/core'; import { indexToGroup } from '../../core/esl-carousel.utils'; import { ESLCarouselChangeEvent, ESLCarouselSlideEvent } from '../../core/esl-carousel.events'; /** * {@link ESLCarousel} Dots navigation element * * Example: * ``` * <esl-carousel-dots></esl-carousel-dots> * ``` */ let ESLCarouselNavDots = ESLCarouselNavDots_1 = class ESLCarouselNavDots extends ESLBaseElement { /** Default dot template implementation (readonly) */ static defaultDotBuilder(index, { tabIndex, dotLabelFormat, targetID }) { const dot = document.createElement('button'); dot.className = 'esl-carousel-dot'; dot.setAttribute('role', 'tab'); dot.setAttribute('tabindex', tabIndex >= 0 ? '-1' : '0'); dot.setAttribute('aria-label', format(dotLabelFormat, { index: index + 1 })); dot.setAttribute('aria-controls', targetID); return dot; } /** Default dot updater implementation (readonly)*/ static defaultDotUpdater($dot, index, { activeIndex }) { const isActive = index === activeIndex; $dot.toggleAttribute('active', isActive); $dot.setAttribute('aria-selected', String(isActive)); $dot.setAttribute('aria-current', String(isActive)); } // TODO: implement in future // /** Use arrow keys to navigate */ // @attr({defaultValue: true, parser: parseBoolean}) // public keyboardArrows: boolean; /** * Dots number according carousel config. * Will be 0 if carousel does not require dots (carousel incomplete). * (Note: memoization used during update stage) */ get count() { var _a; if (!((_a = this.$carousel) === null || _a === void 0 ? void 0 : _a.renderer)) return 0; const { count, size } = this.$carousel.state; const value = Math.ceil(size / count); return value > 1 ? value : 0; } /** Active dot index according to carousel config. (Note: memoization used during update stage) */ get activeIndex() { var _a; if (!((_a = this.$carousel) === null || _a === void 0 ? void 0 : _a.renderer)) return 0; const { activeIndex, count, size } = this.$carousel.state; return indexToGroup(activeIndex, count, size); } /** Previous dot index (cycled) */ get prevIndex() { return this.activeIndex > 0 ? this.activeIndex - 1 : this.count - 1; } /** Next dot index (cycled) */ get nextIndex() { return this.activeIndex < this.count - 1 ? this.activeIndex + 1 : 0; } /** Returns amount of slides associated with one group(dot) */ get groupSize() { var _a; return ((_a = this.$carousel) === null || _a === void 0 ? void 0 : _a.state.count) || 0; } /** Current dots collection */ get $dots() { return [...this.querySelectorAll('[esl-carousel-dot]')]; } /** @returns ESLCarousel instance; based on {@link carousel} attribute */ get $carousel() { return ESLTraversingQuery.first(this.carousel, this); } /** @returns accessible target ID */ get targetID() { var _a; return ((_a = this.$carousel) === null || _a === void 0 ? void 0 : _a.id) || ''; } connectedCallback() { super.connectedCallback(); this.replaceChildren(); this.update(); this.updateA11y(); } disconnectedCallback() { super.disconnectedCallback(); memoize.clear(this, '$carousel'); } attributeChangedCallback(name, oldValue, newValue) { if (!this.connected) return; if (name === 'target') { memoize.clear(this, '$carousel'); this.$$on(this._onSlideChange); } this.update(true); this.updateA11y(); } /** Updates dots state, rebuilds dots if needed */ update(force) { memoize.clear(this, ['count', 'activeIndex']); // invalidate state memoization if (force || this.$dots.length !== this.count) { const $dots = new Array(this.count).fill(null).map((_, index) => this.dotBuilder(index, this)); // Attribute `esl-carousel-dot` is necessary for proper work of ESLCarouselNavDots plugin, we do not relay on customizable dotBuilder(including default) $dots.forEach(($dot, index) => $dot.setAttribute('esl-carousel-dot', String(index))); memoize.clear(this, '$dots'); this.replaceChildren(...$dots); } this.$dots.forEach(($dot, index) => this.dotUpdater($dot, index, this)); } /** Updates a11y of `ESLCarouselNavDots` as a container */ updateA11y() { this.$$attr('role', 'tablist'); if (!this.hasAttribute('aria-label')) { this.$$attr('aria-label', ESLCarouselNavDots_1.DEFAULT_ARIA_LABEL); } } /** Handles carousel state changes */ _onSlideChange(e) { if (this.$carousel === e.target) this.update(); } /** Handles `click` on the dots */ _onClick(event) { if (!this.$carousel || typeof this.$carousel.goTo !== 'function') return; const $btn = event.$delegate; const target = $btn.getAttribute('esl-carousel-dot') || ''; this.$carousel.goTo(`group:${+target}`); (this.tabIndex >= 0 ? this : $btn).focus({ preventScroll: true }); } /** Handles `keydown` event */ _onKeydown(event) { var _a, _b; if (ARROW_LEFT === event.key) { (_a = this.$dots[this.prevIndex]) === null || _a === void 0 ? void 0 : _a.click(); } if (ARROW_RIGHT === event.key) { (_b = this.$dots[this.nextIndex]) === null || _b === void 0 ? void 0 : _b.click(); } } }; ESLCarouselNavDots.is = 'esl-carousel-dots'; ESLCarouselNavDots.observedAttributes = ['target']; ESLCarouselNavDots.DEFAULT_ARIA_LABEL = 'Carousel dots'; /** Default dots builder function {@link ESLCarouselNavDotBuilder} */ ESLCarouselNavDots.dotBuilder = ESLCarouselNavDots_1.defaultDotBuilder; /** Default dots updater function {@link ESLCarouselNavDotUpdater} */ ESLCarouselNavDots.dotUpdater = ESLCarouselNavDots_1.defaultDotUpdater; __decorate([ attr({ name: 'target', defaultValue: '::parent(.esl-carousel-nav-container)::find(esl-carousel)' }) ], ESLCarouselNavDots.prototype, "carousel", void 0); __decorate([ attr({ defaultValue: ($this) => `Go to slide ${$this.groupSize > 1 ? 'group ' : ''}{index}` }) ], ESLCarouselNavDots.prototype, "dotLabelFormat", void 0); __decorate([ prop(($this) => $this.constructor.dotBuilder) ], ESLCarouselNavDots.prototype, "dotBuilder", void 0); __decorate([ prop(($this) => $this.constructor.dotUpdater) ], ESLCarouselNavDots.prototype, "dotUpdater", void 0); __decorate([ memoize() ], ESLCarouselNavDots.prototype, "count", null); __decorate([ memoize() ], ESLCarouselNavDots.prototype, "activeIndex", null); __decorate([ memoize() ], ESLCarouselNavDots.prototype, "$dots", null); __decorate([ memoize() ], ESLCarouselNavDots.prototype, "$carousel", null); __decorate([ ready ], ESLCarouselNavDots.prototype, "connectedCallback", null); __decorate([ listen({ event: `${ESLCarouselSlideEvent.AFTER} ${ESLCarouselChangeEvent.TYPE}`, target: ($el) => $el.$carousel }) ], ESLCarouselNavDots.prototype, "_onSlideChange", null); __decorate([ listen({ event: 'click', selector: '[esl-carousel-dot]' }) ], ESLCarouselNavDots.prototype, "_onClick", null); __decorate([ listen('keydown') ], ESLCarouselNavDots.prototype, "_onKeydown", null); ESLCarouselNavDots = ESLCarouselNavDots_1 = __decorate([ ExportNs('Carousel.Dots') ], ESLCarouselNavDots); export { ESLCarouselNavDots };