/** * Vue 3 Carousel 0.8.1 * (c) 2024 * @license MIT */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('vue')) : typeof define === 'function' && define.amd ? define(['exports', 'vue'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.VueCarousel = {}, global.Vue)); })(this, (function (exports, vue) { 'use strict'; const SNAP_ALIGN_OPTIONS = ['center', 'start', 'end', 'center-even', 'center-odd']; const BREAKPOINT_MODE_OPTIONS = ['viewport', 'carousel']; const DIR_OPTIONS = [ 'ltr', 'left-to-right', 'rtl', 'right-to-left', 'ttb', 'top-to-bottom', 'btt', 'bottom-to-top', ]; const I18N_DEFAULT_CONFIG = { ariaNextSlide: 'Navigate to next slide', ariaPreviousSlide: 'Navigate to previous slide', ariaNavigateToSlide: 'Navigate to slide {slideNumber}', ariaGallery: 'Gallery', itemXofY: 'Item {currentSlide} of {slidesCount}', iconArrowUp: 'Arrow pointing upwards', iconArrowDown: 'Arrow pointing downwards', iconArrowRight: 'Arrow pointing to the right', iconArrowLeft: 'Arrow pointing to the left', }; const DEFAULT_CONFIG = { enabled: true, itemsToShow: 1, itemsToScroll: 1, modelValue: 0, transition: 300, autoplay: 0, gap: 0, height: 'auto', wrapAround: false, pauseAutoplayOnHover: false, mouseDrag: true, touchDrag: true, snapAlign: SNAP_ALIGN_OPTIONS[0], dir: DIR_OPTIONS[0], breakpointMode: BREAKPOINT_MODE_OPTIONS[0], breakpoints: undefined, i18n: I18N_DEFAULT_CONFIG, }; const carouselProps = { // enable/disable the carousel component enabled: { default: DEFAULT_CONFIG.enabled, type: Boolean, }, // count of items to showed per view itemsToShow: { default: DEFAULT_CONFIG.itemsToShow, type: Number, }, // count of items to be scrolled itemsToScroll: { default: DEFAULT_CONFIG.itemsToScroll, type: Number, }, // control infinite scrolling mode wrapAround: { default: DEFAULT_CONFIG.wrapAround, type: Boolean, }, // control the gap between slides gap: { default: DEFAULT_CONFIG.gap, type: Number, }, // control the gap between slides height: { default: DEFAULT_CONFIG.height, type: [Number, String], }, // control snap position alignment snapAlign: { default: DEFAULT_CONFIG.snapAlign, validator(value) { return SNAP_ALIGN_OPTIONS.includes(value); }, }, // sliding transition time in ms transition: { default: DEFAULT_CONFIG.transition, type: Number, }, // controls the breakpoint mode relative to the carousel container or the viewport breakpointMode: { default: DEFAULT_CONFIG.breakpointMode, validator(value) { return BREAKPOINT_MODE_OPTIONS.includes(value); }, }, // an object to store breakpoints breakpoints: { default: DEFAULT_CONFIG.breakpoints, type: Object, }, // time to auto advance slides in ms autoplay: { default: DEFAULT_CONFIG.autoplay, type: Number, }, // pause autoplay when mouse hover over the carousel pauseAutoplayOnHover: { default: DEFAULT_CONFIG.pauseAutoplayOnHover, type: Boolean, }, // slide number number of initial slide modelValue: { default: undefined, type: Number, }, // toggle mouse dragging. mouseDrag: { default: DEFAULT_CONFIG.mouseDrag, type: Boolean, }, // toggle mouse dragging. touchDrag: { default: DEFAULT_CONFIG.touchDrag, type: Boolean, }, // control snap position alignment dir: { default: DEFAULT_CONFIG.dir, validator(value) { // The value must match one of these strings return DIR_OPTIONS.includes(value); }, }, // aria-labels and additional text labels i18n: { default: DEFAULT_CONFIG.i18n, type: Object, }, }; /** * Determines the maximum slide index based on the configuration. * * @param {Args} args - The carousel configuration and slide count. * @returns {number} The maximum slide index. */ function getMaxSlideIndex({ config, slidesCount }) { var _a; const { snapAlign = 'N/A', wrapAround, itemsToShow = 1 } = config; // If wrapAround is enabled, the max index is the last slide if (wrapAround) { return Math.max(slidesCount - 1, 0); } // Map snapAlign values to calculation logic const snapAlignCalculations = { start: Math.ceil(slidesCount - itemsToShow), end: Math.ceil(slidesCount - 1), center: slidesCount - Math.ceil((itemsToShow - 0.5) / 2), 'center-odd': slidesCount - Math.ceil((itemsToShow - 0.5) / 2), 'center-even': slidesCount - Math.ceil(itemsToShow / 2), }; // Compute the max index based on snapAlign, or default to 0 const calculateMaxIndex = (_a = snapAlignCalculations[snapAlign]) !== null && _a !== void 0 ? _a : 0; // Return the result ensuring it's non-negative return Math.max(calculateMaxIndex, 0); } /** * Determines the minimum slide index based on the configuration. * * @param {Args} args - The carousel configuration and slide count. * @returns {number} The minimum slide index. */ function getMinSlideIndex({ config, slidesCount }) { var _a; const { snapAlign = 'N/A', wrapAround, itemsToShow = 1 } = config; // If wrapAround is enabled or itemsToShow exceeds slidesCount, the minimum index is always 0 if (wrapAround || itemsToShow > slidesCount) { return 0; } // Map of snapAlign to offset calculations const snapAlignCalculations = { start: 0, end: Math.floor(itemsToShow - 1), center: Math.floor((itemsToShow - 1) / 2), 'center-odd': Math.floor((itemsToShow - 1) / 2), 'center-even': Math.floor((itemsToShow - 2) / 2), }; // Return the calculated offset or default to 0 for invalid snapAlign values return (_a = snapAlignCalculations[snapAlign]) !== null && _a !== void 0 ? _a : 0; } function getNumberInRange({ val, max, min }) { if (max < min) { return val; } return Math.min(Math.max(val, min), max); } const calculateOffset = (snapAlign, itemsToShow) => { var _a; const offsetMap = { start: 0, center: (itemsToShow - 1) / 2, 'center-odd': (itemsToShow - 1) / 2, 'center-even': (itemsToShow - 2) / 2, end: itemsToShow - 1, }; return (_a = offsetMap[snapAlign]) !== null && _a !== void 0 ? _a : 0; // Fallback to 0 for unknown snapAlign }; function getScrolledIndex({ config, currentSlide, slidesCount }) { const { snapAlign = 'N/A', wrapAround, itemsToShow = 1 } = config; // Calculate the offset based on snapAlign const offset = calculateOffset(snapAlign, itemsToShow); // Compute the index with or without wrapAround if (!wrapAround) { return getNumberInRange({ val: currentSlide - offset, max: slidesCount - itemsToShow, min: 0, }); } return currentSlide - offset; } function getSlidesVNodes(vNode) { if (!vNode) return []; return vNode.reduce((acc, node) => { var _a; if (node.type === vue.Fragment) { return [...acc, ...getSlidesVNodes(node.children)]; } if (((_a = node.type) === null || _a === void 0 ? void 0 : _a.name) === 'CarouselSlide') { return [...acc, node]; } return acc; }, []); } function mapNumberToRange({ val, max, min = 0 }) { const mod = max - min + 1; return ((val - min) % mod + mod) % mod + min; } /** * return a throttle version of the function * Throttling * */ // eslint-disable-next-line no-unused-vars function throttle(fn) { let isRunning = false; return function (...args) { if (!isRunning) { isRunning = true; requestAnimationFrame(() => { fn.apply(this, args); isRunning = false; }); } }; } /** * return a debounced version of the function * @param fn * @param delay */ // eslint-disable-next-line no-unused-vars function debounce(fn, delay) { let timerId; return function (...args) { if (timerId) { clearTimeout(timerId); } timerId = setTimeout(() => { fn(...args); timerId = null; }, delay); }; } function i18nFormatter(string = '', values = {}) { return Object.entries(values).reduce((acc, [key, value]) => acc.replace(`{${key}}`, String(value)), string); } var ARIAComponent = vue.defineComponent({ name: 'ARIA', setup() { const config = vue.inject('config', vue.reactive(Object.assign({}, DEFAULT_CONFIG))); 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', }, i18nFormatter(config.i18n['itemXofY'], { currentSlide: currentSlide.value + 1, slidesCount: slidesCount.value, })); }, }); var Carousel = vue.defineComponent({ name: 'Carousel', props: carouselProps, emits: [ 'init', 'drag', 'slide-start', 'loop', 'update:modelValue', 'slide-end', 'before-init', ], setup(props, { slots, emit, expose }) { var _a; const root = vue.ref(null); const viewport = vue.ref(null); const slides = vue.ref([]); const slideSize = vue.ref(0); const slidesCount = vue.ref(0); const fallbackConfig = vue.computed(() => (Object.assign(Object.assign(Object.assign({}, DEFAULT_CONFIG), props), { i18n: Object.assign(Object.assign({}, DEFAULT_CONFIG.i18n), props.i18n), breakpoints: undefined }))); // current active config const config = vue.reactive(Object.assign({}, fallbackConfig.value)); // slides const currentSlideIndex = vue.ref((_a = props.modelValue) !== null && _a !== void 0 ? _a : 0); const prevSlideIndex = vue.ref(0); const middleSlideIndex = vue.ref(0); const maxSlideIndex = vue.ref(0); const minSlideIndex = vue.ref(0); let autoplayTimer = null; let transitionTimer = null; let resizeObserver = null; const effectiveSlideSize = vue.computed(() => slideSize.value + config.gap); const normalizeDir = vue.computed(() => { const dir = config.dir || 'lrt'; const dirMap = { 'left-to-right': 'ltr', 'right-to-left': 'rtl', 'top-to-bottom': 'ttb', 'bottom-to-top': 'btt', }; return dirMap[dir] || dir; }); const isVertical = vue.computed(() => ['ttb', 'btt'].includes(normalizeDir.value)); vue.provide('config', config); vue.provide('slidesCount', slidesCount); vue.provide('currentSlide', currentSlideIndex); vue.provide('maxSlide', maxSlideIndex); vue.provide('minSlide', minSlideIndex); vue.provide('slideSize', slideSize); vue.provide('isVertical', isVertical); vue.provide('normalizeDir', normalizeDir); function updateBreakpointsConfig() { var _a; // Determine the width source based on the 'breakpointMode' config const widthSource = (config.breakpointMode === 'carousel' ? (_a = root.value) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect().width : window.innerWidth) || 0; const breakpointsArray = Object.keys(props.breakpoints || {}) .map((key) => Number(key)) .sort((a, b) => +b - +a); let newConfig = Object.assign({}, fallbackConfig.value); breakpointsArray.some((breakpoint) => { var _a; if (widthSource >= breakpoint) { newConfig = Object.assign(Object.assign({}, newConfig), (_a = props.breakpoints) === null || _a === void 0 ? void 0 : _a[breakpoint]); return true; } return false; }); Object.assign(config, newConfig); } const handleResize = debounce(() => { updateBreakpointsConfig(); updateSlidesData(); updateSlideSize(); }, 16); /** * Setup functions */ function updateSlideSize() { if (!viewport.value) return; const rect = viewport.value.getBoundingClientRect(); // Calculate the total gap space const totalGap = (config.itemsToShow - 1) * config.gap; // Calculate size based on orientation if (isVertical.value) { slideSize.value = (rect.height - totalGap) / config.itemsToShow; } else { slideSize.value = (rect.width - totalGap) / config.itemsToShow; } } function updateSlidesData() { if (slidesCount.value <= 0) return; middleSlideIndex.value = Math.ceil((slidesCount.value - 1) / 2); maxSlideIndex.value = getMaxSlideIndex({ config, slidesCount: slidesCount.value }); minSlideIndex.value = getMinSlideIndex({ config, slidesCount: slidesCount.value }); if (!config.wrapAround) { currentSlideIndex.value = getNumberInRange({ val: currentSlideIndex.value, max: maxSlideIndex.value, min: minSlideIndex.value, }); } } vue.onMounted(() => { vue.nextTick(() => updateSlideSize()); // Overcome some edge cases setTimeout(() => updateSlideSize(), 1000); updateBreakpointsConfig(); initAutoplay(); window.addEventListener('resize', handleResize, { passive: true }); resizeObserver = new ResizeObserver(handleResize); if (root.value) { resizeObserver.observe(root.value); } emit('init'); }); vue.onUnmounted(() => { if (transitionTimer) { clearTimeout(transitionTimer); } if (autoplayTimer) { clearInterval(autoplayTimer); } if (resizeObserver && root.value) { resizeObserver.unobserve(root.value); resizeObserver = null; } window.removeEventListener('resize', handleResize, { passive: true, }); }); /** * Carousel Event listeners */ let isTouch = false; const startPosition = { x: 0, y: 0 }; const dragged = vue.reactive({ x: 0, y: 0 }); const isHover = vue.ref(false); const isDragging = vue.ref(false); const handleMouseEnter = () => { isHover.value = true; }; const handleMouseLeave = () => { isHover.value = false; }; function handleDragStart(event) { // Prevent drag initiation on input elements or if already sliding const targetTagName = event.target.tagName; if (['INPUT', 'TEXTAREA', 'SELECT'].includes(targetTagName) || isSliding.value) { return; } // Detect if the event is a touchstart or mousedown event isTouch = event.type === 'touchstart'; // For mouse events, prevent default to avoid text selection if (!isTouch) { event.preventDefault(); if (event.button !== 0) { // Ignore non-left-click mouse events return; } } // Initialize start positions for the drag startPosition.x = isTouch ? event.touches[0].clientX : event.clientX; startPosition.y = isTouch ? event.touches[0].clientY : event.clientY; // Attach event listeners for dragging and drag end const moveEvent = isTouch ? 'touchmove' : 'mousemove'; const endEvent = isTouch ? 'touchend' : 'mouseup'; document.addEventListener(moveEvent, handleDragging, { passive: false }); document.addEventListener(endEvent, handleDragEnd, { passive: true }); } const handleDragging = throttle((event) => { isDragging.value = true; // Get the current position based on the interaction type (touch or mouse) const currentX = isTouch ? event.touches[0].clientX : event.clientX; const currentY = isTouch ? event.touches[0].clientY : event.clientY; // Calculate deltas for X and Y axes const deltaX = currentX - startPosition.x; const deltaY = currentY - startPosition.y; // Update dragged state reactively dragged.x = deltaX; dragged.y = deltaY; // Emit a drag event for further customization if needed emit('drag', { deltaX, deltaY }); }); function handleDragEnd() { // Determine the active axis and direction multiplier const dragAxis = isVertical.value ? 'y' : 'x'; const directionMultiplier = ['rtl', 'btt'].includes(normalizeDir.value) ? -1 : 1; // Calculate dragged slides with a tolerance to account for incomplete drags const tolerance = Math.sign(dragged[dragAxis]) * 0.4; // Smooth out small drags const draggedSlides = Math.round(dragged[dragAxis] / effectiveSlideSize.value + tolerance) * directionMultiplier; // Prevent accidental clicks when there is a slide drag if (draggedSlides && !isTouch) { const preventClick = (e) => { e.preventDefault(); window.removeEventListener('click', preventClick); }; window.addEventListener('click', preventClick); } // Slide to the appropriate slide index const targetSlideIndex = currentSlideIndex.value - draggedSlides; slideTo(targetSlideIndex); // Reset drag state dragged.x = 0; dragged.y = 0; isDragging.value = false; const moveEvent = isTouch ? 'touchmove' : 'mousemove'; const endEvent = isTouch ? 'touchend' : 'mouseup'; document.removeEventListener(moveEvent, handleDragging); document.removeEventListener(endEvent, handleDragEnd); } /** * Autoplay */ function initAutoplay() { if (!config.autoplay || config.autoplay <= 0) { return; } autoplayTimer = setInterval(() => { if (config.pauseAutoplayOnHover && isHover.value) { return; } next(); }, config.autoplay); } function resetAutoplay() { if (autoplayTimer) { clearInterval(autoplayTimer); autoplayTimer = null; } initAutoplay(); } /** * Navigation function */ const isSliding = vue.ref(false); function slideTo(slideIndex) { const currentVal = config.wrapAround ? slideIndex : getNumberInRange({ val: slideIndex, max: maxSlideIndex.value, min: minSlideIndex.value, }); if (currentSlideIndex.value === currentVal || isSliding.value) { return; } emit('slide-start', { slidingToIndex: slideIndex, currentSlideIndex: currentSlideIndex.value, prevSlideIndex: prevSlideIndex.value, slidesCount: slidesCount.value, }); isSliding.value = true; prevSlideIndex.value = currentSlideIndex.value; currentSlideIndex.value = currentVal; transitionTimer = setTimeout(() => { if (config.wrapAround) { const mappedNumber = mapNumberToRange({ val: currentVal, max: maxSlideIndex.value, min: 0, }); if (mappedNumber !== currentSlideIndex.value) { currentSlideIndex.value = mappedNumber; emit('loop', { currentSlideIndex: currentSlideIndex.value, slidingToIndex: slideIndex, }); } } emit('update:modelValue', currentSlideIndex.value); emit('slide-end', { currentSlideIndex: currentSlideIndex.value, prevSlideIndex: prevSlideIndex.value, slidesCount: slidesCount.value, }); isSliding.value = false; resetAutoplay(); }, config.transition); } function next() { slideTo(currentSlideIndex.value + config.itemsToScroll); } function prev() { slideTo(currentSlideIndex.value - config.itemsToScroll); } const nav = { slideTo, next, prev }; vue.provide('nav', nav); vue.provide('isSliding', isSliding); function restartCarousel() { updateBreakpointsConfig(); updateSlidesData(); updateSlideSize(); resetAutoplay(); } // Update the carousel on props change vue.watch(() => (Object.assign({}, props)), restartCarousel, { deep: true }); // Handle changing v-model value vue.watch(() => props['modelValue'], (val) => { if (val === currentSlideIndex.value) { return; } slideTo(Number(val)); }); // Handel when slides added/removed vue.watch(slidesCount, updateSlidesData); // Init carousel emit('before-init'); const data = { config, slidesCount, slideSize, currentSlide: currentSlideIndex, maxSlide: maxSlideIndex, minSlide: minSlideIndex, middleSlide: middleSlideIndex, }; expose({ updateBreakpointsConfig, updateSlidesData, updateSlideSize, restartCarousel, slideTo, next, prev, nav, data, }); /** * Track style */ const trackTransform = vue.computed(() => { // Calculate the scrolled index with wrapping offset if applicable const scrolledIndex = getScrolledIndex({ config, currentSlide: currentSlideIndex.value, slidesCount: slidesCount.value, }); const cloneOffset = config.wrapAround ? slidesCount.value : 0; // Determine direction multiplier for orientation const isReverseDirection = ['rtl', 'btt'].includes(normalizeDir.value); const directionMultiplier = isReverseDirection ? -1 : 1; // Calculate the total offset for slide transformation const totalOffset = (scrolledIndex + cloneOffset) * effectiveSlideSize.value * directionMultiplier; // Include user drag interaction offset const dragOffset = isVertical.value ? dragged.y : dragged.x; // Generate the appropriate CSS transformation const translateAxis = isVertical.value ? 'Y' : 'X'; return `translate${translateAxis}(${dragOffset - totalOffset}px)`; }); const slotSlides = slots.default || slots.slides; const slotAddons = slots.addons; const slotsProps = vue.reactive(data); return () => { if (!config.enabled) { return vue.h('section', { ref: root, class: ['carousel', 'is-disabled'], }, slotSlides === null || slotSlides === void 0 ? void 0 : slotSlides()); } const slidesElements = getSlidesVNodes(slotSlides === null || slotSlides === void 0 ? void 0 : slotSlides(slotsProps)); const addonsElements = (slotAddons === null || slotAddons === void 0 ? void 0 : slotAddons(slotsProps)) || []; slidesElements.forEach((el, index) => { if (el.props) { el.props.index = index; } else { el.props = { index }; } }); let output = slidesElements; if (config.wrapAround) { const slidesBefore = slidesElements.map((el, index) => vue.cloneVNode(el, { index: -slidesElements.length + index, isClone: true, key: `clone-before-${index}`, })); const slidesAfter = slidesElements.map((el, index) => vue.cloneVNode(el, { index: slidesElements.length + index, isClone: true, key: `clone-after-${index}`, })); output = [...slidesBefore, ...slidesElements, ...slidesAfter]; } slides.value = slidesElements; slidesCount.value = Math.max(slidesElements.length, 1); const trackEl = vue.h('ol', { class: 'carousel__track', style: { transform: trackTransform.value, transition: `${isSliding.value ? config.transition : 0}ms`, gap: `${config.gap}px`, }, onMousedownCapture: config.mouseDrag ? handleDragStart : null, onTouchstartPassiveCapture: config.touchDrag ? handleDragStart : null, }, output); const viewPortEl = vue.h('div', { class: 'carousel__viewport', ref: viewport }, trackEl); return vue.h('section', { ref: root, class: [ 'carousel', `is-${normalizeDir.value}`, { 'is-vertical': isVertical.value, 'is-sliding': isSliding.value, 'is-dragging': isDragging.value, 'is-hover': isHover.value, }, ], style: { '--vc-trk-height': `${typeof config.height === 'number' ? `${config.height}px` : config.height}`, }, dir: normalizeDir.value, 'aria-label': config.i18n['ariaGallery'], tabindex: '0', onMouseenter: handleMouseEnter, onMouseleave: handleMouseLeave, }, [viewPortEl, addonsElements, vue.h(ARIAComponent)]); }; }, }); var IconName; (function (IconName) { IconName["arrowUp"] = "arrowUp"; IconName["arrowDown"] = "arrowDown"; IconName["arrowRight"] = "arrowRight"; IconName["arrowLeft"] = "arrowLeft"; })(IconName || (IconName = {})); const icons = { arrowUp: 'M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z', arrowDown: 'M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z', arrowRight: 'M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z', arrowLeft: 'M15.41 16.59L10.83 12l4.58-4.59L14 6l-6 6 6 6 1.41-1.41z', }; function isIconName(candidate) { return candidate in IconName; } const Icon = (props) => { const config = vue.inject('config', vue.reactive(Object.assign({}, DEFAULT_CONFIG))); const iconName = String(props.name); const iconI18n = `icon${iconName.charAt(0).toUpperCase() + iconName.slice(1)}`; if (!iconName || typeof iconName !== 'string' || !isIconName(iconName)) { return; } const path = icons[iconName]; const pathEl = vue.h('path', { d: path }); const iconTitle = config.i18n[iconI18n] || props.title || iconName; const titleEl = vue.h('title', iconTitle); return vue.h('svg', { class: 'carousel__icon', viewBox: '0 0 24 24', role: 'img', 'aria-label': iconTitle, }, [titleEl, pathEl]); }; Icon.props = { name: String, title: String }; const Navigation = (props, { slots, attrs }) => { const { next: slotNext, prev: slotPrev } = slots || {}; const config = vue.inject('config', vue.reactive(Object.assign({}, DEFAULT_CONFIG))); const maxSlide = vue.inject('maxSlide', vue.ref(1)); const minSlide = vue.inject('minSlide', vue.ref(1)); const normalizeDir = vue.inject('normalizeDir', vue.ref('ltr')); const currentSlide = vue.inject('currentSlide', vue.ref(1)); const nav = vue.inject('nav', {}); const { wrapAround, i18n } = config; const getPrevIcon = () => { const directionIcons = { ltr: 'arrowLeft', rtl: 'arrowRight', ttb: 'arrowUp', btt: 'arrowDown', }; return directionIcons[normalizeDir.value]; }; const getNextIcon = () => { const directionIcons = { ltr: 'arrowRight', rtl: 'arrowLeft', ttb: 'arrowDown', btt: 'arrowUp', }; return directionIcons[normalizeDir.value]; }; const prevButton = vue.h('button', { type: 'button', class: [ 'carousel__prev', !wrapAround && currentSlide.value <= minSlide.value && 'carousel__prev--disabled', attrs === null || attrs === void 0 ? void 0 : attrs.class, ], 'aria-label': i18n['ariaPreviousSlide'], title: i18n['ariaPreviousSlide'], onClick: nav.prev, }, (slotPrev === null || slotPrev === void 0 ? void 0 : slotPrev()) || vue.h(Icon, { name: getPrevIcon() })); const nextButton = vue.h('button', { type: 'button', class: [ 'carousel__next', !wrapAround && currentSlide.value >= maxSlide.value && 'carousel__next--disabled', attrs === null || attrs === void 0 ? void 0 : attrs.class, ], 'aria-label': i18n['ariaNextSlide'], title: i18n['ariaNextSlide'], onClick: nav.next, }, (slotNext === null || slotNext === void 0 ? void 0 : slotNext()) || vue.h(Icon, { name: getNextIcon() })); return [prevButton, nextButton]; }; const Pagination = () => { const config = vue.inject('config', vue.reactive(Object.assign({}, DEFAULT_CONFIG))); 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', {}); const isActive = (slide) => mapNumberToRange({ val: currentSlide.value, max: maxSlide.value, min: 0, }) === slide; const children = []; for (let slide = minSlide.value; slide < maxSlide.value + 1; slide++) { const buttonLabel = i18nFormatter(config.i18n['ariaNavigateToSlide'], { slideNumber: slide + 1, }); const button = vue.h('button', { type: 'button', class: { 'carousel__pagination-button': true, 'carousel__pagination-button--active': isActive(slide), }, 'aria-label': buttonLabel, title: buttonLabel, onClick: () => nav.slideTo(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({ name: 'CarouselSlide', props: { index: { type: Number, default: 1, }, isClone: { type: Boolean, default: false, }, }, setup(props, { slots }) { const config = vue.inject('config', vue.reactive(Object.assign({}, DEFAULT_CONFIG))); const currentSlide = vue.inject('currentSlide', vue.ref(0)); const slidesToScroll = vue.inject('slidesToScroll', vue.ref(0)); const isSliding = vue.inject('isSliding', vue.ref(false)); const isVertical = vue.inject('isVertical', vue.ref(false)); const slideSize = vue.inject('slideSize', vue.ref(0)); const isActive = vue.computed(() => props.index === currentSlide.value); const isPrev = vue.computed(() => props.index === currentSlide.value - 1); const isNext = vue.computed(() => props.index === currentSlide.value + 1); const isVisible = vue.computed(() => { const min = Math.floor(slidesToScroll.value); const max = Math.ceil(slidesToScroll.value + config.itemsToShow - 1); return props.index >= min && props.index <= max; }); const slideStyle = vue.computed(() => { const dimension = config.gap ? `${slideSize.value}px` : `${100 / config.itemsToShow}%`; return isVertical.value ? { height: dimension, width: '' } : { width: dimension, height: '' }; }); return () => { var _a, _b; if (!config.enabled) { return (_a = slots.default) === null || _a === void 0 ? void 0 : _a.call(slots); } return vue.h('li', { style: slideStyle.value, class: { carousel__slide: true, 'carousel__slide--clone': props.isClone, 'carousel__slide--visible': isVisible.value, 'carousel__slide--active': isActive.value, 'carousel__slide--prev': isPrev.value, 'carousel__slide--next': isNext.value, 'carousel__slide--sliding': isSliding.value, }, 'aria-hidden': !isVisible.value, }, (_b = slots.default) === null || _b === void 0 ? void 0 : _b.call(slots, { isActive: isActive.value, isClone: props.isClone, isPrev: isPrev.value, isNext: isNext.value, isSliding: isSliding.value, isVisible: isVisible.value, })); }; }, }); exports.Carousel = Carousel; exports.Icon = Icon; exports.Navigation = Navigation; exports.Pagination = Pagination; exports.Slide = Slide; }));