vue3-carousel
Advanced tools
Comparing version 0.1.48 to 0.2.0
@@ -74,3 +74,3 @@ import * as vue from 'vue'; | ||
}; | ||
}, () => vue.VNode<vue.RendererNode, vue.RendererElement, { | ||
}, () => VNode<vue.RendererNode, vue.RendererElement, { | ||
[key: string]: any; | ||
@@ -122,2 +122,16 @@ }>, unknown, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, vue.EmitsOptions, string, vue.VNodeProps & vue.AllowedComponentProps & vue.ComponentCustomProps, Readonly<{ | ||
interface Data { | ||
[key: string]: unknown; | ||
} | ||
declare const Icon: { | ||
(props: Data): vue.VNode<vue.RendererNode, vue.RendererElement, { | ||
[key: string]: any; | ||
}> | undefined; | ||
props: { | ||
name: StringConstructor; | ||
title: StringConstructor; | ||
}; | ||
}; | ||
declare const Navigation: (props: any, { slots, attrs }: any) => vue.VNode<vue.RendererNode, vue.RendererElement, { | ||
@@ -127,2 +141,6 @@ [key: string]: any; | ||
declare const Pagination: () => VNode<vue.RendererNode, vue.RendererElement, { | ||
[key: string]: any; | ||
}>; | ||
declare const _default: vue.DefineComponent<{ | ||
@@ -133,2 +151,6 @@ index: { | ||
}; | ||
isClone: { | ||
type: BooleanConstructor; | ||
default: boolean; | ||
}; | ||
}, () => vue.VNode<vue.RendererNode, vue.RendererElement, { | ||
@@ -138,26 +160,11 @@ [key: string]: any; | ||
index?: unknown; | ||
isClone?: unknown; | ||
} & { | ||
index: number; | ||
isClone: boolean; | ||
} & {}>, { | ||
index: number; | ||
isClone: boolean; | ||
}>; | ||
interface Data { | ||
[key: string]: unknown; | ||
} | ||
declare const Pagination: () => VNode<vue.RendererNode, vue.RendererElement, { | ||
[key: string]: any; | ||
}>; | ||
declare const Icon: { | ||
(props: Data): vue.VNode<vue.RendererNode, vue.RendererElement, { | ||
[key: string]: any; | ||
}> | undefined; | ||
props: { | ||
name: StringConstructor; | ||
title: StringConstructor; | ||
}; | ||
}; | ||
export { _default$1 as Carousel, Icon, Navigation, Pagination, _default as Slide }; |
/** | ||
* Vue 3 Carousel 0.1.48 | ||
* Vue 3 Carousel 0.2.0 | ||
* (c) 2022 | ||
* @license MIT | ||
*/ | ||
import { defineComponent, ref, reactive, provide, onMounted, nextTick, onUnmounted, computed, watch, watchEffect, h, withModifiers, inject } from 'vue'; | ||
import { defineComponent, inject, ref, h, reactive, provide, onMounted, nextTick, onUnmounted, computed, watch, watchEffect, cloneVNode, withModifiers } from 'vue'; | ||
@@ -155,3 +155,3 @@ const defaultConfigs = { | ||
case 'center-odd': | ||
return slidesCount - Math.ceil(config.itemsToShow / 2); | ||
return slidesCount - Math.ceil((config.itemsToShow - 0.5) / 2); | ||
case 'center-even': | ||
@@ -187,7 +187,4 @@ return slidesCount - Math.ceil(config.itemsToShow / 2); | ||
} | ||
function getSlidesToScroll({ slidesBuffer, currentSlide, snapAlign, itemsToShow, wrapAround, slidesCount, }) { | ||
let output = slidesBuffer.indexOf(currentSlide); | ||
if (output === -1) { | ||
output = slidesBuffer.indexOf(Math.ceil(currentSlide)); | ||
} | ||
function getSlidesToScroll({ currentSlide, snapAlign, itemsToShow, wrapAround, slidesCount, }) { | ||
let output = currentSlide; | ||
if (snapAlign === 'center' || snapAlign === 'center-odd') { | ||
@@ -209,3 +206,25 @@ output -= (itemsToShow - 1) / 2; | ||
} | ||
function mapNumberToRange(current, max, min = 0) { | ||
if (current > max) { | ||
return mapNumberToRange(current - (max + 1), max, min); | ||
} | ||
if (current < min) { | ||
return mapNumberToRange(current + (max + 1), max, min); | ||
} | ||
return current; | ||
} | ||
var ARIAComponent = defineComponent({ | ||
name: 'ARIA', | ||
setup() { | ||
const currentSlide = inject('currentSlide', ref(0)); | ||
const slidesCount = inject('slidesCount', ref(0)); | ||
return () => h('div', { | ||
class: ['carousel__liveregion', 'carousel__sr-only'], | ||
'aria-live': 'polite', | ||
'aria-atomic': 'true', | ||
}, `Item ${currentSlide.value + 1} of ${slidesCount.value}`); | ||
}, | ||
}); | ||
var Carousel = defineComponent({ | ||
@@ -218,7 +237,4 @@ name: 'Carousel', | ||
const slides = ref([]); | ||
const slidesBuffer = ref([]); | ||
const slideWidth = ref(0); | ||
const slidesCount = ref(1); | ||
let autoplayTimer; | ||
let transitionTimer; | ||
let breakpoints = ref({}); | ||
@@ -235,4 +251,5 @@ // generate carousel configs | ||
const minSlideIndex = ref(0); | ||
let autoplayTimer; | ||
let transitionTimer; | ||
provide('config', config); | ||
provide('slidesBuffer', slidesBuffer); | ||
provide('slidesCount', slidesCount); | ||
@@ -242,2 +259,3 @@ provide('currentSlide', currentSlideIndex); | ||
provide('minSlide', minSlideIndex); | ||
provide('slideWidth', slideWidth); | ||
/** | ||
@@ -271,6 +289,5 @@ * Configs | ||
function bindConfigs(newConfig) { | ||
for (let key in newConfig) { | ||
// @ts-ignore | ||
config[key] = newConfig[key]; | ||
} | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
//@ts-ignore | ||
Object.entries(newConfig).forEach(([key, val]) => (config[key] = val)); | ||
} | ||
@@ -302,30 +319,2 @@ const handleWindowResize = debounce(() => { | ||
} | ||
function updateSlidesBuffer() { | ||
const slidesArray = [...Array(slidesCount.value).keys()]; | ||
const shouldShiftSlides = config.wrapAround && config.itemsToShow + 1 <= slidesCount.value; | ||
if (shouldShiftSlides) { | ||
const buffer = config.itemsToShow !== 1 | ||
? Math.round((slidesCount.value - config.itemsToShow) / 2) | ||
: 0; | ||
let shifts = buffer - currentSlideIndex.value; | ||
if (config.snapAlign === 'end') { | ||
shifts += Math.floor(config.itemsToShow - 1); | ||
} | ||
else if (config.snapAlign === 'center' || config.snapAlign === 'center-odd') { | ||
shifts++; | ||
} | ||
// Check shifting directions | ||
if (shifts < 0) { | ||
for (let i = shifts; i < 0; i++) { | ||
slidesArray.push(Number(slidesArray.shift())); | ||
} | ||
} | ||
else { | ||
for (let i = 0; i < shifts; i++) { | ||
slidesArray.unshift(Number(slidesArray.pop())); | ||
} | ||
} | ||
} | ||
slidesBuffer.value = slidesArray; | ||
} | ||
onMounted(() => { | ||
@@ -387,3 +376,2 @@ if (breakpoints.value) { | ||
const draggedSlides = Math.round(dragged.x / slideWidth.value + tolerance) * direction; | ||
let newSlide = getCurrentSlideIndex(config, currentSlideIndex.value - draggedSlides, maxSlideIndex.value, minSlideIndex.value); | ||
// Prevent clicking if there is clicked slides | ||
@@ -397,3 +385,3 @@ if (draggedSlides && !isTouch) { | ||
} | ||
slideTo(newSlide); | ||
slideTo(currentSlideIndex.value - draggedSlides); | ||
dragged.x = 0; | ||
@@ -419,2 +407,5 @@ dragged.y = 0; | ||
function resetAutoplay() { | ||
if (!config.autoplay || config.autoplay <= 0) { | ||
return; | ||
} | ||
if (autoplayTimer) { | ||
@@ -430,24 +421,17 @@ clearInterval(autoplayTimer); | ||
const isSliding = ref(false); | ||
function slideTo(slideIndex, mute = false) { | ||
function slideTo(slideIndex) { | ||
if (currentSlideIndex.value === slideIndex || isSliding.value) { | ||
return; | ||
} | ||
isSliding.value = true; | ||
resetAutoplay(); | ||
// Wrap slide index | ||
const lastSlideIndex = slidesCount.value - 1; | ||
if (slideIndex > lastSlideIndex) { | ||
return slideTo(slideIndex - slidesCount.value); | ||
} | ||
if (slideIndex < 0) { | ||
return slideTo(slideIndex + slidesCount.value); | ||
} | ||
isSliding.value = true; | ||
const currentVal = getCurrentSlideIndex(config, slideIndex, maxSlideIndex.value, minSlideIndex.value); | ||
prevSlideIndex.value = currentSlideIndex.value; | ||
currentSlideIndex.value = slideIndex; | ||
if (!mute) { | ||
emit('update:modelValue', currentSlideIndex.value); | ||
} | ||
currentSlideIndex.value = currentVal; | ||
transitionTimer = setTimeout(() => { | ||
if (config.wrapAround) | ||
updateSlidesBuffer(); | ||
const mappedNumber = mapNumberToRange(currentVal, maxSlideIndex.value); | ||
if (config.wrapAround) { | ||
currentSlideIndex.value = mappedNumber; | ||
} | ||
emit('update:modelValue', mappedNumber); | ||
isSliding.value = false; | ||
@@ -457,17 +441,10 @@ }, config.transition); | ||
function next() { | ||
let nextSlide = currentSlideIndex.value + config.itemsToScroll; | ||
if (!config.wrapAround) { | ||
nextSlide = Math.min(nextSlide, maxSlideIndex.value); | ||
} | ||
slideTo(nextSlide); | ||
slideTo(currentSlideIndex.value + config.itemsToScroll); | ||
} | ||
function prev() { | ||
let prevSlide = currentSlideIndex.value - config.itemsToScroll; | ||
if (!config.wrapAround) { | ||
prevSlide = Math.max(prevSlide, minSlideIndex.value); | ||
} | ||
slideTo(prevSlide); | ||
slideTo(currentSlideIndex.value - config.itemsToScroll); | ||
} | ||
const nav = { slideTo, next, prev }; | ||
provide('nav', nav); | ||
provide('isSliding', isSliding); | ||
/** | ||
@@ -477,3 +454,2 @@ * Track style | ||
const slidesToScroll = computed(() => getSlidesToScroll({ | ||
slidesBuffer: slidesBuffer.value, | ||
itemsToShow: config.itemsToShow, | ||
@@ -492,2 +468,4 @@ snapAlign: config.snapAlign, | ||
transition: `${isSliding.value ? config.transition : 0}ms`, | ||
margin: config.wrapAround ? `0 -${slidesCount.value * slideWidth.value}px` : '', | ||
width: `100%`, | ||
}; | ||
@@ -502,3 +480,2 @@ }); | ||
updateSlidesData(); | ||
updateSlidesBuffer(); | ||
updateSlideWidth(); | ||
@@ -509,3 +486,2 @@ resetAutoplay(); | ||
updateSlidesData(); | ||
updateSlidesBuffer(); | ||
} | ||
@@ -518,2 +494,7 @@ // Update the carousel on props change | ||
}); | ||
watch(() => props['modelValue'], (val) => { | ||
if (val !== currentSlideIndex.value) { | ||
slideTo(Number(val)); | ||
} | ||
}); | ||
// Init carousel | ||
@@ -523,8 +504,3 @@ initCarousel(); | ||
// Handel when slides added/removed | ||
const needToUpdate = slidesCount.value !== slides.value.length; | ||
const currentSlideUpdated = props.modelValue !== undefined && currentSlideIndex.value !== props.modelValue; | ||
if (currentSlideUpdated) { | ||
slideTo(Number(props.modelValue), true); | ||
} | ||
if (needToUpdate) { | ||
if (slidesCount.value !== slides.value.length) { | ||
updateCarousel(); | ||
@@ -535,3 +511,2 @@ } | ||
config, | ||
slidesBuffer, | ||
slidesCount, | ||
@@ -548,3 +523,2 @@ slideWidth, | ||
updateSlideWidth, | ||
updateSlidesBuffer, | ||
initCarousel, | ||
@@ -565,5 +539,10 @@ restartCarousel, | ||
const addonsElements = (slotAddons === null || slotAddons === void 0 ? void 0 : slotAddons(slotsProps)) || []; | ||
slidesElements.forEach((el, index) => (el.props.index = index)); | ||
let output = slidesElements; | ||
if (config.wrapAround) { | ||
const slidesBefore = slidesElements.map((el, index) => cloneVNode(el, { index: -slidesElements.length + index, isClone: true })); | ||
const slidesAfter = slidesElements.map((el, index) => cloneVNode(el, { index: slidesElements.length + index, isClone: true })); | ||
output = [...slidesBefore, ...slidesElements, ...slidesAfter]; | ||
} | ||
slides.value = slidesElements; | ||
// Bind slide order | ||
slidesElements.forEach((el, index) => (el.props.index = index)); | ||
const trackEl = h('ol', { | ||
@@ -578,3 +557,3 @@ class: 'carousel__track', | ||
: null, | ||
}, slidesElements); | ||
}, output); | ||
const viewPortEl = h('div', { class: 'carousel__viewport' }, trackEl); | ||
@@ -589,5 +568,6 @@ return h('section', { | ||
'aria-label': 'Gallery', | ||
tabindex: '0', | ||
onMouseenter: handleMouseEnter, | ||
onMouseleave: handleMouseLeave, | ||
}, [viewPortEl, addonsElements]); | ||
}, [viewPortEl, addonsElements, h(ARIAComponent)]); | ||
}; | ||
@@ -636,3 +616,3 @@ }, | ||
currentSlide.value <= minSlide.value && | ||
'carousel__prev--in-active', | ||
'carousel__prev--disabled', | ||
attrs === null || attrs === void 0 ? void 0 : attrs.class, | ||
@@ -649,3 +629,3 @@ ], | ||
currentSlide.value >= maxSlide.value && | ||
'carousel__next--in-active', | ||
'carousel__next--disabled', | ||
attrs === null || attrs === void 0 ? void 0 : attrs.class, | ||
@@ -659,2 +639,32 @@ ], | ||
const Pagination = () => { | ||
const maxSlide = inject('maxSlide', ref(1)); | ||
const minSlide = inject('minSlide', ref(1)); | ||
const currentSlide = inject('currentSlide', ref(1)); | ||
const slidesCount = inject('slidesCount', ref(1)); | ||
const nav = inject('nav', {}); | ||
function handleButtonClick(slideNumber) { | ||
nav.slideTo(slideNumber); | ||
} | ||
const isActive = (slide) => { | ||
const val = mapNumberToRange(currentSlide.value, slidesCount.value - 1, 0); | ||
return val === slide; | ||
}; | ||
const children = []; | ||
for (let slide = minSlide.value; slide < maxSlide.value + 1; slide++) { | ||
const button = h('button', { | ||
type: 'button', | ||
class: { | ||
'carousel__pagination-button': true, | ||
'carousel__pagination-button--active': isActive(slide), | ||
}, | ||
'aria-label': `Navigate to slide ${slide + 1}`, | ||
onClick: () => handleButtonClick(slide), | ||
}); | ||
const item = h('li', { class: 'carousel__pagination-item', key: slide }, button); | ||
children.push(item); | ||
} | ||
return h('ol', { class: 'carousel__pagination' }, children); | ||
}; | ||
var Slide = defineComponent({ | ||
@@ -667,22 +677,16 @@ name: 'CarouselSlide', | ||
}, | ||
isClone: { | ||
type: Boolean, | ||
default: false, | ||
}, | ||
}, | ||
setup(props, { slots }) { | ||
const config = inject('config', reactive(Object.assign({}, defaultConfigs))); | ||
const slidesBuffer = inject('slidesBuffer', ref([])); | ||
const currentSlide = inject('currentSlide', ref(0)); | ||
const slidesToScroll = inject('slidesToScroll', ref(0)); | ||
const wrapOrder = ref(props.index); | ||
if (config.wrapAround) { | ||
updateOrder(); | ||
watch(slidesBuffer, updateOrder); | ||
} | ||
function updateOrder() { | ||
wrapOrder.value = slidesBuffer.value.indexOf(props.index); | ||
} | ||
const slideWidth = inject('slideWidth', ref(0)); | ||
const isSliding = inject('isSliding', ref(false)); | ||
const slideStyle = computed(() => { | ||
const items = config.itemsToShow; | ||
const width = `${(1 / items) * 100}%`; | ||
return { | ||
width, | ||
order: wrapOrder.value.toString(), | ||
width: slideWidth.value ? `${slideWidth.value}px` : `100%`, | ||
}; | ||
@@ -692,10 +696,8 @@ }); | ||
const isVisible = () => { | ||
const min = Math.ceil(slidesToScroll.value); | ||
const max = Math.floor(slidesToScroll.value + config.itemsToShow); | ||
const current = slidesBuffer.value.slice(min, max); | ||
return current.includes(props.index); | ||
const min = Math.floor(slidesToScroll.value); | ||
const max = Math.ceil(slidesToScroll.value + config.itemsToShow - 1); | ||
return props.index >= min && props.index <= max; | ||
}; | ||
const isPrev = () => props.index === slidesBuffer.value[Math.ceil(slidesToScroll.value) - 1]; | ||
const isNext = () => props.index === | ||
slidesBuffer.value[Math.floor(slidesToScroll.value + config.itemsToShow)]; | ||
const isPrev = () => props.index === currentSlide.value - 1; | ||
const isNext = () => props.index === currentSlide.value + 1; | ||
return () => { | ||
@@ -707,7 +709,10 @@ var _a; | ||
carousel__slide: true, | ||
'carousel_slide--clone': props.isClone, | ||
'carousel__slide--visible': isVisible(), | ||
'carousel__slide--active': isActive(), | ||
'carousel__slide--visible': isVisible(), | ||
'carousel__slide--prev': isPrev(), | ||
'carousel__slide--next': isNext(), | ||
'carousel__slide--sliding': isSliding.value, | ||
}, | ||
'aria-hidden': !isVisible(), | ||
}, (_a = slots.default) === null || _a === void 0 ? void 0 : _a.call(slots)); | ||
@@ -718,33 +723,2 @@ }; | ||
const Pagination = () => { | ||
const maxSlide = inject('maxSlide', ref(1)); | ||
const minSlide = inject('minSlide', ref(1)); | ||
const currentSlide = inject('currentSlide', ref(1)); | ||
const nav = inject('nav', {}); | ||
function handleButtonClick(slideNumber) { | ||
nav.slideTo(slideNumber); | ||
} | ||
const isActive = (slide) => { | ||
const val = currentSlide.value; | ||
return (val === slide || | ||
(val > maxSlide.value && slide >= maxSlide.value) || | ||
(val < minSlide.value && slide <= minSlide.value)); | ||
}; | ||
const children = []; | ||
for (let slide = minSlide.value; slide < maxSlide.value + 1; slide++) { | ||
const button = h('button', { | ||
type: 'button', | ||
class: { | ||
'carousel__pagination-button': true, | ||
'carousel__pagination-button--active': isActive(slide), | ||
}, | ||
'aria-label': `Navigate to slide ${slide + 1}`, | ||
onClick: () => handleButtonClick(slide), | ||
}); | ||
const item = h('li', { class: 'carousel__pagination-item', key: slide }, button); | ||
children.push(item); | ||
} | ||
return h('ol', { class: 'carousel__pagination' }, children); | ||
}; | ||
export { Carousel, Icon, Navigation, Pagination, Slide }; |
/** | ||
* Vue 3 Carousel 0.1.48 | ||
* Vue 3 Carousel 0.2.0 | ||
* (c) 2022 | ||
@@ -159,3 +159,3 @@ * @license MIT | ||
case 'center-odd': | ||
return slidesCount - Math.ceil(config.itemsToShow / 2); | ||
return slidesCount - Math.ceil((config.itemsToShow - 0.5) / 2); | ||
case 'center-even': | ||
@@ -191,7 +191,4 @@ return slidesCount - Math.ceil(config.itemsToShow / 2); | ||
} | ||
function getSlidesToScroll({ slidesBuffer, currentSlide, snapAlign, itemsToShow, wrapAround, slidesCount, }) { | ||
let output = slidesBuffer.indexOf(currentSlide); | ||
if (output === -1) { | ||
output = slidesBuffer.indexOf(Math.ceil(currentSlide)); | ||
} | ||
function getSlidesToScroll({ currentSlide, snapAlign, itemsToShow, wrapAround, slidesCount, }) { | ||
let output = currentSlide; | ||
if (snapAlign === 'center' || snapAlign === 'center-odd') { | ||
@@ -213,3 +210,25 @@ output -= (itemsToShow - 1) / 2; | ||
} | ||
function mapNumberToRange(current, max, min = 0) { | ||
if (current > max) { | ||
return mapNumberToRange(current - (max + 1), max, min); | ||
} | ||
if (current < min) { | ||
return mapNumberToRange(current + (max + 1), max, min); | ||
} | ||
return current; | ||
} | ||
var ARIAComponent = vue.defineComponent({ | ||
name: 'ARIA', | ||
setup() { | ||
const currentSlide = vue.inject('currentSlide', vue.ref(0)); | ||
const slidesCount = vue.inject('slidesCount', vue.ref(0)); | ||
return () => vue.h('div', { | ||
class: ['carousel__liveregion', 'carousel__sr-only'], | ||
'aria-live': 'polite', | ||
'aria-atomic': 'true', | ||
}, `Item ${currentSlide.value + 1} of ${slidesCount.value}`); | ||
}, | ||
}); | ||
var Carousel = vue.defineComponent({ | ||
@@ -222,7 +241,4 @@ name: 'Carousel', | ||
const slides = vue.ref([]); | ||
const slidesBuffer = vue.ref([]); | ||
const slideWidth = vue.ref(0); | ||
const slidesCount = vue.ref(1); | ||
let autoplayTimer; | ||
let transitionTimer; | ||
let breakpoints = vue.ref({}); | ||
@@ -239,4 +255,5 @@ // generate carousel configs | ||
const minSlideIndex = vue.ref(0); | ||
let autoplayTimer; | ||
let transitionTimer; | ||
vue.provide('config', config); | ||
vue.provide('slidesBuffer', slidesBuffer); | ||
vue.provide('slidesCount', slidesCount); | ||
@@ -246,2 +263,3 @@ vue.provide('currentSlide', currentSlideIndex); | ||
vue.provide('minSlide', minSlideIndex); | ||
vue.provide('slideWidth', slideWidth); | ||
/** | ||
@@ -275,6 +293,5 @@ * Configs | ||
function bindConfigs(newConfig) { | ||
for (let key in newConfig) { | ||
// @ts-ignore | ||
config[key] = newConfig[key]; | ||
} | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
//@ts-ignore | ||
Object.entries(newConfig).forEach(([key, val]) => (config[key] = val)); | ||
} | ||
@@ -306,30 +323,2 @@ const handleWindowResize = debounce(() => { | ||
} | ||
function updateSlidesBuffer() { | ||
const slidesArray = [...Array(slidesCount.value).keys()]; | ||
const shouldShiftSlides = config.wrapAround && config.itemsToShow + 1 <= slidesCount.value; | ||
if (shouldShiftSlides) { | ||
const buffer = config.itemsToShow !== 1 | ||
? Math.round((slidesCount.value - config.itemsToShow) / 2) | ||
: 0; | ||
let shifts = buffer - currentSlideIndex.value; | ||
if (config.snapAlign === 'end') { | ||
shifts += Math.floor(config.itemsToShow - 1); | ||
} | ||
else if (config.snapAlign === 'center' || config.snapAlign === 'center-odd') { | ||
shifts++; | ||
} | ||
// Check shifting directions | ||
if (shifts < 0) { | ||
for (let i = shifts; i < 0; i++) { | ||
slidesArray.push(Number(slidesArray.shift())); | ||
} | ||
} | ||
else { | ||
for (let i = 0; i < shifts; i++) { | ||
slidesArray.unshift(Number(slidesArray.pop())); | ||
} | ||
} | ||
} | ||
slidesBuffer.value = slidesArray; | ||
} | ||
vue.onMounted(() => { | ||
@@ -391,3 +380,2 @@ if (breakpoints.value) { | ||
const draggedSlides = Math.round(dragged.x / slideWidth.value + tolerance) * direction; | ||
let newSlide = getCurrentSlideIndex(config, currentSlideIndex.value - draggedSlides, maxSlideIndex.value, minSlideIndex.value); | ||
// Prevent clicking if there is clicked slides | ||
@@ -401,3 +389,3 @@ if (draggedSlides && !isTouch) { | ||
} | ||
slideTo(newSlide); | ||
slideTo(currentSlideIndex.value - draggedSlides); | ||
dragged.x = 0; | ||
@@ -423,2 +411,5 @@ dragged.y = 0; | ||
function resetAutoplay() { | ||
if (!config.autoplay || config.autoplay <= 0) { | ||
return; | ||
} | ||
if (autoplayTimer) { | ||
@@ -434,24 +425,17 @@ clearInterval(autoplayTimer); | ||
const isSliding = vue.ref(false); | ||
function slideTo(slideIndex, mute = false) { | ||
function slideTo(slideIndex) { | ||
if (currentSlideIndex.value === slideIndex || isSliding.value) { | ||
return; | ||
} | ||
isSliding.value = true; | ||
resetAutoplay(); | ||
// Wrap slide index | ||
const lastSlideIndex = slidesCount.value - 1; | ||
if (slideIndex > lastSlideIndex) { | ||
return slideTo(slideIndex - slidesCount.value); | ||
} | ||
if (slideIndex < 0) { | ||
return slideTo(slideIndex + slidesCount.value); | ||
} | ||
isSliding.value = true; | ||
const currentVal = getCurrentSlideIndex(config, slideIndex, maxSlideIndex.value, minSlideIndex.value); | ||
prevSlideIndex.value = currentSlideIndex.value; | ||
currentSlideIndex.value = slideIndex; | ||
if (!mute) { | ||
emit('update:modelValue', currentSlideIndex.value); | ||
} | ||
currentSlideIndex.value = currentVal; | ||
transitionTimer = setTimeout(() => { | ||
if (config.wrapAround) | ||
updateSlidesBuffer(); | ||
const mappedNumber = mapNumberToRange(currentVal, maxSlideIndex.value); | ||
if (config.wrapAround) { | ||
currentSlideIndex.value = mappedNumber; | ||
} | ||
emit('update:modelValue', mappedNumber); | ||
isSliding.value = false; | ||
@@ -461,17 +445,10 @@ }, config.transition); | ||
function next() { | ||
let nextSlide = currentSlideIndex.value + config.itemsToScroll; | ||
if (!config.wrapAround) { | ||
nextSlide = Math.min(nextSlide, maxSlideIndex.value); | ||
} | ||
slideTo(nextSlide); | ||
slideTo(currentSlideIndex.value + config.itemsToScroll); | ||
} | ||
function prev() { | ||
let prevSlide = currentSlideIndex.value - config.itemsToScroll; | ||
if (!config.wrapAround) { | ||
prevSlide = Math.max(prevSlide, minSlideIndex.value); | ||
} | ||
slideTo(prevSlide); | ||
slideTo(currentSlideIndex.value - config.itemsToScroll); | ||
} | ||
const nav = { slideTo, next, prev }; | ||
vue.provide('nav', nav); | ||
vue.provide('isSliding', isSliding); | ||
/** | ||
@@ -481,3 +458,2 @@ * Track style | ||
const slidesToScroll = vue.computed(() => getSlidesToScroll({ | ||
slidesBuffer: slidesBuffer.value, | ||
itemsToShow: config.itemsToShow, | ||
@@ -496,2 +472,4 @@ snapAlign: config.snapAlign, | ||
transition: `${isSliding.value ? config.transition : 0}ms`, | ||
margin: config.wrapAround ? `0 -${slidesCount.value * slideWidth.value}px` : '', | ||
width: `100%`, | ||
}; | ||
@@ -506,3 +484,2 @@ }); | ||
updateSlidesData(); | ||
updateSlidesBuffer(); | ||
updateSlideWidth(); | ||
@@ -513,3 +490,2 @@ resetAutoplay(); | ||
updateSlidesData(); | ||
updateSlidesBuffer(); | ||
} | ||
@@ -522,2 +498,7 @@ // Update the carousel on props change | ||
}); | ||
vue.watch(() => props['modelValue'], (val) => { | ||
if (val !== currentSlideIndex.value) { | ||
slideTo(Number(val)); | ||
} | ||
}); | ||
// Init carousel | ||
@@ -527,8 +508,3 @@ initCarousel(); | ||
// Handel when slides added/removed | ||
const needToUpdate = slidesCount.value !== slides.value.length; | ||
const currentSlideUpdated = props.modelValue !== undefined && currentSlideIndex.value !== props.modelValue; | ||
if (currentSlideUpdated) { | ||
slideTo(Number(props.modelValue), true); | ||
} | ||
if (needToUpdate) { | ||
if (slidesCount.value !== slides.value.length) { | ||
updateCarousel(); | ||
@@ -539,3 +515,2 @@ } | ||
config, | ||
slidesBuffer, | ||
slidesCount, | ||
@@ -552,3 +527,2 @@ slideWidth, | ||
updateSlideWidth, | ||
updateSlidesBuffer, | ||
initCarousel, | ||
@@ -569,5 +543,10 @@ restartCarousel, | ||
const addonsElements = (slotAddons === null || slotAddons === void 0 ? void 0 : slotAddons(slotsProps)) || []; | ||
slidesElements.forEach((el, index) => (el.props.index = index)); | ||
let output = slidesElements; | ||
if (config.wrapAround) { | ||
const slidesBefore = slidesElements.map((el, index) => vue.cloneVNode(el, { index: -slidesElements.length + index, isClone: true })); | ||
const slidesAfter = slidesElements.map((el, index) => vue.cloneVNode(el, { index: slidesElements.length + index, isClone: true })); | ||
output = [...slidesBefore, ...slidesElements, ...slidesAfter]; | ||
} | ||
slides.value = slidesElements; | ||
// Bind slide order | ||
slidesElements.forEach((el, index) => (el.props.index = index)); | ||
const trackEl = vue.h('ol', { | ||
@@ -582,3 +561,3 @@ class: 'carousel__track', | ||
: null, | ||
}, slidesElements); | ||
}, output); | ||
const viewPortEl = vue.h('div', { class: 'carousel__viewport' }, trackEl); | ||
@@ -593,5 +572,6 @@ return vue.h('section', { | ||
'aria-label': 'Gallery', | ||
tabindex: '0', | ||
onMouseenter: handleMouseEnter, | ||
onMouseleave: handleMouseLeave, | ||
}, [viewPortEl, addonsElements]); | ||
}, [viewPortEl, addonsElements, vue.h(ARIAComponent)]); | ||
}; | ||
@@ -640,3 +620,3 @@ }, | ||
currentSlide.value <= minSlide.value && | ||
'carousel__prev--in-active', | ||
'carousel__prev--disabled', | ||
attrs === null || attrs === void 0 ? void 0 : attrs.class, | ||
@@ -653,3 +633,3 @@ ], | ||
currentSlide.value >= maxSlide.value && | ||
'carousel__next--in-active', | ||
'carousel__next--disabled', | ||
attrs === null || attrs === void 0 ? void 0 : attrs.class, | ||
@@ -663,2 +643,32 @@ ], | ||
const Pagination = () => { | ||
const maxSlide = vue.inject('maxSlide', vue.ref(1)); | ||
const minSlide = vue.inject('minSlide', vue.ref(1)); | ||
const currentSlide = vue.inject('currentSlide', vue.ref(1)); | ||
const slidesCount = vue.inject('slidesCount', vue.ref(1)); | ||
const nav = vue.inject('nav', {}); | ||
function handleButtonClick(slideNumber) { | ||
nav.slideTo(slideNumber); | ||
} | ||
const isActive = (slide) => { | ||
const val = mapNumberToRange(currentSlide.value, slidesCount.value - 1, 0); | ||
return val === slide; | ||
}; | ||
const children = []; | ||
for (let slide = minSlide.value; slide < maxSlide.value + 1; slide++) { | ||
const button = vue.h('button', { | ||
type: 'button', | ||
class: { | ||
'carousel__pagination-button': true, | ||
'carousel__pagination-button--active': isActive(slide), | ||
}, | ||
'aria-label': `Navigate to slide ${slide + 1}`, | ||
onClick: () => handleButtonClick(slide), | ||
}); | ||
const item = vue.h('li', { class: 'carousel__pagination-item', key: slide }, button); | ||
children.push(item); | ||
} | ||
return vue.h('ol', { class: 'carousel__pagination' }, children); | ||
}; | ||
var Slide = vue.defineComponent({ | ||
@@ -671,22 +681,16 @@ name: 'CarouselSlide', | ||
}, | ||
isClone: { | ||
type: Boolean, | ||
default: false, | ||
}, | ||
}, | ||
setup(props, { slots }) { | ||
const config = vue.inject('config', vue.reactive(Object.assign({}, defaultConfigs))); | ||
const slidesBuffer = vue.inject('slidesBuffer', vue.ref([])); | ||
const currentSlide = vue.inject('currentSlide', vue.ref(0)); | ||
const slidesToScroll = vue.inject('slidesToScroll', vue.ref(0)); | ||
const wrapOrder = vue.ref(props.index); | ||
if (config.wrapAround) { | ||
updateOrder(); | ||
vue.watch(slidesBuffer, updateOrder); | ||
} | ||
function updateOrder() { | ||
wrapOrder.value = slidesBuffer.value.indexOf(props.index); | ||
} | ||
const slideWidth = vue.inject('slideWidth', vue.ref(0)); | ||
const isSliding = vue.inject('isSliding', vue.ref(false)); | ||
const slideStyle = vue.computed(() => { | ||
const items = config.itemsToShow; | ||
const width = `${(1 / items) * 100}%`; | ||
return { | ||
width, | ||
order: wrapOrder.value.toString(), | ||
width: slideWidth.value ? `${slideWidth.value}px` : `100%`, | ||
}; | ||
@@ -696,10 +700,8 @@ }); | ||
const isVisible = () => { | ||
const min = Math.ceil(slidesToScroll.value); | ||
const max = Math.floor(slidesToScroll.value + config.itemsToShow); | ||
const current = slidesBuffer.value.slice(min, max); | ||
return current.includes(props.index); | ||
const min = Math.floor(slidesToScroll.value); | ||
const max = Math.ceil(slidesToScroll.value + config.itemsToShow - 1); | ||
return props.index >= min && props.index <= max; | ||
}; | ||
const isPrev = () => props.index === slidesBuffer.value[Math.ceil(slidesToScroll.value) - 1]; | ||
const isNext = () => props.index === | ||
slidesBuffer.value[Math.floor(slidesToScroll.value + config.itemsToShow)]; | ||
const isPrev = () => props.index === currentSlide.value - 1; | ||
const isNext = () => props.index === currentSlide.value + 1; | ||
return () => { | ||
@@ -711,7 +713,10 @@ var _a; | ||
carousel__slide: true, | ||
'carousel_slide--clone': props.isClone, | ||
'carousel__slide--visible': isVisible(), | ||
'carousel__slide--active': isActive(), | ||
'carousel__slide--visible': isVisible(), | ||
'carousel__slide--prev': isPrev(), | ||
'carousel__slide--next': isNext(), | ||
'carousel__slide--sliding': isSliding.value, | ||
}, | ||
'aria-hidden': !isVisible(), | ||
}, (_a = slots.default) === null || _a === void 0 ? void 0 : _a.call(slots)); | ||
@@ -722,33 +727,2 @@ }; | ||
const Pagination = () => { | ||
const maxSlide = vue.inject('maxSlide', vue.ref(1)); | ||
const minSlide = vue.inject('minSlide', vue.ref(1)); | ||
const currentSlide = vue.inject('currentSlide', vue.ref(1)); | ||
const nav = vue.inject('nav', {}); | ||
function handleButtonClick(slideNumber) { | ||
nav.slideTo(slideNumber); | ||
} | ||
const isActive = (slide) => { | ||
const val = currentSlide.value; | ||
return (val === slide || | ||
(val > maxSlide.value && slide >= maxSlide.value) || | ||
(val < minSlide.value && slide <= minSlide.value)); | ||
}; | ||
const children = []; | ||
for (let slide = minSlide.value; slide < maxSlide.value + 1; slide++) { | ||
const button = vue.h('button', { | ||
type: 'button', | ||
class: { | ||
'carousel__pagination-button': true, | ||
'carousel__pagination-button--active': isActive(slide), | ||
}, | ||
'aria-label': `Navigate to slide ${slide + 1}`, | ||
onClick: () => handleButtonClick(slide), | ||
}); | ||
const item = vue.h('li', { class: 'carousel__pagination-item', key: slide }, button); | ||
children.push(item); | ||
} | ||
return vue.h('ol', { class: 'carousel__pagination' }, children); | ||
}; | ||
exports.Carousel = Carousel; | ||
@@ -755,0 +729,0 @@ exports.Icon = Icon; |
{ | ||
"name": "vue3-carousel", | ||
"version": "0.1.48", | ||
"version": "0.2.0", | ||
"scripts": { | ||
@@ -12,2 +12,6 @@ "build": "rollup -c", | ||
"release": "sh scripts/new-release.sh", | ||
"lint": "eslint . --ext .ts", | ||
"lint:fix": "yarn lint --fix", | ||
"prettier": "prettier . --check", | ||
"prettier:fix": "yarn prettier --write", | ||
"test": "jest", | ||
@@ -26,6 +30,10 @@ "test:watch": "npm run test -- --watchAll" | ||
"@types/jest": "^26.0.24", | ||
"@typescript-eslint/parser": "^5.10.0", | ||
"@typescript-eslint/eslint-plugin": "^5.38.1", | ||
"@typescript-eslint/parser": "^5.38.1", | ||
"@vue/test-utils": "^2.0.0-rc.10", | ||
"eslint": "^7.32.0", | ||
"eslint-plugin-prettier": "^4.0.0", | ||
"eslint-config-prettier": "^8.5.0", | ||
"eslint-import-resolver-typescript": "^3.5.1", | ||
"eslint-plugin-import": "^2.26.0", | ||
"eslint-plugin-prettier": "^4.2.1", | ||
"eslint-plugin-vue": "^8.3.0", | ||
@@ -40,2 +48,3 @@ "jest": "^27.0.6", | ||
"rollup-plugin-typescript2": "^0.30.0", | ||
"stylus": "^0.59.0", | ||
"ts-jest": "^27.0.3", | ||
@@ -42,0 +51,0 @@ "typescript": "^4.5.4", |
@@ -14,3 +14,3 @@ # Vue 3 Carousel | ||
## TODO | ||
## Features | ||
@@ -23,5 +23,3 @@ - [x] Responsive breakpoints | ||
- [x] RTL | ||
- [ ] Vertical scroll | ||
- [ ] Sync multiple carousel | ||
- [ ] Enrich a11y | ||
- [x] Enrich a11y | ||
@@ -59,4 +57,4 @@ ## Getting started | ||
// If you are using PurgeCSS, make sure to whitelist the carousel CSS classes | ||
import 'vue3-carousel/dist/carousel.css'; | ||
import { Carousel, Slide, Pagination, Navigation } from 'vue3-carousel'; | ||
import 'vue3-carousel/dist/carousel.css' | ||
import { Carousel, Slide, Pagination, Navigation } from 'vue3-carousel' | ||
@@ -71,4 +69,4 @@ export default { | ||
}, | ||
}; | ||
} | ||
</script> | ||
``` | ||
``` |
@@ -1,5 +0,5 @@ | ||
import Carousel from '@/components/Carousel'; | ||
import Slide from '@/components/Slide'; | ||
import { defineComponent, h } from 'vue' | ||
import { defineComponent, h } from 'vue'; | ||
import Carousel from '@/components/Carousel' | ||
import Slide from '@/components/Slide' | ||
@@ -9,3 +9,3 @@ export default defineComponent({ | ||
setup() { | ||
const slides = [1, 2, 3, 4, 5]; | ||
const slides = [1, 2, 3, 4, 5] | ||
return () => | ||
@@ -17,7 +17,7 @@ h( | ||
slides() { | ||
return slides.map((item) => h(Slide, {})); | ||
return slides.map((item) => h(Slide, {})) | ||
}, | ||
} | ||
); | ||
) | ||
}, | ||
}); | ||
}) |
@@ -1,26 +0,27 @@ | ||
import App from './App'; | ||
import { mount } from '@vue/test-utils'; | ||
import { mount } from '@vue/test-utils' | ||
import App from './App' | ||
describe('Carousel.ts', () => { | ||
let wrapper: any; | ||
let wrapper: any | ||
beforeAll(async () => { | ||
wrapper = mount(App); | ||
console.log(wrapper.html()); | ||
}); | ||
wrapper = mount(App) | ||
console.log(wrapper.html()) | ||
}) | ||
it('It renders *five* slides correctly', () => { | ||
const slides = wrapper.findAll('.carousel__slide'); | ||
expect(slides.length).toBe(5); | ||
}); | ||
const slides = wrapper.findAll('.carousel__slide') | ||
expect(slides.length).toBe(5) | ||
}) | ||
it('Should display *one* visible item', () => { | ||
const slides = wrapper.findAll('.carousel__slide--visible'); | ||
expect(slides.length).toBe(1); | ||
}); | ||
const slides = wrapper.findAll('.carousel__slide--visible') | ||
expect(slides.length).toBe(1) | ||
}) | ||
it('Should display *one* next item', () => { | ||
const slides = wrapper.findAll('.carousel__slide--next'); | ||
expect(slides.length).toBe(1); | ||
}); | ||
}); | ||
const slides = wrapper.findAll('.carousel__slide--next') | ||
expect(slides.length).toBe(1) | ||
}) | ||
}) |
@@ -1,15 +0,16 @@ | ||
import Carousel from '@/components/Carousel'; | ||
import { mount } from '@vue/test-utils'; | ||
import { mount } from '@vue/test-utils' | ||
import Carousel from '@/components/Carousel' | ||
describe('Carousel.ts', () => { | ||
let wrapper: any; | ||
let wrapper: any | ||
beforeEach(async () => { | ||
wrapper = mount(Carousel); | ||
}); | ||
wrapper = mount(Carousel) | ||
}) | ||
it('It renders correctly', () => { | ||
const carousel = wrapper.find('.carousel'); | ||
expect(carousel.exists()).toBe(true); | ||
}); | ||
}); | ||
const carousel = wrapper.find('.carousel') | ||
expect(carousel.exists()).toBe(true) | ||
}) | ||
}) |
Sorry, the diff of this file is not supported yet
271547
12
24
1788
69