UNPKG

uikit

Version:

UIkit is a lightweight and modular front-end framework for developing fast and powerful web interfaces.

385 lines (322 loc) • 11.3 kB
import { $, $$, addClass, attr, children, css, data, dimensions, findIndex, getIndex, hasOwn, includes, isVisible, last, selFocusable, sumBy, toFloat, toNumber, toggleClass, } from 'uikit-util'; import { resize } from '../api/observables'; import Class from '../mixin/class'; import Slider, { speedUp } from '../mixin/slider'; import SliderParallax from '../mixin/slider-parallax'; import SliderReactive from '../mixin/slider-reactive'; import SliderPreload from './internal/slider-preload'; import Transitioner, { getMax, getWidth } from './internal/slider-transitioner'; export default { mixins: [Class, Slider, SliderReactive, SliderParallax, SliderPreload], props: { center: Boolean, sets: Boolean, active: String, }, data: { center: false, sets: false, attrItem: 'uk-slider-item', selList: '.uk-slider-items', selNav: '.uk-slider-nav', clsContainer: 'uk-slider-container', active: 'all', Transitioner, }, computed: { finite({ finite }) { return finite || isFinite(this.list, this.center); }, maxIndex() { if (!this.finite || (this.center && !this.sets)) { return this.length - 1; } if (this.center) { return last(this.sets); } let lft = 0; const max = getMax(this.list); const index = findIndex(this.slides, (el) => { if (lft >= max) { return true; } lft += dimensions(el).width; }); return ~index ? index : this.length - 1; }, sets({ sets: enabled }) { if (!enabled || this.parallax) { return; } let left = 0; const sets = []; const width = dimensions(this.list).width; for (let i = 0; i < this.length; i++) { const slideWidth = dimensions(this.slides[i]).width; if (left + slideWidth > width) { left = 0; } if (this.center) { if ( left < width / 2 && left + slideWidth + dimensions(this.slides[getIndex(+i + 1, this.slides)]).width / 2 > width / 2 ) { sets.push(+i); left = width / 2 - slideWidth / 2; } } else if (left === 0) { sets.push(Math.min(+i, this.maxIndex)); } left += slideWidth; } if (sets.length) { return sets; } }, transitionOptions() { return { center: this.center, list: this.list, }; }, slides() { return children(this.list).filter(isVisible); }, }, connected() { toggleClass(this.$el, this.clsContainer, !$(`.${this.clsContainer}`, this.$el)); }, observe: resize({ target: ({ slides, $el }) => [$el, ...slides], }), update: { write() { for (const el of this.navItems) { const index = toNumber(data(el, this.attrItem)); if (index !== false) { el.hidden = !this.maxIndex || index > this.maxIndex || (this.sets && !includes(this.sets, index)); } } this.reorder(); this.updateActiveClasses(); }, events: ['resize'], }, events: { beforeitemshow(e) { if ( !this.dragging && this.sets && this.stack.length < 2 && !includes(this.sets, this.index) ) { this.index = this.getValidIndex(); } const diff = Math.abs( this.index - this.prevIndex + ((this.dir > 0 && this.index < this.prevIndex) || (this.dir < 0 && this.index > this.prevIndex) ? (this.maxIndex + 1) * this.dir : 0), ); if (!this.dragging && diff > 1) { for (let i = 0; i < diff; i++) { this.stack.splice(1, 0, this.dir > 0 ? 'next' : 'previous'); } e.preventDefault(); return; } const index = this.dir < 0 || !this.slides[this.prevIndex] ? this.index : this.prevIndex; const avgWidth = getWidth(this.list) / this.length; this.duration = speedUp(avgWidth / this.velocity) * (dimensions(this.slides[index]).width / avgWidth); this.reorder(); }, itemshow() { if (~this.prevIndex) { addClass(this._getTransitioner().getItemIn(), this.clsActive); } this.updateActiveClasses(this.prevIndex); }, itemshown() { this.updateActiveClasses(); }, }, methods: { reorder() { if (this.finite) { css(this.slides, 'order', ''); return; } const index = this.dir > 0 && this.slides[this.prevIndex] ? this.prevIndex : this.index; this.slides.forEach((slide, i) => css( slide, 'order', this.dir > 0 && i < index ? 1 : this.dir < 0 && i >= this.index ? -1 : '', ), ); if (!this.center || !this.length) { return; } const next = this.slides[index]; let width = dimensions(this.list).width / 2 - dimensions(next).width / 2; let j = 0; while (width > 0) { const slideIndex = this.getIndex(--j + index, index); const slide = this.slides[slideIndex]; css(slide, 'order', slideIndex > index ? -2 : -1); width -= dimensions(slide).width; } }, updateActiveClasses(currentIndex = this.index) { let actives = this._getTransitioner(currentIndex).getActives(); if (this.active !== 'all') { actives = [this.slides[this.getValidIndex(currentIndex)]]; } const activeClasses = [ this.clsActive, !this.sets || includes(this.sets, toFloat(this.index)) ? this.clsActivated : '', ]; for (const slide of this.slides) { const active = includes(actives, slide); toggleClass(slide, activeClasses, active); attr(slide, 'aria-hidden', !active); for (const focusable of $$(selFocusable, slide)) { if (!hasOwn(focusable, '_tabindex')) { focusable._tabindex = attr(focusable, 'tabindex'); } attr(focusable, 'tabindex', active ? focusable._tabindex : -1); } } }, getValidIndex(index = this.index, prevIndex = this.prevIndex) { index = this.getIndex(index, prevIndex); if (!this.sets) { return index; } let prev; do { if (includes(this.sets, index)) { return index; } prev = index; index = this.getIndex(index + this.dir, prevIndex); } while (index !== prev); return index; }, getAdjacentSlides() { const { width } = dimensions(this.list); const left = -width; const right = width * 2; const slideWidth = dimensions(this.slides[this.index]).width; const slideLeft = this.center ? width / 2 - slideWidth / 2 : 0; const slides = new Set(); for (const i of [-1, 1]) { let currentLeft = slideLeft + (i > 0 ? slideWidth : 0); let j = 0; do { const slide = this.slides[this.getIndex(this.index + i + j++ * i)]; currentLeft += dimensions(slide).width * i; slides.add(slide); } while (this.length > j && currentLeft > left && currentLeft < right); } return Array.from(slides); }, getIndexAt(percent) { let index = -1; const scrollDist = this.center ? getWidth(this.list) - (dimensions(this.slides[0]).width / 2 + dimensions(last(this.slides)).width / 2) : getWidth(this.list, this.maxIndex); let dist = percent * scrollDist; let slidePercent = 0; do { const slideWidth = dimensions(this.slides[++index]).width; const slideDist = this.center ? slideWidth / 2 + dimensions(this.slides[index + 1]).width / 2 : slideWidth; slidePercent = (dist / slideDist) % 1; dist -= slideDist; } while (dist >= 0 && index < this.maxIndex); return [index, slidePercent]; }, }, }; function isFinite(list, center) { if (!list || list.length < 2) { return true; } const { width: listWidth } = dimensions(list); if (!center) { return Math.ceil(getWidth(list)) < Math.trunc(listWidth + getMaxElWidth(list)); } const slides = children(list); const listHalf = Math.trunc(listWidth / 2); for (const index in slides) { const slide = slides[index]; const slideWidth = dimensions(slide).width; const slidesInView = new Set([slide]); let diff = 0; for (const i of [-1, 1]) { let left = slideWidth / 2; let j = 0; while (left < listHalf) { const nextSlide = slides[getIndex(+index + i + j++ * i, slides)]; if (slidesInView.has(nextSlide)) { return true; } left += dimensions(nextSlide).width; slidesInView.add(nextSlide); } diff = Math.max( diff, slideWidth / 2 + dimensions(slides[getIndex(+index + i, slides)]).width / 2 - (left - listHalf), ); } if ( Math.trunc(diff) > sumBy( slides.filter((slide) => !slidesInView.has(slide)), (slide) => dimensions(slide).width, ) ) { return true; } } return false; } function getMaxElWidth(list) { return Math.max(0, ...children(list).map((el) => dimensions(el).width)); }