UNPKG

@exadel/esl

Version:

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

237 lines (236 loc) 10.2 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { isEqual } from '../../esl-utils/misc/object'; import { parseTime } from '../../esl-utils/misc/format'; import { promisifyTimeout } from '../../esl-utils/async/promise/timeout'; import { ESLCarouselDirection } from './esl-carousel.types'; import { ESLCarouselMoveEvent, ESLCarouselSlideEvent } from './esl-carousel.events'; import { ESLCarouselNavRejection } from './esl-carousel.errors'; import { normalize, normalizeIndex, sequence } from './esl-carousel.utils'; import { ESLCarouselRendererRegistry } from './esl-carousel.renderer.registry'; export class ESLCarouselRenderer { constructor($carousel, options) { /** (visible) slide count per view */ this.count = 0; /** cyclic carousel rendering mode */ this.loop = false; /** vertical carousel rendering mode */ this.vertical = false; /** marker if the renderer is applied to the carousel */ this._bound = false; this.$carousel = $carousel; this.count = options.count; this.loop = options.loop; this.vertical = options.vertical; } /** @returns marker if the renderer is applied to the carousel */ get bound() { return this._bound; } /** @returns renderer type name */ get type() { return this.constructor.is; } /** @returns slide total count or 0 if the renderer is not bound */ get size() { return this._bound ? this.$slides.length : 0; } /** @returns slides shift size in pixels */ get offset() { return 0; } /** @returns active slide index or -1 if the renderer is not bound */ get activeIndex() { if (this.size <= 0) return -1; if (this.$carousel.isActive(0)) { for (let i = this.size - 1; i > 0; --i) { if (!this.$carousel.isActive(i)) return normalize(i + 1, this.size); } } return this.$slides.findIndex(this.$carousel.isActive, this.$carousel); } /** @returns list of active slide indexes */ get activeIndexes() { const start = this.activeIndex; if (start < 0) return []; const indexes = []; for (let i = 0; i < this.size; i++) { const index = normalize(i + start, this.size); if (this.$carousel.isActive(index)) indexes.push(index); } return indexes; } /** @returns renderer config safe copy */ get config() { const { type, size, count, loop, vertical } = this; return { type, size, count, loop, vertical }; } /** @returns renderer state safe copy */ get state() { const { size, count, loop, vertical, activeIndex, offset } = this; return { size, count, loop, vertical, activeIndex, offset }; } /** @returns {@link ESLCarousel} `$slidesArea` */ get $area() { return this.$carousel.$slidesArea; } /** @returns {@link ESLCarousel} `$slides` */ get $slides() { return this.$carousel.$slides || []; } get animating() { return this.$carousel.hasAttribute('animating'); } set animating(value) { this.$carousel.toggleAttribute('animating', value); } get transitionDuration() { const name = ESLCarouselRenderer.TRANSITION_DURATION_PROP; const duration = getComputedStyle(this.$area).getPropertyValue(name); return parseTime(duration); } set transitionDuration(value) { if (typeof value === 'number' && value > 0) { this.$carousel.style.setProperty(ESLCarouselRenderer.TRANSITION_DURATION_PROP, `${value}ms`); } else { this.$carousel.style.removeProperty(ESLCarouselRenderer.TRANSITION_DURATION_PROP); } } get transitionDuration$$() { return promisifyTimeout(this.transitionDuration); } equal(config) { return this._bound && isEqual(this.config, config); } bind() { this._bound = true; const type = this.constructor; const orientationCls = `esl-carousel-${this.vertical ? 'vertical' : 'horizontal'}`; this.$carousel.classList.add(orientationCls, ...type.classes); this.onBind(); } unbind() { if (!this._bound) return; const type = this.constructor; const orientationCls = ['esl-carousel-vertical', 'esl-carousel-horizontal']; this.$carousel.classList.remove(...orientationCls, ...type.classes); this.onUnbind(); this._bound = false; } /** Processes binding of defined renderer to the carousel {@link ESLCarousel}. */ onBind() { } /** Processes unbinding of defined renderer from the carousel {@link ESLCarousel}. */ onUnbind() { } /** Processes drawing of the carousel {@link ESLCarousel}. */ redraw() { } /** Normalizes an index before navigation */ normalizeIndex(index, params) { return normalizeIndex(index, this); } /** Normalizes a direction before navigation */ normalizeDirection(direction, params) { return (this.loop ? params && params.direction : null) || direction || ESLCarouselDirection.NEXT; } /** Processes changing slides */ navigate(to, params) { return __awaiter(this, void 0, void 0, function* () { const index = this.normalizeIndex(to.index, params); const direction = this.normalizeDirection(to.direction, params); if (index === this.activeIndex && !this.offset) return; // skip if index is already active if (!this.dispatchChangeEvent('BEFORE', index, Object.assign(Object.assign({}, params), { direction }))) return; try { this.transitionDuration = params.stepDuration; yield this.onBeforeAnimate(index, direction, params); yield this.onAnimate(index, direction, params); yield this.onAfterAnimate(index, direction, params); } catch (e) { if (e instanceof Error) throw e; } finally { this.transitionDuration = null; } }); } /** Pre-processing animation action. */ onBeforeAnimate(index, direction, params) { return __awaiter(this, void 0, void 0, function* () { if (this.animating) throw new ESLCarouselNavRejection(index); this.setPreActive(index, Object.assign(Object.assign({}, params), { direction, final: true })); }); } /** Post-processing animation action. */ onAfterAnimate(index, direction, params) { return __awaiter(this, void 0, void 0, function* () { this.setActive(index, Object.assign(Object.assign({}, params), { direction })); }); } /** Sets active slides from passed index **/ setActive(index, event) { const count = Math.min(this.count, this.size); for (let i = 0; i < this.size; i++) { const position = normalize(i + index, this.size); const $slide = this.$slides[position]; $slide.toggleAttribute('active', i < count); $slide.toggleAttribute('pre-active', false); $slide.toggleAttribute('next', i === count && (this.loop || position !== 0)); $slide.toggleAttribute('prev', i === this.size - 1 && i >= count && (this.loop || position !== this.size - 1)); } event && this.dispatchChangeEvent('AFTER', index, Object.assign({}, event)); } /** Sets pre-active (slides that are going to be active) slides from the passed index **/ setPreActive(index, event, final = false) { let changed = false; const count = Math.min(this.count, this.size); for (let i = 0; i < this.size; ++i) { const position = normalize(i + index, this.size); const $slide = this.$slides[position]; if ($slide.hasAttribute('active')) continue; // skip already active slides changed = changed || ($slide.hasAttribute('pre-active') !== (i < count)); $slide.toggleAttribute('pre-active', i < count); } if (event && changed) this.dispatchChangeEvent('CHANGE', index, event); } /** Dispatches a change event with the given type and index */ dispatchChangeEvent(name, index, event) { const count = Math.min(this.count, this.size); const indexesAfter = sequence(index, count, this.size); const details = Object.assign(Object.assign({}, event), { indexesAfter }); return this.$carousel.dispatchEvent(ESLCarouselSlideEvent.create(name, details)); } /** Dispatches a move event with the given offset, index, delta and event details */ dispatchMoveEvent(offsetBefore, event) { const offset = this.offset; if (Math.floor(offset - offsetBefore) === 0) return; // skip if offset is not changed const indexesAfter = this.activeIndexes; const details = Object.assign(Object.assign({}, event), { offset, offsetBefore, indexesAfter }); this.$carousel.dispatchEvent(ESLCarouselMoveEvent.create(details)); } // Register API static register(view = this) { ESLCarouselRendererRegistry.instance.register(view); } } /** CSS variable name to set transition duration */ ESLCarouselRenderer.TRANSITION_DURATION_PROP = '--esl-carousel-step-duration'; ESLCarouselRenderer.classes = [];