/**
 * Vue 3 Carousel 0.10.0
 * (c) 2024
 * @license MIT
 */
import { cloneVNode, defineComponent, inject, h, ref, shallowReactive, computed, reactive, watchEffect, onMounted, onBeforeUnmount, provide, watch, useId, getCurrentInstance, onUnmounted, onUpdated } from 'vue';

// Use a symbol for inject provide to avoid any kind of collision with another lib
// https://vuejs.org/guide/components/provide-inject#working-with-symbol-keys
const injectCarousel = Symbol('carousel');

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}',
    ariaNavigateToPage: 'Navigate to page {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 DIR_MAP = {
    'left-to-right': 'ltr',
    'right-to-left': 'rtl',
    'top-to-bottom': 'ttb',
    'bottom-to-top': 'btt',
};
const NORMALIZED_DIR_OPTIONS = Object.values(DIR_MAP);
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,
    ignoreAnimations: false,
};

function getNumberInRange({ val, max, min }) {
    if (max < min) {
        return val;
    }
    return Math.min(Math.max(val, isNaN(min) ? val : min), isNaN(max) ? val : max);
}

function mapNumberToRange({ val, max, min = 0 }) {
    const mod = max - min + 1;
    return ((((val - min) % mod) + mod) % mod) + min;
}

const calculateOffset = (snapAlign, itemsToShow) => {
    switch (snapAlign) {
        default:
        case 'start': return 0;
        case 'center': return (itemsToShow - 1) / 2;
        case 'center-odd': return (itemsToShow - 1) / 2;
        case 'center-even': return (itemsToShow - 2) / 2;
        case 'end': return itemsToShow - 1;
    }
};
function getScrolledIndex({ config, currentSlide, slidesCount, }) {
    const { snapAlign = 'center', 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,
        });
    }
    else {
        return mapNumberToRange({
            val: currentSlide - offset,
            max: slidesCount + itemsToShow,
            min: 0 - itemsToShow,
        });
    }
}

/**
 * Determines the minimum slide index based on the configuration.
 *
 * @param {GetMinSlideIndexArgs} args - The carousel configuration and slide count.
 * @returns {number} The minimum slide index.
 */
function getMinSlideIndex({ config, slidesCount }) {
    const { snapAlign = 'center', wrapAround, itemsToShow = 1 } = config;
    // If wrapAround is enabled or itemsToShow exceeds slidesCount, the minimum index is always 0
    if (wrapAround || itemsToShow > slidesCount) {
        return 0;
    }
    // Return the calculated offset or default to 0 for invalid snapAlign values
    return Math.max(0, Math.floor(calculateOffset(snapAlign, itemsToShow)));
}

/**
 * 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 }) {
    const { snapAlign = 'center', wrapAround, itemsToShow = 1 } = config;
    // Map snapAlign values to calculation logic
    function snapAlignCalculations() {
        // If wrapAround is enabled, fallback to default which is the last slide
        switch (wrapAround ? '' : snapAlign) {
            case 'start':
                return Math.ceil(slidesCount - itemsToShow);
            case 'center':
            case 'center-odd':
                return slidesCount - Math.ceil((itemsToShow - 0.5) / 2);
            case 'center-even':
                return slidesCount - Math.ceil(itemsToShow / 2);
            case 'end':
            default:
                return Math.ceil(slidesCount - 1);
        }
    }
    // Return the result ensuring it's non-negative
    return Math.max(snapAlignCalculations(), 0);
}

function i18nFormatter(string = '', values = {}) {
    return Object.entries(values).reduce((acc, [key, value]) => acc.replace(`{${key}}`, String(value)), string);
}

/**
 * Returns a throttled version of the function using requestAnimationFrame.
 *
 * @param fn - The function to throttle.
 * @param ms - The number of milliseconds to wait for the throttled function to be called again
 */
