// Styles import "../../../src/components/VSlideGroup/VSlideGroup.sass"; // Components import VIcon from '../VIcon'; import { VFadeTransition } from '../transitions'; // Extensions import { BaseItemGroup } from '../VItemGroup/VItemGroup'; // Directives import Resize from '../../directives/resize'; import Touch from '../../directives/touch'; // Utilities import mixins from '../../util/mixins'; export const BaseSlideGroup = mixins(BaseItemGroup /* @vue/component */ ).extend({ name: 'base-slide-group', directives: { Resize, Touch }, props: { activeClass: { type: String, default: 'v-slide-item--active' }, centerActive: Boolean, nextIcon: { type: String, default: '$vuetify.icons.next' }, mobileBreakPoint: { type: [Number, String], default: 1264, validator: v => !isNaN(parseInt(v)) }, prevIcon: { type: String, default: '$vuetify.icons.prev' }, showArrows: Boolean }, data: () => ({ isOverflowing: false, resizeTimeout: 0, startX: 0, scrollOffset: 0, widths: { content: 0, wrapper: 0 } }), computed: { __cachedNext() { return this.genTransition('next'); }, __cachedPrev() { return this.genTransition('prev'); }, classes() { return { ...BaseItemGroup.options.computed.classes.call(this), 'v-slide-group': true }; }, hasAffixes() { return (this.showArrows || !this.isMobile) && this.isOverflowing; }, hasNext() { if (!this.hasAffixes) return false; const { content, wrapper } = this.widths; // Check one scroll ahead to know the width of right-most item return content > Math.abs(this.scrollOffset) + wrapper; }, hasPrev() { return this.hasAffixes && this.scrollOffset !== 0; }, isMobile() { return this.$vuetify.breakpoint.width < this.mobileBreakPoint; } }, watch: { internalValue: 'setWidths', // When overflow changes, the arrows alter // the widths of the content and wrapper // and need to be recalculated isOverflowing: 'setWidths', scrollOffset(val) { this.$refs.content.style.transform = `translateX(${-val}px)`; } }, methods: { genNext() { if (!this.hasAffixes) return null; const slot = this.$scopedSlots.next ? this.$scopedSlots.next({}) : this.$slots.next || this.__cachedNext; return this.$createElement('div', { staticClass: 'v-slide-group__next', class: { 'v-slide-group__next--disabled': !this.hasNext }, on: { click: () => this.onAffixClick('next') }, key: 'next' }, [slot]); }, genContent() { return this.$createElement('div', { staticClass: 'v-slide-group__content', ref: 'content' }, this.$slots.default); }, genData() { return { class: this.classes, directives: [{ name: 'resize', value: this.onResize }] }; }, genIcon(location) { let icon = location; if (this.$vuetify.rtl && location === 'prev') { icon = 'next'; } else if (this.$vuetify.rtl && location === 'next') { icon = 'prev'; } const upperLocation = `${location[0].toUpperCase()}${location.slice(1)}`; const hasAffix = this[`has${upperLocation}`]; if (!this.showArrows && !hasAffix) return null; return this.$createElement(VIcon, { props: { disabled: !hasAffix } }, this[`${icon}Icon`]); }, genPrev() { if (!this.hasAffixes) return null; const slot = this.$scopedSlots.prev ? this.$scopedSlots.prev({}) : this.$slots.prev || this.__cachedPrev; return this.$createElement('div', { staticClass: 'v-slide-group__prev', class: { 'v-slide-group__prev--disabled': !this.hasPrev }, on: { click: () => this.onAffixClick('prev') }, key: 'prev' }, [slot]); }, genTransition(location) { return this.$createElement(VFadeTransition, [this.genIcon(location)]); }, genWrapper() { return this.$createElement('div', { staticClass: 'v-slide-group__wrapper', directives: [{ name: 'touch', value: { start: e => this.overflowCheck(e, this.onTouchStart), move: e => this.overflowCheck(e, this.onTouchMove), end: e => this.overflowCheck(e, this.onTouchEnd) } }], ref: 'wrapper' }, [this.genContent()]); }, calculateNewOffset(direction, widths, rtl, currentScrollOffset) { const sign = rtl ? -1 : 1; const newAbosluteOffset = sign * currentScrollOffset + (direction === 'prev' ? -1 : 1) * widths.wrapper; return sign * Math.max(Math.min(newAbosluteOffset, widths.content - widths.wrapper), 0); }, onAffixClick(location) { this.$emit(`click:${location}`); this.scrollTo(location); }, onResize() { /* istanbul ignore next */ if (this._isDestroyed) return; this.setWidths(); }, onTouchStart(e) { const { content } = this.$refs; this.startX = this.scrollOffset + e.touchstartX; content.style.setProperty('transition', 'none'); content.style.setProperty('willChange', 'transform'); }, onTouchMove(e) { this.scrollOffset = this.startX - e.touchmoveX; }, onTouchEnd() { const { content, wrapper } = this.$refs; const maxScrollOffset = content.clientWidth - wrapper.clientWidth; content.style.setProperty('transition', null); content.style.setProperty('willChange', null); /* istanbul ignore else */ if (this.scrollOffset < 0 || !this.isOverflowing) { this.scrollOffset = 0; } else if (this.scrollOffset >= maxScrollOffset) { this.scrollOffset = maxScrollOffset; } }, overflowCheck(e, fn) { e.stopPropagation(); this.isOverflowing && fn(e); }, scrollIntoView /* istanbul ignore next */ () { if (!this.selectedItem) { return; } if (this.centerActive) { this.scrollOffset = this.calculateCenteredOffset(this.selectedItem.$el, this.widths, this.$vuetify.rtl); } else if (this.isOverflowing) { this.scrollOffset = this.calculateUpdatedOffset(this.selectedItem.$el, this.widths, this.$vuetify.rtl, this.scrollOffset); } else { this.scrollOffset = 0; } }, calculateUpdatedOffset(selectedElement, widths, rtl, currentScrollOffset) { const clientWidth = selectedElement.clientWidth; const offsetLeft = rtl ? widths.content - selectedElement.offsetLeft - clientWidth : selectedElement.offsetLeft; if (rtl) { currentScrollOffset = -currentScrollOffset; } const totalWidth = widths.wrapper + currentScrollOffset; const itemOffset = clientWidth + offsetLeft; const additionalOffset = clientWidth * 0.3; if (offsetLeft < currentScrollOffset) { currentScrollOffset = Math.max(offsetLeft - additionalOffset, 0); } else if (totalWidth < itemOffset) { currentScrollOffset = Math.min(currentScrollOffset - (totalWidth - itemOffset - additionalOffset), widths.content - widths.wrapper); } return rtl ? -currentScrollOffset : currentScrollOffset; }, calculateCenteredOffset(selectedElement, widths, rtl) { const { offsetLeft, clientWidth } = selectedElement; if (rtl) { const offsetCentered = widths.content - offsetLeft - clientWidth / 2 - widths.wrapper / 2; return -Math.min(widths.content - widths.wrapper, Math.max(0, offsetCentered)); } else { const offsetCentered = offsetLeft + clientWidth / 2 - widths.wrapper / 2; return Math.min(widths.content - widths.wrapper, Math.max(0, offsetCentered)); } }, scrollTo /* istanbul ignore next */ (location) { this.scrollOffset = this.calculateNewOffset(location, { // Force reflow content: this.$refs.content ? this.$refs.content.clientWidth : 0, wrapper: this.$refs.wrapper ? this.$refs.wrapper.clientWidth : 0 }, this.$vuetify.rtl, this.scrollOffset); }, setWidths /* istanbul ignore next */ () { window.requestAnimationFrame(() => { const { content, wrapper } = this.$refs; this.widths = { content: content ? content.clientWidth : 0, wrapper: wrapper ? wrapper.clientWidth : 0 }; this.isOverflowing = this.widths.wrapper < this.widths.content; this.scrollIntoView(); }); } }, render(h) { return h('div', this.genData(), [this.genPrev(), this.genWrapper(), this.genNext()]); } }); export default BaseSlideGroup.extend({ name: 'v-slide-group', provide() { return { slideGroup: this }; } }); //# sourceMappingURL=VSlideGroup.js.map