diff --git a/packages/docs/src/examples/v-carousel/prop-mousewheel.vue b/packages/docs/src/examples/v-carousel/prop-mousewheel.vue new file mode 100644 index 00000000000..c8bcecf0fbe --- /dev/null +++ b/packages/docs/src/examples/v-carousel/prop-mousewheel.vue @@ -0,0 +1,20 @@ + + + diff --git a/packages/docs/src/pages/en/components/carousels.md b/packages/docs/src/pages/en/components/carousels.md index 58cae914219..d6dc5b2dcf6 100644 --- a/packages/docs/src/pages/en/components/carousels.md +++ b/packages/docs/src/pages/en/components/carousels.md @@ -87,6 +87,12 @@ You can show a linear progress bar with the **progress** prop. It will indicate +#### Mousewheel + +Enable mouse-wheel (and trackpad scroll) navigation with the **mousewheel** prop. Scrolling down or right advances to the next slide; scrolling up or left goes to the previous one. A 300ms cooldown prevents accidental multi-slide jumps per scroll gesture. + + + #### Model You can control carousel with **v-model**. diff --git a/packages/vuetify/src/components/VCarousel/VCarousel.tsx b/packages/vuetify/src/components/VCarousel/VCarousel.tsx index f20401b8c9d..db6ed524f71 100644 --- a/packages/vuetify/src/components/VCarousel/VCarousel.tsx +++ b/packages/vuetify/src/components/VCarousel/VCarousel.tsx @@ -13,8 +13,8 @@ import { useLocale } from '@/composables/locale' import { useProxiedModel } from '@/composables/proxiedModel' // Utilities -import { nextTick, onMounted, ref, watch } from 'vue' -import { convertToUnit, genericComponent, propsFactory, useRender } from '@/util' +import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue' +import { convertToUnit, genericComponent, IN_BROWSER, propsFactory, useRender } from '@/util' // Types import type { PropType } from 'vue' @@ -40,6 +40,7 @@ export const makeVCarouselProps = propsFactory({ default: 6000, validator: (value: string | number) => Number(value) > 0, }, + mousewheel: Boolean, progress: [Boolean, String], verticalDelimiters: [Boolean, String] as PropType, @@ -105,6 +106,41 @@ export const VCarousel = genericComponent( window.requestAnimationFrame(startTimeout) } + let lastWheelAt = 0 + function onWheel (e: WheelEvent) { + e.preventDefault() + const now = Date.now() + if (now - lastWheelAt < 300 || !windowRef.value) return + lastWheelAt = now + if (e.deltaY > 0 || e.deltaX > 0) { + windowRef.value.group.next() + } else { + windowRef.value.group.prev() + } + } + + watch(() => props.mousewheel, val => { + if (!IN_BROWSER || !windowRef.value) return + const el: HTMLElement = windowRef.value.$el + if (val) { + el.addEventListener('wheel', onWheel, { passive: false }) + } else { + el.removeEventListener('wheel', onWheel) + } + }) + + onMounted(() => { + if (props.mousewheel && windowRef.value) { + windowRef.value.$el.addEventListener('wheel', onWheel, { passive: false }) + } + }) + + onBeforeUnmount(() => { + if (windowRef.value) { + windowRef.value.$el.removeEventListener('wheel', onWheel) + } + }) + function onDelimiterKeyDown (e: KeyboardEvent, group: GroupProvide) { if ( (props.direction === 'horizontal' && e.key === 'ArrowLeft') ||