function throttle(fn, ms = 0) {
    let isThrottled = false;
    let start = 0;
    let frameId = null;
    function throttled(...args) {
        if (isThrottled)
            return;
        isThrottled = true;
        const step = () => {
            frameId = requestAnimationFrame((time) => {
                const elapsed = time - start;
                if (elapsed > ms) {
                    start = time;
                    fn(...args);
                    isThrottled = false;
                }
                else {
                    step();
                }
            });
        };
        step();
    }
    throttled.cancel = () => {
        if (frameId) {
            cancelAnimationFrame(frameId);
            frameId = null;
            isThrottled = false;
        }
    };
    return throttled;
}

/** Useful function to destructure props without triggering reactivity for certain keys */
function except(obj, keys) {
    return Object.keys(obj).filter((k) => !keys.includes(k))
        .reduce((acc, key) => (acc[key] = obj[key], acc), {});
}

function getTransformValues(el) {
    const { transform } = window.getComputedStyle(el);
    //add sanity check
    return transform
        .split(/[(,)]/)
        .slice(1, -1)
        .map((v) => parseFloat(v));
}

function createCloneSlides({ slides, position, toShow }) {
    const clones = [];
    const isBefore = position === 'before';
    const start = isBefore ? -toShow : 0;
    const end = isBefore ? 0 : toShow;
    if (slides.length <= 0) {
        return clones;
    }
    for (let i = start; i < end; i++) {
        const index = isBefore ? i : i + slides.length;
        const props = {
            index,
            isClone: true,
            id: undefined, // Make sure we don't duplicate the id which would be invalid html
            key: `clone-${position}-${i}`,
        };
        const vnode = slides[((i % slides.length) + slides.length) % slides.length].vnode;
        const clone = cloneVNode(vnode, props);
        clone.el = null;
        clones.push(clone);
    }
    return clones;
}

const ARIA = defineComponent({
    name: 'CarouselAria',
    setup() {
        const carousel = inject(injectCarousel);
        if (!carousel) {
            return () => '';
        }
        return () => h('div', {
            class: ['carousel__liveregion', 'carousel__sr-only'],
            'aria-live': 'polite',
            'aria-atomic': 'true',
        }, i18nFormatter(carousel.config.i18n['itemXofY'], {
            currentSlide: carousel.currentSlide + 1,
            slidesCount: carousel.slidesCount,
        }));
    },
});

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: {
        type: String,
        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,
    },
    ignoreAnimations: {
        default: false,
        type: [Array, Boolean, String],
    }
};

