import { createVNode as _createVNode, mergeProps as _mergeProps } from "vue"; // Styles import "./VPagination.css"; // Components import { VBtn } from "../VBtn/index.mjs"; // Composables import { useDisplay } from "../../composables/index.mjs"; import { makeBorderProps } from "../../composables/border.mjs"; import { makeComponentProps } from "../../composables/component.mjs"; import { provideDefaults } from "../../composables/defaults.mjs"; import { makeDensityProps } from "../../composables/density.mjs"; import { makeElevationProps } from "../../composables/elevation.mjs"; import { IconValue } from "../../composables/icons.mjs"; import { useLocale, useRtl } from "../../composables/locale.mjs"; import { useProxiedModel } from "../../composables/proxiedModel.mjs"; import { useRefs } from "../../composables/refs.mjs"; import { useResizeObserver } from "../../composables/resizeObserver.mjs"; import { makeRoundedProps } from "../../composables/rounded.mjs"; import { makeSizeProps } from "../../composables/size.mjs"; import { makeTagProps } from "../../composables/tag.mjs"; import { makeThemeProps, provideTheme } from "../../composables/theme.mjs"; import { makeVariantProps } from "../../composables/variant.mjs"; // Utilities import { computed, nextTick, shallowRef, toRef } from 'vue'; import { createRange, genericComponent, keyValues, propsFactory, useRender } from "../../util/index.mjs"; // Types export const makeVPaginationProps = propsFactory({ activeColor: String, start: { type: [Number, String], default: 1 }, modelValue: { type: Number, default: props => props.start }, disabled: Boolean, length: { type: [Number, String], default: 1, validator: val => val % 1 === 0 }, totalVisible: [Number, String], firstIcon: { type: IconValue, default: '$first' }, prevIcon: { type: IconValue, default: '$prev' }, nextIcon: { type: IconValue, default: '$next' }, lastIcon: { type: IconValue, default: '$last' }, ariaLabel: { type: String, default: '$vuetify.pagination.ariaLabel.root' }, pageAriaLabel: { type: String, default: '$vuetify.pagination.ariaLabel.page' }, currentPageAriaLabel: { type: String, default: '$vuetify.pagination.ariaLabel.currentPage' }, firstAriaLabel: { type: String, default: '$vuetify.pagination.ariaLabel.first' }, previousAriaLabel: { type: String, default: '$vuetify.pagination.ariaLabel.previous' }, nextAriaLabel: { type: String, default: '$vuetify.pagination.ariaLabel.next' }, lastAriaLabel: { type: String, default: '$vuetify.pagination.ariaLabel.last' }, ellipsis: { type: String, default: '...' }, showFirstLastPage: Boolean, ...makeBorderProps(), ...makeComponentProps(), ...makeDensityProps(), ...makeElevationProps(), ...makeRoundedProps(), ...makeSizeProps(), ...makeTagProps({ tag: 'nav' }), ...makeThemeProps(), ...makeVariantProps({ variant: 'text' }) }, 'VPagination'); export const VPagination = genericComponent()({ name: 'VPagination', props: makeVPaginationProps(), emits: { 'update:modelValue': value => true, first: value => true, prev: value => true, next: value => true, last: value => true }, setup(props, _ref) { let { slots, emit } = _ref; const page = useProxiedModel(props, 'modelValue'); const { t, n } = useLocale(); const { isRtl } = useRtl(); const { themeClasses } = provideTheme(props); const { width } = useDisplay(); const maxButtons = shallowRef(-1); provideDefaults(undefined, { scoped: true }); const { resizeRef } = useResizeObserver(entries => { if (!entries.length) return; const { target, contentRect } = entries[0]; const firstItem = target.querySelector('.v-pagination__list > *'); if (!firstItem) return; const totalWidth = contentRect.width; const itemWidth = firstItem.offsetWidth + parseFloat(getComputedStyle(firstItem).marginRight) * 2; maxButtons.value = getMax(totalWidth, itemWidth); }); const length = computed(() => parseInt(props.length, 10)); const start = computed(() => parseInt(props.start, 10)); const totalVisible = computed(() => { if (props.totalVisible != null) return parseInt(props.totalVisible, 10);else if (maxButtons.value >= 0) return maxButtons.value; return getMax(width.value, 58); }); function getMax(totalWidth, itemWidth) { const minButtons = props.showFirstLastPage ? 5 : 3; return Math.max(0, Math.floor( // Round to two decimal places to avoid floating point errors +((totalWidth - itemWidth * minButtons) / itemWidth).toFixed(2))); } const range = computed(() => { if (length.value <= 0 || isNaN(length.value) || length.value > Number.MAX_SAFE_INTEGER) return []; if (totalVisible.value <= 0) return [];else if (totalVisible.value === 1) return [page.value]; if (length.value <= totalVisible.value) { return createRange(length.value, start.value); } const even = totalVisible.value % 2 === 0; const middle = even ? totalVisible.value / 2 : Math.floor(totalVisible.value / 2); const left = even ? middle : middle + 1; const right = length.value - middle; if (left - page.value >= 0) { return [...createRange(Math.max(1, totalVisible.value - 1), start.value), props.ellipsis, length.value]; } else if (page.value - right >= (even ? 1 : 0)) { const rangeLength = totalVisible.value - 1; const rangeStart = length.value - rangeLength + start.value; return [start.value, props.ellipsis, ...createRange(rangeLength, rangeStart)]; } else { const rangeLength = Math.max(1, totalVisible.value - 3); const rangeStart = rangeLength === 1 ? page.value : page.value - Math.ceil(rangeLength / 2) + start.value; return [start.value, props.ellipsis, ...createRange(rangeLength, rangeStart), props.ellipsis, length.value]; } }); // TODO: 'first' | 'prev' | 'next' | 'last' does not work here? function setValue(e, value, event) { e.preventDefault(); page.value = value; event && emit(event, value); } const { refs, updateRef } = useRefs(); provideDefaults({ VPaginationBtn: { color: toRef(props, 'color'), border: toRef(props, 'border'), density: toRef(props, 'density'), size: toRef(props, 'size'), variant: toRef(props, 'variant'), rounded: toRef(props, 'rounded'), elevation: toRef(props, 'elevation') } }); const items = computed(() => { return range.value.map((item, index) => { const ref = e => updateRef(e, index); if (typeof item === 'string') { return { isActive: false, key: `ellipsis-${index}`, page: item, props: { ref, ellipsis: true, icon: true, disabled: true } }; } else { const isActive = item === page.value; return { isActive, key: item, page: n(item), props: { ref, ellipsis: false, icon: true, disabled: !!props.disabled || +props.length < 2, color: isActive ? props.activeColor : props.color, 'aria-current': isActive, 'aria-label': t(isActive ? props.currentPageAriaLabel : props.pageAriaLabel, item), onClick: e => setValue(e, item) } }; } }); }); const controls = computed(() => { const prevDisabled = !!props.disabled || page.value <= start.value; const nextDisabled = !!props.disabled || page.value >= start.value + length.value - 1; return { first: props.showFirstLastPage ? { icon: isRtl.value ? props.lastIcon : props.firstIcon, onClick: e => setValue(e, start.value, 'first'), disabled: prevDisabled, 'aria-label': t(props.firstAriaLabel), 'aria-disabled': prevDisabled } : undefined, prev: { icon: isRtl.value ? props.nextIcon : props.prevIcon, onClick: e => setValue(e, page.value - 1, 'prev'), disabled: prevDisabled, 'aria-label': t(props.previousAriaLabel), 'aria-disabled': prevDisabled }, next: { icon: isRtl.value ? props.prevIcon : props.nextIcon, onClick: e => setValue(e, page.value + 1, 'next'), disabled: nextDisabled, 'aria-label': t(props.nextAriaLabel), 'aria-disabled': nextDisabled }, last: props.showFirstLastPage ? { icon: isRtl.value ? props.firstIcon : props.lastIcon, onClick: e => setValue(e, start.value + length.value - 1, 'last'), disabled: nextDisabled, 'aria-label': t(props.lastAriaLabel), 'aria-disabled': nextDisabled } : undefined }; }); function updateFocus() { const currentIndex = page.value - start.value; refs.value[currentIndex]?.$el.focus(); } function onKeydown(e) { if (e.key === keyValues.left && !props.disabled && page.value > +props.start) { page.value = page.value - 1; nextTick(updateFocus); } else if (e.key === keyValues.right && !props.disabled && page.value < start.value + length.value - 1) { page.value = page.value + 1; nextTick(updateFocus); } } useRender(() => _createVNode(props.tag, { "ref": resizeRef, "class": ['v-pagination', themeClasses.value, props.class], "style": props.style, "role": "navigation", "aria-label": t(props.ariaLabel), "onKeydown": onKeydown, "data-test": "v-pagination-root" }, { default: () => [_createVNode("ul", { "class": "v-pagination__list" }, [props.showFirstLastPage && _createVNode("li", { "key": "first", "class": "v-pagination__first", "data-test": "v-pagination-first" }, [slots.first ? slots.first(controls.value.first) : _createVNode(VBtn, _mergeProps({ "_as": "VPaginationBtn" }, controls.value.first), null)]), _createVNode("li", { "key": "prev", "class": "v-pagination__prev", "data-test": "v-pagination-prev" }, [slots.prev ? slots.prev(controls.value.prev) : _createVNode(VBtn, _mergeProps({ "_as": "VPaginationBtn" }, controls.value.prev), null)]), items.value.map((item, index) => _createVNode("li", { "key": item.key, "class": ['v-pagination__item', { 'v-pagination__item--is-active': item.isActive }], "data-test": "v-pagination-item" }, [slots.item ? slots.item(item) : _createVNode(VBtn, _mergeProps({ "_as": "VPaginationBtn" }, item.props), { default: () => [item.page] })])), _createVNode("li", { "key": "next", "class": "v-pagination__next", "data-test": "v-pagination-next" }, [slots.next ? slots.next(controls.value.next) : _createVNode(VBtn, _mergeProps({ "_as": "VPaginationBtn" }, controls.value.next), null)]), props.showFirstLastPage && _createVNode("li", { "key": "last", "class": "v-pagination__last", "data-test": "v-pagination-last" }, [slots.last ? slots.last(controls.value.last) : _createVNode(VBtn, _mergeProps({ "_as": "VPaginationBtn" }, controls.value.last), null)])])] })); return {}; } }); //# sourceMappingURL=VPagination.mjs.map