diff --git a/index.css b/index.css index c6ce5f8..3e9f82f 100644 --- a/index.css +++ b/index.css @@ -164,9 +164,11 @@ overflow: hidden; } .ssr-carousel-mask:not(.disabled):not(.not-draggable) { + cursor: -webkit-grab; cursor: grab; } .ssr-carousel-mask:not(.disabled):not(.not-draggable).pressing { + cursor: -webkit-grabbing; cursor: grabbing; } .ssr-carousel-visually-hidden { diff --git a/index.js b/index.js index 0be2b98..c31427a 100644 --- a/index.js +++ b/index.js @@ -362,7 +362,7 @@ ssr_carousel_arrowsvue_type_template_id_559c4b35_lang_pug_render._withStripped = }); // CONCATENATED MODULE: ./src/ssr-carousel-arrows.vue?vue&type=script&lang=coffee& /* harmony default export */ var src_ssr_carousel_arrowsvue_type_script_lang_coffee_ = (ssr_carousel_arrowsvue_type_script_lang_coffee_); -// CONCATENATED MODULE: ./node_modules/mini-css-extract-plugin/dist/loader.js!./node_modules/css-loader/dist/cjs.js!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/dist/cjs.js!./node_modules/stylus-loader!./node_modules/vue-loader/lib??vue-loader-options!./src/ssr-carousel-arrows.vue?vue&type=style&index=0&id=559c4b35&prod&lang=stylus& +// CONCATENATED MODULE: ./node_modules/mini-css-extract-plugin/dist/loader.js!./node_modules/css-loader/dist/cjs.js!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src!./node_modules/stylus-loader!./node_modules/vue-loader/lib??vue-loader-options!./src/ssr-carousel-arrows.vue?vue&type=style&index=0&id=559c4b35&prod&lang=stylus& // extracted by mini-css-extract-plugin // CONCATENATED MODULE: ./src/ssr-carousel-arrows.vue?vue&type=style&index=0&id=559c4b35&prod&lang=stylus& @@ -546,7 +546,7 @@ ssr_carousel_dotsvue_type_template_id_98a1e6f8_lang_pug_render._withStripped = t }); // CONCATENATED MODULE: ./src/ssr-carousel-dots.vue?vue&type=script&lang=coffee& /* harmony default export */ var src_ssr_carousel_dotsvue_type_script_lang_coffee_ = (ssr_carousel_dotsvue_type_script_lang_coffee_); -// CONCATENATED MODULE: ./node_modules/mini-css-extract-plugin/dist/loader.js!./node_modules/css-loader/dist/cjs.js!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/dist/cjs.js!./node_modules/stylus-loader!./node_modules/vue-loader/lib??vue-loader-options!./src/ssr-carousel-dots.vue?vue&type=style&index=0&id=98a1e6f8&prod&lang=stylus& +// CONCATENATED MODULE: ./node_modules/mini-css-extract-plugin/dist/loader.js!./node_modules/css-loader/dist/cjs.js!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src!./node_modules/stylus-loader!./node_modules/vue-loader/lib??vue-loader-options!./src/ssr-carousel-dots.vue?vue&type=style&index=0&id=98a1e6f8&prod&lang=stylus& // extracted by mini-css-extract-plugin // CONCATENATED MODULE: ./src/ssr-carousel-dots.vue?vue&type=style&index=0&id=98a1e6f8&prod&lang=stylus& @@ -574,7 +574,7 @@ var ssr_carousel_dots_component = normalizeComponent( /* harmony default export */ var ssr_carousel_dots = (ssr_carousel_dots_component.exports); // CONCATENATED MODULE: ./node_modules/babel-loader/lib!./node_modules/coffee-loader!./node_modules/vue-loader/lib??vue-loader-options!./src/ssr-carousel-track.vue?vue&type=script&lang=coffee& var interactiveSelector, - indexOf = [].indexOf; + indexOf = [].indexOf; interactiveSelector = 'a, button, input, textarea, select'; /* harmony default export */ var ssr_carousel_trackvue_type_script_lang_coffee_ = ({ props: { @@ -585,12 +585,26 @@ interactiveSelector = 'a, button, input, textarea, select'; leftPeekingSlideIndex: Number, rightPeekingSlideIndex: Number }, + // Should the track element be an ul + data: function () { + return { + renderAsList: false + }; + }, // Set tabindex of inactive slides on mount mounted: function () { this.denyTabindex(this.inactiveSlides); return this.denyTabindex(this.clonedSlides); }, computed: { + // The HTML element of the track + trackHTMLElement: function () { + if (this.renderAsList) { + return 'ul'; + } else { + return 'div'; + } + }, // Get the count of non-cloned slides uniqueSlidesCount: function () { return this.slideOrder.length; @@ -608,9 +622,11 @@ interactiveSelector = 'a, button, input, textarea, select'; var ref; return function () { var results = []; + for (var j = 0, ref = this.uniqueSlidesCount; 0 <= ref ? j < ref : j > ref; 0 <= ref ? j++ : j--) { results.push(j); } + return results; }.apply(this).filter(index => { return indexOf.call(this.activeSlides, index) < 0; @@ -621,9 +637,11 @@ interactiveSelector = 'a, button, input, textarea, select'; var ref, ref1; return function () { var results = []; + for (var j = ref = this.uniqueSlidesCount, ref1 = this.allSlidesCount; ref <= ref1 ? j < ref1 : j > ref1; ref <= ref1 ? j++ : j--) { results.push(j); } + return results; }.apply(this); }, @@ -649,18 +667,25 @@ interactiveSelector = 'a, button, input, textarea, select'; return this.getSlideComponents().map((vnode, index) => { var cssClass, isPeekingClone, peekingIndex, slideCount; vnode = this.makeReactiveVnode(vnode); - // This is a peeking clone if it's index is greater than the slide count + + if (!index && vnode.tag === 'li') { + this.renderAsList = true; + } // This is a peeking clone if it's index is greater than the slide count + + slideCount = this.uniqueSlidesCount; isPeekingClone = index >= slideCount; - peekingIndex = index - slideCount; - // Add the slide class using staticClass since it isn't reactive to data + peekingIndex = index - slideCount; // Add the slide class using staticClass since it isn't reactive to data + cssClass = 'ssr-carousel-slide'; + if (vnode.data.staticClass) { vnode.data.staticClass += ` ${cssClass}`; } else { vnode.data.staticClass = cssClass; - } - // Order the slide, like for looping + } // Order the slide, like for looping + + if (!isPeekingClone) { vnode.data.style.order = this.slideOrder[index] || 0; } else { @@ -669,24 +694,29 @@ interactiveSelector = 'a, button, input, textarea, select'; switch (false) { case peekingIndex !== this.leftPeekingSlideIndex: return '-1'; + case peekingIndex !== this.rightPeekingSlideIndex: return this.slideOrder.length; } }.call(this); - } - // Hide cloned slides that aren't involved in peeking + } // Hide cloned slides that aren't involved in peeking + + if (isPeekingClone && peekingIndex !== this.leftPeekingSlideIndex && peekingIndex !== this.rightPeekingSlideIndex) { vnode.data.style.display = 'none'; - } - // Make peeking clones and slides not in viewport as aria-hidden + } // Make peeking clones and slides not in viewport as aria-hidden + + if (isPeekingClone || indexOf.call(this.activeSlides, index) < 0) { vnode.data.attrs['aria-hidden'] = 'true'; - } - // Prevent duplicate keys on clones + } // Prevent duplicate keys on clones + + if (isPeekingClone && vnode.key != null) { vnode.key += '-clone-' + index; - } - // Return modified vnode + } // Return modified vnode + + return vnode; }); }, @@ -702,28 +732,26 @@ interactiveSelector = 'a, button, input, textarea, select'; // https://github.com/vuejs/vue/issues/6052#issuecomment-313705168 makeReactiveVnode: function (vnode) { var newVnode; + if (!vnode.data) { // Expect a data object. When it doesn't exist, it's a sign this this // vnode can't be manipulated vue-ssr-carousel. console.error("vnode has no data", vnode); - } - // Make the new vnode and data - newVnode = { - ...vnode - }; - newVnode.data = { - ...vnode.data + } // Make the new vnode and data + + + newVnode = { ...vnode }; - // Clone style property. String styles will be on staticStyle so we can + newVnode.data = { ...vnode.data + }; // Clone style property. String styles will be on staticStyle so we can // ignore them. - newVnode.data.style = { - ...vnode.data.style - }; - // Clone attrs property - newVnode.data.attrs = { - ...vnode.data.attrs - }; - // Return the clone + + newVnode.data.style = { ...vnode.data.style + }; // Clone attrs property + + newVnode.data.attrs = { ...vnode.data.attrs + }; // Return the clone + return newVnode; }, // Prevent tabbing to interactive elements in slides with the passed in @@ -741,18 +769,21 @@ interactiveSelector = 'a, button, input, textarea, select'; var el, j, len, ref, results; ref = this.getSlideElementsByIndices(slideIndices); results = []; + for (j = 0, len = ref.length; j < len; j++) { - el = ref[j]; - // Set tabindex value on the slide, like in the case that the slide is + el = ref[j]; // Set tabindex value on the slide, like in the case that the slide is // an + if (el.matches(interactiveSelector)) { el.tabIndex = tabindexValue; - } - // Set tabindex values on all interactive children + } // Set tabindex values on all interactive children + + results.push(el.querySelectorAll(interactiveSelector).forEach(function (el) { return el.tabIndex = tabindexValue; })); } + return results; }, // Get the slide elements that match the array of indices @@ -764,7 +795,7 @@ interactiveSelector = 'a, button, input, textarea, select'; }, // Render the track and slotted slides render: function (create) { - return create('div', { + return create(this.trackHTMLElement, { class: ['ssr-carousel-track', { dragging: this.dragging }], @@ -774,10 +805,10 @@ interactiveSelector = 'a, button, input, textarea, select'; }); // CONCATENATED MODULE: ./src/ssr-carousel-track.vue?vue&type=script&lang=coffee& /* harmony default export */ var src_ssr_carousel_trackvue_type_script_lang_coffee_ = (ssr_carousel_trackvue_type_script_lang_coffee_); -// CONCATENATED MODULE: ./node_modules/mini-css-extract-plugin/dist/loader.js!./node_modules/css-loader/dist/cjs.js!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/dist/cjs.js!./node_modules/stylus-loader!./node_modules/vue-loader/lib??vue-loader-options!./src/ssr-carousel-track.vue?vue&type=style&index=0&id=01145ade&prod&lang=stylus& +// CONCATENATED MODULE: ./node_modules/mini-css-extract-plugin/dist/loader.js!./node_modules/css-loader/dist/cjs.js!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src!./node_modules/stylus-loader!./node_modules/vue-loader/lib??vue-loader-options!./src/ssr-carousel-track.vue?vue&type=style&index=0&id=47a1014b&prod&lang=stylus& // extracted by mini-css-extract-plugin -// CONCATENATED MODULE: ./src/ssr-carousel-track.vue?vue&type=style&index=0&id=01145ade&prod&lang=stylus& +// CONCATENATED MODULE: ./src/ssr-carousel-track.vue?vue&type=style&index=0&id=47a1014b&prod&lang=stylus& // CONCATENATED MODULE: ./src/ssr-carousel-track.vue var ssr_carousel_track_render, ssr_carousel_track_staticRenderFns @@ -821,8 +852,10 @@ Code related to supporting keyboard interaction and screen readers switch (false) { case !this.paginationLabel: return this.paginationLabel; + case !this.paginateBySlide: return "Slide"; + default: return "Page"; } @@ -883,6 +916,7 @@ Code related to auotplay features of the carousel case !this.usingKeyboard: return true; // Stop animation if window is hidden or if carousel is focused + case !this.pauseOnFocus: return this.windowHidden || this.isFocused; } @@ -903,12 +937,14 @@ Code related to auotplay features of the carousel // Require a delay amount if (!this.autoplayDelay) { return; - } - // Don't loop if we only have one page + } // Don't loop if we only have one page + + if (!this.pages) { return; - } - // Start autoplaying + } // Start autoplaying + + return this.autoPlayInterval = setInterval(() => { if (!this.autoplayPaused) { return this.autoplayNext(); @@ -940,9 +976,9 @@ Code related to measuring the size of the carousel after mounting carouselWidth: null, // Width of a page of the carousel gutterWidth: 0 // Computed width of gutters, since they support css vars + }; }, - // Add resize listening mounted: function () { this.onResize(); @@ -975,16 +1011,18 @@ Code related to measuring the size of the carousel after mounting // Figure out the width of the last page, which may not have enough slides // to fill it. lastPageWidth: function () { - var slidesOnLastPage, slidesPerPage, width; - // Determine how many slides are on the final page of pagination. If the + var slidesOnLastPage, slidesPerPage, width; // Determine how many slides are on the final page of pagination. If the // remainder was 0, that means the page is flush with slides, so swap // the 0 for the max amount. + slidesPerPage = this.currentSlidesPerPage; slidesOnLastPage = this.slidesCount % slidesPerPage; + if (slidesOnLastPage === 0) { slidesOnLastPage = slidesPerPage; - } - // Turn the slide count into a width value + } // Turn the slide count into a width value + + width = slidesOnLastPage * this.slideWidth; return width; }, @@ -1010,16 +1048,20 @@ Code related to measuring the size of the carousel after mounting // the width of the gutter since that's effectively part of the page. onResize: function () { var firstSlide, ref; + if (((ref = this.$el) != null ? ref.nodeType : void 0) !== Node.ELEMENT_NODE) { return; } + if (!(firstSlide = this.$refs.track.$el.firstElementChild)) { return; } + this.gutterWidth = parseInt(getComputedStyle(firstSlide).marginRight); this.carouselWidth = this.$el.getBoundingClientRect().width + this.gutterWidth; this.viewportWidth = window.innerWidth; this.capturePeekingMeasurements(); + if (this.isVariableWidth) { return this.captureTrackWidth(); } @@ -1030,6 +1072,7 @@ Code related to measuring the size of the carousel after mounting if (this.isVariableWidth) { return; } + return `${this.scopeSelector} .ssr-carousel-slide { width: ${this.makeSlideWidthCalc(breakpoint)}; }`; @@ -1042,17 +1085,19 @@ Code related to measuring the size of the carousel after mounting var gutter, isDisabled, peekLeft, peekRight, slidesPerPage; isDisabled = this.isDisabledAtBreakpoint(breakpoint); slidesPerPage = this.getResponsiveValue('slidesPerPage', breakpoint); - gutter = this.getResponsiveValue('gutter', breakpoint); - // A common use case when not looping is to have a larger peek on just the + gutter = this.getResponsiveValue('gutter', breakpoint); // A common use case when not looping is to have a larger peek on just the // right. But when disabled, this looks strange. So this balances out // the peeking in the disbaled state. + peekLeft = this.getResponsiveValue('peekLeft', breakpoint); + if (this.matchPeekWhenDisabled && isDisabled) { peekRight = peekLeft; } else { peekRight = this.getResponsiveValue('peekRight', breakpoint); - } - // Render the styles + } // Render the styles + + return `calc( ${100 / slidesPerPage}% - (${this.autoUnit(peekLeft)} + ${this.autoUnit(peekRight)}) / ${slidesPerPage} - (${this.autoUnit(gutter)} * ${slidesPerPage - 1}) / ${slidesPerPage} )`; } } @@ -1106,9 +1151,9 @@ notPassive = { dragVelocity: null, // The px/tick while dragging, negative is rightward dragDirectionRatio: null // The ratio of horizontal vs vertical dragging + }; }, - // Cleanup listeners beforeDestroy: function () { window.removeEventListener('mousemove', this.onPointerMove, passive); @@ -1127,8 +1172,10 @@ notPassive = { case !(Math.abs(this.dragVelocity) <= 2): return Math.round(this.fractionalIndex); // User was moving forward + case !(this.dragVelocity < 0): return Math.ceil(this.fractionalIndex); + default: // User was moving backward return Math.floor(this.fractionalIndex); @@ -1143,37 +1190,42 @@ notPassive = { // haven't traveled the same width as it took to get from 1 to 2. fractionalIndex: function () { var distanceIntoPage, isLastPage, pageIndex, pageProgressPercent, pageWidth, remainingSlides, setIndex, slidesPerPage, widthDivisor, x; + if (!this.trackWidth) { return 0; - } - // Work in positive numbers - x = this.currentX * -1; - // Figure out what set we're in, like if, through looping, we've gone + } // Work in positive numbers + + + x = this.currentX * -1; // Figure out what set we're in, like if, through looping, we've gone // through all the pages multiple times. - setIndex = Math.floor(x / this.trackWidth); - // Figure out the index of last page of the set that has been fully + + setIndex = Math.floor(x / this.trackWidth); // Figure out the index of last page of the set that has been fully // scrolled into. Not using modulo for this because I got rounding errors. + widthDivisor = this.paginateBySlide ? this.slideWidth : this.pageWidth; - pageIndex = Math.floor((x - setIndex * this.trackWidth) / widthDivisor); - // Figure out the progress into the current page - distanceIntoPage = x - setIndex * this.trackWidth - pageIndex * widthDivisor; - // Determine if we're on the last page. If we're not looping, an extra + pageIndex = Math.floor((x - setIndex * this.trackWidth) / widthDivisor); // Figure out the progress into the current page + + distanceIntoPage = x - setIndex * this.trackWidth - pageIndex * widthDivisor; // Determine if we're on the last page. If we're not looping, an extra // "page" of slides is treated as part of the last page because of how we // end with the slides flush with the right edge. + slidesPerPage = this.currentSlidesPerPage; + remainingSlides = function () { switch (false) { case !this.shouldLoop: return this.slidesCount - pageIndex * slidesPerPage; + default: return this.slidesCount - (pageIndex + 1) * slidesPerPage; } }.call(this); - isLastPage = remainingSlides <= slidesPerPage; - // Make a percentage of travel into the page + + isLastPage = remainingSlides <= slidesPerPage; // Make a percentage of travel into the page + pageWidth = isLastPage ? this.lastPageWidth : widthDivisor; - pageProgressPercent = distanceIntoPage / pageWidth; - // Return the final value by adding all the passed index values + pageProgressPercent = distanceIntoPage / pageWidth; // Return the final value by adding all the passed index values + return pageProgressPercent + setIndex * this.pages + pageIndex; }, // Determine if the user is dragging vertically @@ -1181,6 +1233,7 @@ notPassive = { if (!this.dragDirectionRatio) { return; } + return this.dragDirectionRatio < this.verticalDragTreshold; }, // If we're horiztonally swiping on a touch device, prevent vertical scroll @@ -1191,15 +1244,16 @@ notPassive = { watch: { // Watch for mouse move changes when the user starts dragging pressing: function () { - var moveEvent, upEvent; - // Determine the type of event - [moveEvent, upEvent] = this.isTouchDrag ? ['touchmove', 'touchend'] : ['mousemove', 'mouseup']; - // Pointer is down, start watching for drags + var moveEvent, upEvent; // Determine the type of event + + [moveEvent, upEvent] = this.isTouchDrag ? ['touchmove', 'touchend'] : ['mousemove', 'mouseup']; // Pointer is down, start watching for drags + if (this.pressing) { window.addEventListener(moveEvent, this.onPointerMove, passive); window.addEventListener(upEvent, this.onPointerUp, passive); window.addEventListener('contextmenu', this.onPointerUp, passive); this.dragVelocity = 0; // Reset any previous velocity + this.preventContentDrag(); this.stopTweening(); } else { @@ -1210,25 +1264,26 @@ notPassive = { this.gotoStart(); } else { this.gotoEnd(); - } - // If rendering variable width slides, don't come to a rest at an index + } // If rendering variable width slides, don't come to a rest at an index + } else if (this.isVariableWidth) { - this.tweenToStop(); - // If user was vertically dragging, reset the index + this.tweenToStop(); // If user was vertically dragging, reset the index } else if (this.isVerticalDrag) { this.goto(this.index); } else { // Handle normal swiping this.goto(this.dragIndex); - } - // Cleanup vars and listeners + } // Cleanup vars and listeners + + window.removeEventListener(moveEvent, this.onPointerMove, passive); window.removeEventListener(upEvent, this.onPointerUp, passive); window.removeEventListener('contextmenu', this.onPointerUp, passive); this.dragging = false; this.startPointer = this.lastPointer = this.dragDirectionRatio = null; - } - // Fire events + } // Fire events + + if (this.pressing) { return this.$emit('press'); } else { @@ -1250,6 +1305,7 @@ notPassive = { if (!(this.isVerticalDrag && this.isTouchDrag)) { return; } + return this.pressing = false; }, // Stop vertical scrolling by listening for touchmove events on the body @@ -1282,18 +1338,20 @@ notPassive = { // Keep x values up to date while dragging onPointerMove: function (pointerEvent) { var pointer; + if (!this.dragging) { // Mark the carousel as dragging, which is used to disable clicks this.dragging = true; - } - // Calculated how much drag has happened since the list move + } // Calculated how much drag has happened since the list move + + pointer = this.getPointerCoords(pointerEvent); this.dragVelocity = pointer.x - this.lastPointer.x; this.targetX += this.dragVelocity; - this.lastPointer = pointer; - // Caculate the drag direction ratio - this.dragDirectionRatio = Math.abs((pointer.x - this.startPointer.x) / (pointer.y - this.startPointer.y)); - // Update the track position + this.lastPointer = pointer; // Caculate the drag direction ratio + + this.dragDirectionRatio = Math.abs((pointer.x - this.startPointer.x) / (pointer.y - this.startPointer.y)); // Update the track position + return this.currentX = this.applyBoundaryDampening(this.targetX); }, // Helper to get the x position of either a touch or mouse event @@ -1310,10 +1368,13 @@ notPassive = { case !this.shouldLoop: return x; // Don't apply dampening + case !(x > 0): return Math.pow(x, this.boundaryDampening); + case !(x < this.endX): return this.endX - Math.pow(this.endX - x, this.boundaryDampening); + default: return this.applyXBoundaries(x); } @@ -1333,6 +1394,7 @@ notPassive = { if (this.contentDragPrevented) { return; } + this.$refs.track.$el.querySelectorAll('a, img').forEach(function (el) { return el.addEventListener('dragstart', function (e) { return e.preventDefault(); @@ -1357,26 +1419,30 @@ Code related to implementing feathering effect. methods: { // Add feathering styles via breakpoint makeBreakpointFeatheringStyle: function (breakpoint) { - var cssValue, feather; - // Disable feathering if not enough slides + var cssValue, feather; // Disable feathering if not enough slides + if (this.isDisabledAtBreakpoint(breakpoint)) { return; - } - // Get feathering amount + } // Get feathering amount + + feather = this.getResponsiveValue('feather', breakpoint); + if (feather === false || feather === null) { return; } + if (!(feather && typeof feather !== 'boolean')) { feather = 20; } - feather = this.autoUnit(feather); - // Make the rule value + + feather = this.autoUnit(feather); // Make the rule value + cssValue = `linear-gradient(to right, transparent, black ${feather}, black calc(100% - ${feather}), - transparent)`; - // Write the style, with browser prefixes + transparent)`; // Write the style, with browser prefixes + return `${this.scopeSelector} .ssr-carousel-mask { -webkit-mask-image: ${cssValue}; mask-image: ${cssValue}; @@ -1421,6 +1487,7 @@ Code related to focus and hover state if (!this.watchesHover) { return; } + return document.addEventListener('visibilitychange', this.updateVisibility); }, beforeDestroy: function () { @@ -1443,11 +1510,11 @@ Code related to the gutters between slides // Apply gutters between slides via margins makeBreakpointSlideGutterStyle: function (breakpoint) { var gutter, lastChildGutter; - gutter = this.getResponsiveValue('gutter', breakpoint); - // If carousel would be disabled for not having enough slides, then remove + gutter = this.getResponsiveValue('gutter', breakpoint); // If carousel would be disabled for not having enough slides, then remove // gutter from last slide. - lastChildGutter = this.isDisabledAtBreakpoint(breakpoint) ? 0 : gutter; - // Render styles + + lastChildGutter = this.isDisabledAtBreakpoint(breakpoint) ? 0 : gutter; // Render styles + return `${this.scopeSelector} .ssr-carousel-slide { margin-right: ${this.autoUnit(gutter)}; } @@ -1489,13 +1556,17 @@ Code related to looping / infinite scroll // track transform so that the slides don't feel like they were re-ordered. trackLoopOffset: function () { var offsetSlideCount; + if (!this.shouldLoop) { return 0; } + offsetSlideCount = this.currentSlideIndex; + if (this.hasLeftPeekClone) { offsetSlideCount -= 1; } + return offsetSlideCount * this.slideWidth; }, // Get slideIndex of the right most and left most slides indexes @@ -1529,42 +1600,50 @@ Code related to looping / infinite scroll // Calculating via watcher to prevent unnecesary recalculations (I noticed a // bunch of calls when this was done via a computed property) setSlideOrder: function () { - var count, indices, split; - // Make an array as long as the slides count with incrementing values + var count, indices, split; // Make an array as long as the slides count with incrementing values + indices = [...Array(this.slidesCount).keys()]; - count = indices.length; - // Shift the order to applying centering effect + count = indices.length; // Shift the order to applying centering effect + if (this.center) { split = Math.floor(this.currentSlidesPerPage / 2); indices = [...indices.slice(split), ...indices.slice(0, split)]; - } - // Re-order while looping + } // Re-order while looping + + if (this.shouldLoop) { split = (count - this.currentSlideIndex) % count; indices = [...indices.slice(split), ...indices.slice(0, split)]; - } - // Set the new index order + } // Set the new index order + + return this.slideOrder = indices; }, // Reorder the initial slide state using CSS because the order is dependent // on the slides per page which isn't known via JS until hydrating makeBreakpointSlideOrderStyle: function (breakpoint) { var i, rules, slidesPerPage, split; + if (!this.center) { return; } + slidesPerPage = this.getResponsiveValue('slidesPerPage', breakpoint); split = Math.floor(slidesPerPage / 2); + rules = function () { var j, ref, results; results = []; + for (i = j = 0, ref = this.slidesCount; 0 <= ref ? j <= ref : j >= ref; i = 0 <= ref ? ++j : --j) { results.push(`${this.scopeSelector} .ssr-carousel-slide:nth-child(${i + 1}) { order: ${(i + split) % this.slidesCount}; }`); } + return results; }.call(this); + return rules.join(''); } } @@ -1590,9 +1669,9 @@ Code related to dealing with advancing between pages currentX: 0, // The actual left offset of the slides container targetX: 0 // Where we may be tweening the slide to + }; }, - computed: { // The current number of pages pages: function () { @@ -1601,8 +1680,10 @@ Code related to dealing with advancing between pages case !(this.paginateBySlide && this.shouldLoop): return this.slidesCount; // Else, restrict pages so you the last slide is flush with right edge + case !this.paginateBySlide: return this.slidesCount - this.currentSlidesPerPage + 1; + default: // When not paginating by slide, the amount of pages is related to the // current number of slides shown per page. @@ -1632,6 +1713,7 @@ Code related to dealing with advancing between pages boundedIndex: function () { var boundedIndex; boundedIndex = this.index % this.pages; + if (boundedIndex < 0) { return this.pages + boundedIndex; } else { @@ -1646,37 +1728,44 @@ Code related to dealing with advancing between pages // viewport. Aka, the count will be equal the currentSlidesPerPage per page. activeSlides: function () { var ref, ref1, start; + if (this.isVariableWidth) { return function () { var results = []; + for (var i = 0, ref = this.slidesCount; 0 <= ref ? i < ref : i > ref; 0 <= ref ? i++ : i--) { results.push(i); } + return results; }.apply(this); - } - // Get the offset of the leftmost slide in the current viewport - start = this.paginateBySlide ? this.boundedIndex : this.boundedIndex * this.currentSlidesPerPage; - // Adjust the start if not looping and on the last page of slides and there + } // Get the offset of the leftmost slide in the current viewport + + + start = this.paginateBySlide ? this.boundedIndex : this.boundedIndex * this.currentSlidesPerPage; // Adjust the start if not looping and on the last page of slides and there // aren't enough slides to make a full page + if (!this.shouldLoop) { start -= this.boundedIndex % this.currentSlidesPerPage; } + return function () { var results = []; + for (var i = start, ref1 = start + this.currentSlidesPerPage; start <= ref1 ? i < ref1 : i > ref1; start <= ref1 ? i++ : i--) { results.push(i); } + return results; }.apply(this).reduce((slides, offset) => { // When looping, use modulo to loop back around if (this.shouldLoop) { - slides.push(offset % this.slidesCount); - // Else, cap the offset to the last slide + slides.push(offset % this.slidesCount); // Else, cap the offset to the last slide } else if (offset < this.slidesCount) { slides.push(offset); - } - // Return updated slides + } // Return updated slides + + return slides; }, []); } @@ -1687,8 +1776,7 @@ Code related to dealing with advancing between pages // If the value exceeds the bounds, immediately emit a new input event // with the corrected value if (this.value !== this.applyIndexBoundaries(this.value)) { - return this.$emit('input', this.boundedIndex); - // Else if the incoming value is different than the current value + return this.$emit('input', this.boundedIndex); // Else if the incoming value is different than the current value // then tween to it } else if (this.value !== this.boundedIndex) { return this.goto(this.value); @@ -1702,7 +1790,6 @@ Code related to dealing with advancing between pages return this.$emit('input', this.boundedIndex); // For v-model } }, - methods: { // Advance methods next: function () { @@ -1749,10 +1836,10 @@ Code related to dealing with advancing between pages }, // Calculate the X value given an index getXForIndex: function (index) { - var x; - // Figure out the new x position - x = this.paginateBySlide ? index * this.slideWidth * -1 : index * this.pageWidth * -1; - // Apply adjustments to x value and persist + var x; // Figure out the new x position + + x = this.paginateBySlide ? index * this.slideWidth * -1 : index * this.pageWidth * -1; // Apply adjustments to x value and persist + x += this.makeIncompletePageOffset(index); return Math.round(this.applyXBoundaries(x)); }, @@ -1763,9 +1850,11 @@ Code related to dealing with advancing between pages // left by one slideWidth in this case. makeIncompletePageOffset: function (index) { var incompleteWidth; + if (!(this.shouldLoop && !this.paginateBySlide)) { return 0; } + incompleteWidth = this.pageWidth - this.lastPageWidth; return Math.floor(index / this.pages) * incompleteWidth; }, @@ -1891,13 +1980,13 @@ gutter space. return this.capturePeekingMeasurements(); // Easier to respond to all } }, - methods: { // Capture measurements of peeking values capturePeekingMeasurements: function () { if (!this.$refs.peekValues) { return; } + return this.$nextTick(function () { // Wait for getResponsiveValue on @peekStyles var styles; @@ -1912,14 +2001,16 @@ gutter space. // hydrates. This gets overridden by the track's inline translateX style. makeBreakpointTrackTransformStyle: function (breakpoint) { var gutter, peekLeft, rule; + if (this.isDisabledAtBreakpoint(breakpoint)) { return; } - peekLeft = this.getResponsiveValue('peekLeft', breakpoint); - // If no peeking slide, just add the offset + + peekLeft = this.getResponsiveValue('peekLeft', breakpoint); // If no peeking slide, just add the offset + rule = !this.hasLeftPeekClone ? `transform: translateX(${// Otherwise, offset by one slide width (including it's gutter) - this.autoUnit(peekLeft)});` : (gutter = this.getResponsiveValue('gutter', breakpoint), `transform: translateX(calc( ${this.autoUnit(peekLeft)} - (${this.makeSlideWidthCalc(breakpoint)} + ${this.autoUnit(gutter)}) ));`); - // Wrap rule in selector + this.autoUnit(peekLeft)});` : (gutter = this.getResponsiveValue('gutter', breakpoint), `transform: translateX(calc( ${this.autoUnit(peekLeft)} - (${this.makeSlideWidthCalc(breakpoint)} + ${this.autoUnit(gutter)}) ));`); // Wrap rule in selector + return `${this.scopeSelector} .ssr-carousel-track { ${rule} }`; } } @@ -1953,8 +2044,7 @@ Code related to changing the slides per page at different viewport widths // Massage media queries into the responsive prop responsiveRules: function () { return this.responsive.map(breakpoint => { - return { - ...breakpoint, + return { ...breakpoint, mediaQuery: this.makeMediaQuery(breakpoint), active: this.isBreakpointActive(breakpoint), // Unpack shorthands @@ -1971,6 +2061,7 @@ Code related to changing the slides per page at different viewport widths // responsiveRules to return the last matching rule. currentResponsiveBreakpoint: function () { var match; + if (match = [...this.responsiveRules].reverse().find(function ({ active }) { @@ -2018,12 +2109,15 @@ Code related to changing the slides per page at different viewport widths makeMediaQuery: function (breakpoint) { var rules; rules = []; + if (breakpoint.maxWidth) { rules.push(`(max-width: ${breakpoint.maxWidth}px)`); } + if (breakpoint.minWidth) { rules.push(`(min-width: ${breakpoint.minWidth}px)`); } + return rules.join(' and '); }, // Make the block of styles for a breakpoint @@ -2034,8 +2128,8 @@ Code related to changing the slides per page at different viewport widths // for the slidesPerPage makeBreakpointDisablingRules: function (breakpoint) { var slidesPerPage; - slidesPerPage = this.getResponsiveValue('slidesPerPage', breakpoint); - // Disabled, center slides and hide carousel UI + slidesPerPage = this.getResponsiveValue('slidesPerPage', breakpoint); // Disabled, center slides and hide carousel UI + if (this.slidesCount <= slidesPerPage) { return `${this.scopeSelector} .ssr-carousel-track { justify-content: center; } ${this.scopeSelector} .ssr-carousel-arrows, @@ -2057,13 +2151,17 @@ ${this.scopeSelector} .ssr-carousel-dots { display: flex; }`; // so I can consume via a compued property isBreakpointActive: function (breakpoint) { var val; + switch (false) { case !!this.viewportWidth: return false; + case !((val = breakpoint.maxWidth) && this.viewportWidth > val): return false; + case !((val = breakpoint.minWidth) && this.viewportWidth < val): return false; + default: return true; } @@ -2071,39 +2169,46 @@ ${this.scopeSelector} .ssr-carousel-dots { display: flex; }`; // Find the first breakpoint with a property set getResponsiveValue: function (property, breakpoint) { var ruleMatch, val; + if ((val = breakpoint[property]) != null) { // If this breakpoint has a value, use it return val; } + if (!this.responsiveRules.length) { // If no responsive rules, use default return this[property]; - } - // Check responsive rules to see if any of them contain a value for the + } // Check responsive rules to see if any of them contain a value for the // property + + ruleMatch = this.responsiveRules.find(function (rule) { // Rule must contain this property if (!rule[property]) { return; } + if (breakpoint.maxWidth && rule.minWidth && rule.minWidth < breakpoint.maxWidth) { // Match if rule's min-width is less than the target max-width return true; } + if (breakpoint.maxWidth && rule.maxWidth && rule.maxWidth < breakpoint.maxWidth) { // Match if rule's max-width is less than the target max-width return true; } + if (breakpoint.minWidth && rule.minWidth && rule.minWidth > breakpoint.minWidth) { // Match if rule's min-width is greater than the target min-width return true; } + if (breakpoint.minWidth && rule.maxWidth && rule.minWidth > breakpoint.minWidth) { // Match if rule's max-width is greater than the target min-width return true; } - }); - // Return matching property or fallback to the main component prop + }); // Return matching property or fallback to the main component prop + if (ruleMatch) { return ruleMatch[property]; } else { @@ -2117,9 +2222,11 @@ ${this.scopeSelector} .ssr-carousel-dots { display: flex; }`; hash = 0; i = 0; len = str.length; + while (i < len) { hash = (hash << 5) - hash + str.charCodeAt(i++) << 0; } + return hash.toString(36); }, // Add px unit to a value if numeric @@ -2127,6 +2234,7 @@ ${this.scopeSelector} .ssr-carousel-dots { display: flex; }`; if (!val) { return '0px'; } + if (String(val).match(/^[\d\-\.]+$/)) { return `${val}px`; } else { @@ -2160,9 +2268,9 @@ Code related to tweening the position of the track targetX: 0, // Where we may be tweening the slide to tweening: false // If there is a current RAF based tween running + }; }, - // Stop any animations that are in flight beforeDestroy: function () { return window.cancelAnimationFrame(this.rafId); @@ -2195,9 +2303,11 @@ Code related to tweening the position of the track if (this.tweening) { return; } + if (this.currentX === this.targetX) { return; } + return this.tweening = true; }, // The watcher on this will kill active tweens @@ -2208,6 +2318,7 @@ Code related to tweening the position of the track tweenToTarget: function () { // Apply tween math this.currentX = this.currentX + (this.targetX - this.currentX) * this.tweenDampening; + if (Math.abs(this.targetX - this.currentX) < 1) { // Stops tweening this.currentX = this.targetX; @@ -2245,6 +2356,7 @@ Functionality related to supporting variable width slides if (!this.$refs.track) { return; } + return this.measuredTrackWidth = this.$refs.track.$el.scrollWidth; } } @@ -2271,8 +2383,7 @@ Functionality related to supporting variable width slides // Component definition name: 'SsrCarousel', // Load concerns - mixins: [accessibility_coffee, autoplay_coffee, dimensions_coffee, dragging_coffee, feathering_coffee, focus_coffee, gutters_coffee, looping_coffee, pagination_coffee, responsive_coffee, peeking_coffee, - // After `responsive` so prop can access `gutter` prop + mixins: [accessibility_coffee, autoplay_coffee, dimensions_coffee, dragging_coffee, feathering_coffee, focus_coffee, gutters_coffee, looping_coffee, pagination_coffee, responsive_coffee, peeking_coffee, // After `responsive` so prop can access `gutter` prop tweening_coffee, variable_width_coffee], components: { SsrCarouselArrows: ssr_carousel_arrows, @@ -2292,9 +2403,9 @@ Functionality related to supporting variable width slides if (!(this.carouselWidth && !this.disabled)) { return; } + return this.currentX + this.trackLoopOffset + this.peekLeftPx; // The value from tweening or dragging // Offset from re-ordering slides for looping // Offset slides for the left peek }, - // Determine whether to create hover event bindings watchesHover: function () { return this.autoplayDelay > 0; @@ -2304,8 +2415,8 @@ Functionality related to supporting variable width slides if (this.disabled) { return {}; } - return { - ...(this.noDrag ? {} : { + + return { ...(this.noDrag ? {} : { mousedown: this.onPointerDown, touchstart: this.onPointerDown }), @@ -2319,7 +2430,7 @@ Functionality related to supporting variable width slides }); // CONCATENATED MODULE: ./src/ssr-carousel.vue?vue&type=script&lang=coffee& /* harmony default export */ var src_ssr_carouselvue_type_script_lang_coffee_ = (ssr_carouselvue_type_script_lang_coffee_); -// CONCATENATED MODULE: ./node_modules/mini-css-extract-plugin/dist/loader.js!./node_modules/css-loader/dist/cjs.js!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/dist/cjs.js!./node_modules/stylus-loader!./node_modules/vue-loader/lib??vue-loader-options!./src/ssr-carousel.vue?vue&type=style&index=0&id=40da5be2&prod&lang=stylus& +// CONCATENATED MODULE: ./node_modules/mini-css-extract-plugin/dist/loader.js!./node_modules/css-loader/dist/cjs.js!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src!./node_modules/stylus-loader!./node_modules/vue-loader/lib??vue-loader-options!./src/ssr-carousel.vue?vue&type=style&index=0&id=40da5be2&prod&lang=stylus& // extracted by mini-css-extract-plugin // CONCATENATED MODULE: ./src/ssr-carousel.vue?vue&type=style&index=0&id=40da5be2&prod&lang=stylus& diff --git a/src/ssr-carousel-track.vue b/src/ssr-carousel-track.vue index 155599b..e39a1c5 100644 --- a/src/ssr-carousel-track.vue +++ b/src/ssr-carousel-track.vue @@ -12,6 +12,9 @@ export default leftPeekingSlideIndex: Number rightPeekingSlideIndex: Number + # Should the track element be an ul + data: -> renderAsList: false + # Set tabindex of inactive slides on mount mounted: -> @denyTabindex @inactiveSlides @@ -19,6 +22,9 @@ export default computed: + # The HTML element of the track + trackHTMLElement: -> if @renderAsList then 'ul' else 'div' + # Get the count of non-cloned slides uniqueSlidesCount: -> @slideOrder.length @@ -51,6 +57,9 @@ export default makeSlides: -> @getSlideComponents().map (vnode, index) => vnode = @makeReactiveVnode vnode + # Check if we are rendering a list of elements + @renderAsList = true if index == 0 and vnode.tag == 'li' + # This is a peeking clone if it's index is greater than the slide count slideCount = @uniqueSlidesCount isPeekingClone = index >= slideCount @@ -144,7 +153,7 @@ export default # Render the track and slotted slides render: (create) -> - create 'div', + create @trackHTMLElement, class: [ 'ssr-carousel-track', { @dragging } ] style: @styles , @makeSlides()