const Carousel = defineComponent({
    name: 'VueCarousel',
    props: carouselProps,
    emits: [
        'init',
        'drag',
        'slide-start',
        'loop',
        'update:modelValue',
        'slide-end',
        'before-init',
    ],
    setup(props, { slots, emit, expose }) {
        var _a;
        const root = ref(null);
        const viewport = ref(null);
        const slides = shallowReactive([]);
        const slideSize = ref(0);
        const slidesCount = computed(() => slides.length);
        const fallbackConfig = computed(() => (Object.assign(Object.assign(Object.assign({}, DEFAULT_CONFIG), except(props, ['breakpoints', 'modelValue'])), { i18n: Object.assign(Object.assign({}, DEFAULT_CONFIG.i18n), props.i18n) })));
        // current active config
        const config = reactive(Object.assign({}, fallbackConfig.value));
        // slides
        const currentSlideIndex = ref((_a = props.modelValue) !== null && _a !== void 0 ? _a : 0);
        const prevSlideIndex = ref(0);
        const middleSlideIndex = computed(() => Math.ceil((slidesCount.value - 1) / 2));
        const maxSlideIndex = computed(() => {
            return getMaxSlideIndex({ config, slidesCount: slidesCount.value });
        });
        const minSlideIndex = computed(() => {
            return getMinSlideIndex({ config, slidesCount: slidesCount.value });
        });
        let autoplayTimer = null;
        let transitionTimer = null;
        let resizeObserver = null;
        const effectiveSlideSize = computed(() => slideSize.value + config.gap);
        const normalizedDir = computed(() => {
            const dir = config.dir || 'ltr';
            return dir in DIR_MAP ? DIR_MAP[dir] : dir;
        });
        const indexCbs = [];
        const registerSlide = (slide, indexCb) => {
            indexCb(slides.length);
            slides.push(slide);
            indexCbs.push(indexCb);
        };
        const unregisterSlide = (slide) => {
            const found = slides.indexOf(slide);
            if (found >= 0) {
                slides.splice(found, 1);
                indexCbs.splice(found, 1);
                // Update indexes after the one that was removed
                indexCbs.slice(found).forEach((cb, index) => cb(found + index));
            }
        };
        const isReversed = computed(() => ['rtl', 'btt'].includes(normalizedDir.value));
        const isVertical = computed(() => ['ttb', 'btt'].includes(normalizedDir.value));
        const clonedSlidesCount = computed(() => Math.ceil(config.itemsToShow) + 1);
        function updateBreakpointsConfig() {
            var _a;
            if (!mounted.value) {
                return;
            }
            // Determine the width source based on the 'breakpointMode' config
            const widthSource = (fallbackConfig.value.breakpointMode === 'carousel'
                ? (_a = root.value) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect().width
                : typeof window !== 'undefined'
                    ? window.innerWidth
                    : 0) || 0;
            const breakpointsArray = Object.keys(props.breakpoints || {})
                .map((key) => Number(key))
                .sort((a, b) => +b - +a);
            const newConfig = {};
            breakpointsArray.some((breakpoint) => {
                if (widthSource >= breakpoint) {
                    Object.assign(newConfig, props.breakpoints[breakpoint]);
                    if (newConfig.i18n) {
                        Object.assign(newConfig.i18n, fallbackConfig.value.i18n, props.breakpoints[breakpoint].i18n);
                    }
                    return true;
                }
                return false;
            });
            Object.assign(config, fallbackConfig.value, newConfig);
        }
        const handleResize = throttle(() => {
            updateBreakpointsConfig();
            updateSlidesData();
            updateSlideSize();
        });
        const totalGap = computed(() => (config.itemsToShow - 1) * config.gap);
        const transformElements = shallowReactive(new Set());
        /**
         * Setup functions
         */
        function updateSlideSize() {
            if (!viewport.value)
                return;
            let multiplierWidth = 1;
            transformElements.forEach((el) => {
                const transformArr = getTransformValues(el);
                if (transformArr.length === 6) {
                    multiplierWidth *= transformArr[0];
                }
            });
            // Calculate size based on orientation
            if (isVertical.value) {
                if (config.height !== 'auto') {
                    const height = typeof config.height === 'string' && isNaN(parseInt(config.height))
                        ? viewport.value.getBoundingClientRect().height
                        : parseInt(config.height);
                    slideSize.value = (height - totalGap.value) / config.itemsToShow;
                }
            }
            else {
                const width = viewport.value.getBoundingClientRect().width;
                slideSize.value = (width / multiplierWidth - totalGap.value) / config.itemsToShow;
            }
        }
        function updateSlidesData() {
            if (!config.wrapAround && slidesCount.value > 0) {
                currentSlideIndex.value = getNumberInRange({
                    val: currentSlideIndex.value,
                    max: maxSlideIndex.value,
                    min: minSlideIndex.value,
                });
            }
        }
        const ignoreAnimations = computed(() => {
            if (typeof props.ignoreAnimations === 'string') {
                return props.ignoreAnimations.split(',');
            }
            else if (Array.isArray(props.ignoreAnimations)) {
                return props.ignoreAnimations;
            }
            else if (!props.ignoreAnimations) {
                return [];
            }
            return false;
        });
        watchEffect(() => updateSlidesData());
        watchEffect(() => {
            // Call updateSlideSize when viewport is ready and track deps
            updateSlideSize();
        });
        let animationInterval;
        const setAnimationInterval = (event) => {
            const target = event.target;
            if (!(target === null || target === void 0 ? void 0 : target.contains(root.value)) ||
                (Array.isArray(ignoreAnimations.value) &&
                    ignoreAnimations.value.includes(event.animationName))) {
                return;
            }
            transformElements.add(target);
            if (!animationInterval) {
                const stepAnimation = () => {
                    animationInterval = requestAnimationFrame(() => {
                        updateSlideSize();
                        stepAnimation();
                    });
                };
                stepAnimation();
            }
        };
        const finishAnimation = (event) => {
            const target = event.target;
            if (target) {
                transformElements.delete(target);
            }
            if (animationInterval && transformElements.size === 0) {
                cancelAnimationFrame(animationInterval);
                updateSlideSize();
            }
        };
        const mounted = ref(false);
        if (typeof document !== 'undefined') {
            watchEffect(() => {
                if (mounted.value && ignoreAnimations.value !== false) {
                    document.addEventListener('animationstart', setAnimationInterval);
                    document.addEventListener('animationend', finishAnimation);
                }
                else {
                    document.removeEventListener('animationstart', setAnimationInterval);
                    document.removeEventListener('animationend', finishAnimation);
                }
            });
        }
        onMounted(() => {
            mounted.value = true;
            updateBreakpointsConfig();
            initAutoplay();
            if (root.value) {
                resizeObserver = new ResizeObserver(handleResize);
                resizeObserver.observe(root.value);
            }
            emit('init');
        });
        onBeforeUnmount(() => {
            mounted.value = false;
            // Empty the slides before they unregister for better performance
            slides.splice(0, slides.length);
            indexCbs.splice(0, indexCbs.length);
            if (transitionTimer) {
                clearTimeout(transitionTimer);
            }
            if (animationInterval) {
                cancelAnimationFrame(animationInterval);
            }
            if (autoplayTimer) {
                clearInterval(autoplayTimer);
            }
            if (resizeObserver) {
                resizeObserver.disconnect();
                resizeObserver = null;
            }
            if (typeof document !== 'undefined') {
                handleBlur();
            }
            if (root.value) {
                root.value.removeEventListener('transitionend', updateSlideSize);
                root.value.removeEventListener('animationiteration', updateSlideSize);
            }
        });
        /**
         * Carousel Event listeners
         */
        let isTouch = false;
        const startPosition = { x: 0, y: 0 };
        const dragged = reactive({ x: 0, y: 0 });
        const isHover = ref(false);
        const isDragging = ref(false);
        const handleMouseEnter = () => {
            isHover.value = true;
        };
        const handleMouseLeave = () => {
            isHover.value = false;
        };
        const handleArrowKeys = throttle((event) => {
            if (event.ctrlKey)
                return;
            switch (event.key) {
                case 'ArrowLeft':
                case 'ArrowUp':
                    if (isVertical.value === event.key.endsWith('Up')) {
                        if (isReversed.value) {
                            nav.next(true);
                        }
                        else {
                            nav.prev(true);
                        }
                    }
                    break;
                case 'ArrowRight':
                case 'ArrowDown':
                    if (isVertical.value === event.key.endsWith('Down')) {
                        if (isReversed.value) {
                            nav.prev(true);
                        }
                        else {
                            nav.next(true);
                        }
                    }
                    break;
            }
        }, 200);
        const handleFocus = () => {
            document.addEventListener('keydown', handleArrowKeys);
        };
        const handleBlur = () => {
            document.removeEventListener('keydown', handleArrowKeys);
        };
        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 = 'touches' in event ? event.touches[0].clientX : event.clientX;
            startPosition.y = 'touches' in event ? 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 = 'touches' in event ? event.touches[0].clientX : event.clientX;
            const currentY = 'touches' in event ? 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() {
            handleDragging.cancel();
            // Determine the active axis and direction multiplier
            const dragAxis = isVertical.value ? 'y' : 'x';
            const directionMultiplier = isReversed.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 stopAutoplay() {
            if (autoplayTimer) {
                clearInterval(autoplayTimer);
                autoplayTimer = null;
            }
        }
        function resetAutoplay() {
            stopAutoplay();
            initAutoplay();
        }
        /**
         * Navigation function
         */
        const isSliding = ref(false);
        function slideTo(slideIndex, skipTransition = false) {
            const currentVal = config.wrapAround
                ? slideIndex
                : getNumberInRange({
                    val: slideIndex,
                    max: maxSlideIndex.value,
                    min: minSlideIndex.value,
                });
            if (currentSlideIndex.value === currentVal ||
                (!skipTransition && isSliding.value)) {
                return;
            }
            emit('slide-start', {
                slidingToIndex: slideIndex,
                currentSlideIndex: currentSlideIndex.value,
                prevSlideIndex: prevSlideIndex.value,
                slidesCount: slidesCount.value,
            });
            stopAutoplay();
            isSliding.value = true;
            prevSlideIndex.value = currentSlideIndex.value;
            const mappedNumber = config.wrapAround
                ? mapNumberToRange({
                    val: currentVal,
                    max: maxSlideIndex.value,
                    min: 0,
                })
                : currentVal;
            currentSlideIndex.value = currentVal;
            if (mappedNumber !== currentVal) {
                modelWatcher.pause();
            }
            emit('update:modelValue', mappedNumber);
            transitionTimer = setTimeout(() => {
                if (config.wrapAround) {
                    if (mappedNumber !== currentVal) {
                        modelWatcher.resume();
                        currentSlideIndex.value = mappedNumber;
                        emit('loop', {
                            currentSlideIndex: currentSlideIndex.value,
                            slidingToIndex: slideIndex,
                        });
                    }
                }
                emit('slide-end', {
                    currentSlideIndex: currentSlideIndex.value,
                    prevSlideIndex: prevSlideIndex.value,
                    slidesCount: slidesCount.value,
                });
                isSliding.value = false;
                resetAutoplay();
            }, config.transition);
        }
        function next(skipTransition = false) {
            slideTo(currentSlideIndex.value + config.itemsToScroll, skipTransition);
        }
        function prev(skipTransition = false) {
            slideTo(currentSlideIndex.value - config.itemsToScroll, skipTransition);
        }
        const nav = { slideTo, next, prev };
        const scrolledIndex = computed(() => getScrolledIndex({
            config,
            currentSlide: currentSlideIndex.value,
            slidesCount: slidesCount.value,
        }));
        const provided = reactive({
            config,
            slidesCount,
            viewport,
            slides,
            clonedSlidesCount,
            scrolledIndex,
            currentSlide: currentSlideIndex,
            maxSlide: maxSlideIndex,
            minSlide: minSlideIndex,
            slideSize,
            isVertical,
            normalizedDir,
            nav,
            isSliding,
            registerSlide,
            unregisterSlide,
        });
        provide(injectCarousel, provided);
        /** @deprecated provides */
        provide('config', config);
        provide('slidesCount', slidesCount);
        provide('currentSlide', currentSlideIndex);
        provide('maxSlide', maxSlideIndex);
        provide('minSlide', minSlideIndex);
        provide('slideSize', slideSize);
        provide('isVertical', isVertical);
        provide('normalizeDir', normalizedDir);
        provide('nav', nav);
        provide('isSliding', isSliding);
        function restartCarousel() {
            updateBreakpointsConfig();
            updateSlidesData();
            updateSlideSize();
            resetAutoplay();
        }
        // Update the carousel on props change
        watch(() => [fallbackConfig.value, props.breakpoints], () => updateBreakpointsConfig(), { deep: true });
        watch(() => props.autoplay, () => resetAutoplay());
        // Handle changing v-model value
        const modelWatcher = watch(() => props.modelValue, (val) => {
            if (val === currentSlideIndex.value) {
                return;
            }
            slideTo(Number(val), true);
        });
        // Init carousel
        emit('before-init');
        const data = reactive({
            config,
            slidesCount,
            slideSize,
            currentSlide: currentSlideIndex,
            maxSlide: maxSlideIndex,
            minSlide: minSlideIndex,
            middleSlide: middleSlideIndex,
        });
        expose({
            updateBreakpointsConfig,
            updateSlidesData,
            updateSlideSize,
            restartCarousel,
            slideTo,
            next,
            prev,
            nav,
            data,
        });
        const trackHeight = computed(() => {
            if (isVertical.value && slideSize.value && config.height === 'auto') {
                return `${slideSize.value * config.itemsToShow + totalGap.value}px`;
            }
            return config.height !== 'auto'
                ? typeof config.height === 'number' ||
                    parseInt(config.height).toString() === config.height
                    ? `${config.height}px`
                    : config.height
                : undefined;
        });
        /**
         * Track style
         */
        const trackTransform = computed(() => {
            // Calculate the scrolled index with wrapping offset if applicable
            const cloneOffset = config.wrapAround && mounted.value ? clonedSlidesCount.value : 0;
            // Determine direction multiplier for orientation
            const directionMultiplier = isReversed.value ? -1 : 1;
            // Calculate the total offset for slide transformation
            const totalOffset = (scrolledIndex.value + 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)`;
        });
        return () => {
            const slotSlides = slots.default || slots.slides;
            const slotAddons = slots.addons;
            let output = (slotSlides === null || slotSlides === void 0 ? void 0 : slotSlides(data)) || [];
            if (!config.enabled || !output.length) {
                return h('section', {
                    ref: root,
                    class: ['carousel', 'is-disabled'],
                }, output);
            }
            const addonsElements = (slotAddons === null || slotAddons === void 0 ? void 0 : slotAddons(data)) || [];
            if (config.wrapAround) {
                const toShow = clonedSlidesCount.value;
                const slidesBefore = createCloneSlides({ slides, position: 'before', toShow });
                const slidesAfter = createCloneSlides({ slides, position: 'after', toShow });
                output = [...slidesBefore, ...output, ...slidesAfter];
            }
            const trackEl = h('ol', {
                class: 'carousel__track',
                style: {
                    transform: trackTransform.value,
                    'transition-duration': isSliding.value ? `${config.transition}ms` : undefined,
                    gap: config.gap > 0 ? `${config.gap}px` : undefined,
                },
                onMousedownCapture: config.mouseDrag ? handleDragStart : null,
                onTouchstartPassiveCapture: config.touchDrag ? handleDragStart : null,
            }, output);
            const viewPortEl = h('div', { class: 'carousel__viewport', ref: viewport }, trackEl);
            return h('section', {
                ref: root,
                class: [
                    'carousel',
                    `is-${normalizedDir.value}`,
                    {
                        'is-vertical': isVertical.value,
                        'is-sliding': isSliding.value,
                        'is-dragging': isDragging.value,
                        'is-hover': isHover.value,
                    },
                ],
                style: {
                    '--vc-trk-height': trackHeight.value,
                },
                dir: normalizedDir.value,
                'aria-label': config.i18n['ariaGallery'],
                tabindex: '0',
                onFocus: handleFocus,
                onBlur: handleBlur,
                onMouseenter: handleMouseEnter,
                onMouseleave: handleMouseLeave,
            }, [viewPortEl, addonsElements, h(ARIA)]);
        };
    },
});

var IconName;
(function (IconName) {
    IconName["arrowUp"] = "arrowUp";
    IconName["arrowDown"] = "arrowDown";
    IconName["arrowRight"] = "arrowRight";
    IconName["arrowLeft"] = "arrowLeft";
})(IconName || (IconName = {}));

function isIconName(candidate) {
    return candidate in IconName;
}
const iconI18n = (name) => `icon${name.charAt(0).toUpperCase() + name.slice(1)}`;
const validateIconName = (value) => {
    return value && isIconName(value);
};
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',
};
const Icon = defineComponent({
    props: {
        name: {
            type: String,
            required: true,
            validator: validateIconName,
        },
        title: {
            type: String,
            default: (props) => props.name ? DEFAULT_CONFIG.i18n[iconI18n(props.name)] : '',
        },
    },
    setup(props) {
        const carousel = inject(injectCarousel, null);
        return () => {
            const iconName = props.name;
            if (!iconName || !validateIconName(iconName))
                return;
            const path = icons[iconName];
            const pathEl = h('path', { d: path });
            const iconTitle = (carousel === null || carousel === void 0 ? void 0 : carousel.config.i18n[iconI18n(iconName)]) || props.title;
            const titleEl = h('title', iconTitle);
            return h('svg', {
                class: 'carousel__icon',
                viewBox: '0 0 24 24',
                role: 'img',
                'aria-label': iconTitle,
            }, [titleEl, pathEl]);
        };
    },
});

const Navigation = defineComponent({
    name: 'CarouselNavigation',
    inheritAttrs: false,
    setup(props, { slots, attrs }) {
        const carousel = inject(injectCarousel);
        if (!carousel) {
            return () => ''; // Don't render, let vue warn about the missing provide
        }
        const { next: slotNext, prev: slotPrev } = slots;
        const getPrevIcon = () => {
            const directionIcons = {
                ltr: 'arrowLeft',
                rtl: 'arrowRight',
                ttb: 'arrowUp',
                btt: 'arrowDown',
            };
            return directionIcons[carousel.normalizedDir];
        };
        const getNextIcon = () => {
            const directionIcons = {
                ltr: 'arrowRight',
                rtl: 'arrowLeft',
                ttb: 'arrowDown',
                btt: 'arrowUp',
            };
            return directionIcons[carousel.normalizedDir];
        };
        const prevDisabled = computed(() => !carousel.config.wrapAround && carousel.currentSlide <= carousel.minSlide);
        const nextDisabled = computed(() => !carousel.config.wrapAround && carousel.currentSlide >= carousel.maxSlide);
        return () => {
            const { i18n } = carousel.config;
            const prevButton = h('button', Object.assign(Object.assign({ type: 'button', disabled: prevDisabled.value, 'aria-label': i18n['ariaPreviousSlide'], title: i18n['ariaPreviousSlide'], onClick: carousel.nav.prev }, attrs), { class: [
                    'carousel__prev',
                    { 'carousel__prev--disabled': prevDisabled.value },
                    attrs.class,
                ] }), (slotPrev === null || slotPrev === void 0 ? void 0 : slotPrev()) || h(Icon, { name: getPrevIcon() }));
            const nextButton = h('button', Object.assign(Object.assign({ type: 'button', disabled: nextDisabled.value, 'aria-label': i18n['ariaNextSlide'], title: i18n['ariaNextSlide'], onClick: carousel.nav.next }, attrs), { class: [
                    'carousel__next',
                    { 'carousel__next--disabled': nextDisabled.value },
                    attrs.class,
                ] }), (slotNext === null || slotNext === void 0 ? void 0 : slotNext()) || h(Icon, { name: getNextIcon() }));
            return [prevButton, nextButton];
        };
    },
});

const Pagination = defineComponent({
    name: 'CarouselPagination',
    props: {
        disableOnClick: {
            type: Boolean,
        },
        paginateByItemsToShow: {
            type: Boolean
        },
    },
    setup(props) {
        const carousel = inject(injectCarousel);
        if (!carousel) {
            return () => ''; // Don't render, let vue warn about the missing provide
        }
        const offset = computed(() => calculateOffset(carousel.config.snapAlign, carousel.config.itemsToShow));
        const isPaginated = computed(() => props.paginateByItemsToShow && carousel.config.itemsToShow > 1);
        const currentPage = computed(() => Math.ceil((carousel.currentSlide - offset.value) / carousel.config.itemsToShow));
        const pageCount = computed(() => Math.ceil(carousel.slidesCount / carousel.config.itemsToShow));
        const isActive = (slide) => mapNumberToRange(isPaginated.value
            ? {
                val: currentPage.value,
                max: pageCount.value - 1,
                min: 0,
            }
            : {
                val: carousel.currentSlide,
                max: carousel.maxSlide,
                min: carousel.minSlide,
            }) === slide;
        return () => {
            var _a, _b;
            const children = [];
            for (let slide = isPaginated.value ? 0 : carousel.minSlide; slide <= (isPaginated.value ? pageCount.value - 1 : carousel.maxSlide); slide++) {
                const buttonLabel = i18nFormatter(carousel.config.i18n[isPaginated.value ? 'ariaNavigateToPage' : 'ariaNavigateToSlide'], {
                    slideNumber: slide + 1,
                });
                const active = isActive(slide);
                const button = h('button', {
                    type: 'button',
                    class: {
                        'carousel__pagination-button': true,
                        'carousel__pagination-button--active': active,
                    },
                    'aria-label': buttonLabel,
                    'aria-pressed': active,
                    'aria-controls': (_b = (_a = carousel.slides[slide]) === null || _a === void 0 ? void 0 : _a.exposed) === null || _b === void 0 ? void 0 : _b.id,
                    title: buttonLabel,
                    disabled: props.disableOnClick,
                    onClick: () => carousel.nav.slideTo(isPaginated.value ? slide * carousel.config.itemsToShow + offset.value : slide),
                });
                const item = h('li', { class: 'carousel__pagination-item', key: slide }, button);
                children.push(item);
            }
            return h('ol', { class: 'carousel__pagination' }, children);
        };
    },
});

const Slide = defineComponent({
    name: 'CarouselSlide',
    props: {
        isClone: {
            type: Boolean,
            default: false,
        },
        id: {
            type: String,
            default: (props) => (props.isClone ? undefined : useId()),
        },
        index: {
            type: Number,
            default: 0,
        },
    },
    setup(props, { slots, expose }) {
        const carousel = inject(injectCarousel);
        provide(injectCarousel, undefined); // Don't provide for nested slides
        if (!carousel) {
            return () => ''; // Don't render, let vue warn about the missing provide
        }
        const index = ref(props.index);
        watch(() => props.index, (i) => index.value = i);
        const isActive = computed(() => index.value === carousel.currentSlide);
        const isPrev = computed(() => index.value === carousel.currentSlide - 1);
        const isNext = computed(() => index.value === carousel.currentSlide + 1);
        const isVisible = computed(() => index.value >= Math.floor(carousel.scrolledIndex) &&
            index.value < Math.ceil(carousel.scrolledIndex) + carousel.config.itemsToShow);
        const slideStyle = computed(() => {
            const dimension = carousel.config.gap > 0 && carousel.config.itemsToShow > 1
                ? `calc(${100 / carousel.config.itemsToShow}% - ${(carousel.config.gap * (carousel.config.itemsToShow - 1)) /
                    carousel.config.itemsToShow}px)`
                : `${100 / carousel.config.itemsToShow}%`;
            return carousel.isVertical ? { height: dimension } : { width: dimension };
        });
        const instance = getCurrentInstance();
        if (!props.isClone) {
            carousel.registerSlide(instance, (resolvedIndex) => (index.value = resolvedIndex));
            onUnmounted(() => {
                carousel.unregisterSlide(instance);
            });
        }
        else {
            const makeUnfocusable = (node) => {
                [
                    ...((node === null || node === void 0 ? void 0 : node.el)
                        ? node.el.querySelectorAll('a[href], button, input, textarea, select, details, [tabindex]:not([tabindex="-1"])')
                        : []),
                ]
                    .filter((el) => !el.hasAttribute('disabled') && !el.getAttribute('aria-hidden'))
                    .forEach((el) => el.setAttribute('tabindex', '-1'));
            };
            // Prevent cloned slides from being focusable
            onMounted(() => {
                makeUnfocusable(instance.vnode);
            });
            onUpdated(() => {
                makeUnfocusable(instance.vnode);
            });
        }
        expose({
            id: props.id,
        });
        return () => {
            var _a, _b;
            if (!carousel.config.enabled) {
                return (_a = slots.default) === null || _a === void 0 ? void 0 : _a.call(slots);
            }
            return 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': carousel.isSliding,
                },
                onFocusin: () => {
                    // Prevent the viewport being scrolled by the focus
                    if (carousel.viewport) {
                        carousel.viewport.scrollLeft = 0;
                    }
                    carousel.nav.slideTo(index.value);
                },
                id: props.isClone ? undefined : props.id,
                'aria-hidden': props.isClone || undefined,
            }, (_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: carousel.isSliding,
                isVisible: isVisible.value,
            }));
        };
    },
});

export { BREAKPOINT_MODE_OPTIONS, Carousel, DEFAULT_CONFIG, DIR_MAP, DIR_OPTIONS, I18N_DEFAULT_CONFIG, Icon, NORMALIZED_DIR_OPTIONS, Navigation, Pagination, SNAP_ALIGN_OPTIONS, Slide, icons, injectCarousel };
