UNPKG

@exadel/esl

Version:

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

238 lines (237 loc) 11.8 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 __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()); }); }; var ESLDefaultCarouselRenderer_1; import { ESLMediaQuery } from '../../esl-media-query/core'; import { normalize, sign } from '../core/esl-carousel.utils'; import { ESLCarouselRenderer } from '../core/esl-carousel.renderer'; /** * Default carousel renderer based on CSS Flexbox stage, order (flex), and stage animated movement via CSS transform. * Supports multiple slides per view, (infinite) loop mode, touch-move, vertical mode, slide siblings rendering. * * Provides default slide width, supports gap between slides. Does not rely on default slide width, potentially can be used with CSS custom slide width. */ let ESLDefaultCarouselRenderer = ESLDefaultCarouselRenderer_1 = class ESLDefaultCarouselRenderer extends ESLCarouselRenderer { constructor() { super(...arguments); /** Slides gap size */ this.gap = 0; /** First index of active slides. */ this.currentIndex = 0; } /** Multiplier for the index move on the slide move */ get INDEX_MOVE_MULTIPLIER() { return 1; } /** @returns true if moving half of the slides before current is forbidden */ get lazyReorder() { const reserve = this.$carousel.getAttribute('lazy-reorder'); if (reserve === null) return false; return ESLMediaQuery.for(reserve).matches; } /** Actual slide size (uses average) */ get slideSize() { return this.$slides.reduce((size, $slide) => { return size + (this.vertical ? $slide.offsetHeight : $slide.offsetWidth); }, 0) / this.$slides.length; } /** * Processes binding of defined renderer to the carousel {@link ESLCarousel}. * Prepare to renderer animation. */ onBind() { this.currentIndex = this.normalizeIndex(Math.max(0, this.$carousel.activeIndex)); this.redraw(true); } redraw(initial = false) { this.resize(); // Calculate initial offset based on current rendered state (available only on an initial render) const fallbackOffset = initial ? this.getOffset(this.getReserveCount()) : 0; this.reorder(); this.setActive(this.currentIndex); // Set initial offset based on pre-calculation initial && this.setTransformOffset(-fallbackOffset); // Update offset according to main algorithm (fix edge cases if the fallback offset is not correct) this.setTransformOffset(-this.getOffset(this.currentIndex)); } /** * Processes unbinding of defined renderer from the carousel {@link ESLCarousel}. * Clear animation. */ onUnbind() { this.$slides.forEach((el) => el.style.removeProperty('order')); this.$area.style.removeProperty('transform'); this.$area.style.removeProperty(ESLDefaultCarouselRenderer_1.SIZE_PROP); this.$carousel.$$attr('animating', false); this.$carousel.$$attr('active', false); } /** @returns slide offset by the slide index */ getOffset(index) { const slide = this.$slides[index]; if (!slide) return 0; return this.vertical ? slide.offsetTop : slide.offsetLeft; } /** Sets scene offset */ setTransformOffset(offset) { this.$area.style.transform = `translate3d(${this.vertical ? `0px, ${offset}px` : `${offset}px, 0px`}, 0px)`; } /** Animates scene offset to index */ animateTo(index_1) { return __awaiter(this, arguments, void 0, function* (index, duration = 250) { this.currentIndex = this.normalizeIndex(index); const offset = -this.getOffset(this.currentIndex); this.$carousel.$$attr('animating', true); yield this.$area.animate({ transform: [`translate3d(${this.vertical ? `0px, ${offset}px` : `${offset}px, 0px`}, 0px)`] }, { duration, easing: 'linear' }).finished; this.$carousel.$$attr('animating', false); }); } /** Pre-processing animation action. */ onBeforeAnimate(nextIndex, direction, params) { return __awaiter(this, void 0, void 0, function* () { if (this.$carousel.hasAttribute('animating')) throw new Error('[ESL] Carousel: already animating'); this.$carousel.$$attr('active', true); }); } /** Processes animation. */ onAnimate(nextIndex, direction, params) { return __awaiter(this, void 0, void 0, function* () { const { activeIndex, $slidesArea } = this.$carousel; this.currentIndex = activeIndex; if (!$slidesArea) return; const distance = normalize((nextIndex - activeIndex) * direction, this.size); const speed = Math.min(1, this.count / distance); while (this.currentIndex !== nextIndex) { yield this.onStepAnimate(direction * this.INDEX_MOVE_MULTIPLIER, params.stepDuration * speed); } }); } /** Post-processing animation action. */ onAfterAnimate(nextIndex, direction, params) { return __awaiter(this, void 0, void 0, function* () { // Make sure we end up in a defined state on transition end this.reorder(); this.setTransformOffset(-this.getOffset(this.currentIndex)); this.$carousel.$$attr('active', false); }); } /** Makes pre-processing the transition animation of one slide. */ onStepAnimate(indexOffset, duration) { return __awaiter(this, void 0, void 0, function* () { const index = normalize(this.currentIndex + indexOffset, this.size); // Make sure there is a slide in required direction this.reorder(indexOffset < 0); const offsetFrom = -this.getOffset(this.currentIndex); this.setTransformOffset(offsetFrom); yield this.animateTo(index, duration); }); } /** Handles the slides transition. */ move(offset, from, params) { this.$carousel.toggleAttribute('active', true); const slideSize = this.slideSize + this.gap; const count = Math.floor(Math.abs(offset) / slideSize); const index = from + count * this.INDEX_MOVE_MULTIPLIER * (offset < 0 ? 1 : -1); // check left border of non-loop state if (!this.loop && offset > 0 && index <= 0) return; // check right border of non-loop state if (!this.loop && offset < 0 && index + this.count >= this.size) return; this.currentIndex = normalize(index, this.size); this.reorder(offset > 0); const stageOffset = this.getOffset(this.currentIndex) - (offset % slideSize); this.setTransformOffset(-stageOffset); if (this.currentIndex !== this.$carousel.activeIndex) { this.setActive(this.currentIndex, { direction: sign(-offset) }); } } /** Ends current transition and make permanent all changes performed in the transition. */ commit(offset, from, params) { return __awaiter(this, void 0, void 0, function* () { const slideSize = this.slideSize + this.gap; const amount = Math.abs(offset) / slideSize; const tolerance = ESLDefaultCarouselRenderer_1.NEXT_SLIDE_TOLERANCE; const count = (amount - Math.floor(amount)) > tolerance ? Math.ceil(amount) : Math.floor(amount); const index = from + count * this.INDEX_MOVE_MULTIPLIER * (offset < 0 ? 1 : -1); yield this.animateTo(index); this.reorder(); this.setTransformOffset(-this.getOffset(this.currentIndex)); this.$carousel.$$attr('active', false); if (this.currentIndex !== this.$carousel.activeIndex) { this.setActive(this.currentIndex, { direction: sign(-offset) }); } }); } /** * @returns count of slides to be rendered (reserved) before the first slide */ getReserveCount(back) { const { size, count, loop, currentIndex } = this; const freeSlides = size - count; // no need to reorder if there are no free slides or loop is disabled if (!loop || !freeSlides) return 0; // if back option is not set, prefer to reserve slides with respect to semantic order if (typeof back !== 'boolean') back = !!currentIndex; // Check if reorder is forbidden (if back option is set - we should reserve at least one slide for animation) if (this.lazyReorder) return back ? 1 : 0; // otherwise, ensure that there are at least half of free slides reserved (if the back option is set - round up, otherwise - round down) return back ? Math.ceil(freeSlides / 2) : Math.floor(freeSlides / 2); } /** * Sets order style property for slides starting at index * @param back - if true, ensures that there is a slide rendered before the current one */ reorder(back) { const { size, loop, currentIndex, $slides } = this; const reserve = this.getReserveCount(back); const index = loop ? currentIndex : 0; for (let i = 0; i < size; ++i) { let offset = (size + i - index) % size; // inverses index for backward reserve if (offset >= size - reserve) offset -= size; $slides[i].style.order = String(offset); } } /** Sets min size for slides */ resize() { if (!this.$area) return; const areaStyles = getComputedStyle(this.$area); this.gap = parseFloat(this.vertical ? areaStyles.rowGap : areaStyles.columnGap); const areaSize = parseFloat(this.vertical ? areaStyles.height : areaStyles.width); const slideSize = Math.floor((areaSize - this.gap * (this.count - 1)) / this.count); this.$area.style.setProperty(ESLDefaultCarouselRenderer_1.SIZE_PROP, slideSize + 'px'); } }; ESLDefaultCarouselRenderer.is = 'default'; ESLDefaultCarouselRenderer.classes = ['esl-carousel-default-renderer']; /** CSS variable name for slide auto size */ ESLDefaultCarouselRenderer.SIZE_PROP = '--esl-slide-size'; /** Tolerance to treat offset enough to move to the next slide. Relative (0-1) to slide width */ ESLDefaultCarouselRenderer.NEXT_SLIDE_TOLERANCE = 0.25; ESLDefaultCarouselRenderer = ESLDefaultCarouselRenderer_1 = __decorate([ ESLCarouselRenderer.register ], ESLDefaultCarouselRenderer); export { ESLDefaultCarouselRenderer };