From 83b3462635d05fffed6de1b06e39ccbbf634556b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armin=20Ov=C4=8Dina?= Date: Fri, 3 Jan 2025 19:23:30 +0100 Subject: [PATCH 1/9] feat: add preventExcessiveDragging option to limit boundary slide gestures refs https://github.com/ismail9k/vue3-carousel/issues/307 --- docs/config.md | 42 +++++++++++++----------- src/components/Carousel/Carousel.ts | 18 ++++++++-- src/components/Carousel/carouselProps.ts | 4 +++ src/shared/constants.ts | 1 + src/shared/types.ts | 1 + 5 files changed, 43 insertions(+), 23 deletions(-) diff --git a/docs/config.md b/docs/config.md index e03a596..5af54d8 100644 --- a/docs/config.md +++ b/docs/config.md @@ -2,26 +2,28 @@ ## Available Props -| Prop | Default | Description | -| ---------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `enabled` | true | Controlled weather the carousel is enabled or disabled. | -| `itemsToShow` | 1 | Count of items to showed per view (can be a fraction). Must be between 1 and the total number of slides. If set to a value less than 1, it defaults to 1. If set to a value greater than the total number of slides, it defaults to the total number of slides. | -| `itemsToScroll` | 1 | Number of slides to be scrolled | -| `wrapAround` | false | Enable infinite scrolling mode. | -| `snapAlign` | 'center' | Controls the carousel position alignment, can be 'start', 'end', 'center-[odd\|even]' | -| `transition` | 300 | Sliding transition time in ms. | -| `autoplay` | 0 | Auto play time in ms. | -| `breakpointMode` | 'viewport' | Determines how the carousel breakpoints are calculated. acceptable values: 'viewport', 'carousel' | -| `breakpoints` | null | An object to pass all the breakpoints settings. | -| `modelValue` | 0 | Index number of the initial slide. | -| `mouseDrag` | true | Toggle mouse dragging | -| `touchDrag` | true | Toggle pointer touch dragging | -| `pauseAutoplayOnHover` | false | Toggle if auto play should pause on mouse hover | -| `dir` | 'ltr' | Controls the carousel direction. Available values: 'ltr', 'rtl', 'ttb', 'btt' or use verbose 'left-to-right', 'right-to-left', 'top-to-bottom', 'bottom-to-top' | -| `i18n` | [`{ ariaNextSlide: ...}`](#i18n) | Used to translate and/or change aria labels and additional texts used in the carousel. | -| `gap` | 0 | Used to add gap between the slides. | -| `height` | 'auto' | Carousel track height. | -| `ignoreAnimations` | false | List of animation names to ignore for size calculations. Can be a boolean, string, or array of strings. | +| Prop | Default | Description | +|----------------------------|----------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `enabled` | true | Controlled weather the carousel is enabled or disabled. | +| `itemsToShow` | 1 | Count of items to showed per view (can be a fraction). Must be between 1 and the total number of slides. If set to a value less than 1, it defaults to 1. If set to a value greater than the total number of slides, it defaults to the total number of slides. | +| `itemsToScroll` | 1 | Number of slides to be scrolled | +| `wrapAround` | false | Enable infinite scrolling mode. | +| `snapAlign` | 'center' | Controls the carousel position alignment, can be 'start', 'end', 'center-[odd\|even]' | +| `transition` | 300 | Sliding transition time in ms. | +| `autoplay` | 0 | Auto play time in ms. | +| `breakpointMode` | 'viewport' | Determines how the carousel breakpoints are calculated. acceptable values: 'viewport', 'carousel' | +| `breakpoints` | null | An object to pass all the breakpoints settings. | +| `modelValue` | 0 | Index number of the initial slide. | +| `mouseDrag` | true | Toggle mouse dragging | +| `touchDrag` | true | Toggle pointer touch dragging | +| `pauseAutoplayOnHover` | false | Toggle if auto play should pause on mouse hover | +| `dir` | 'ltr' | Controls the carousel direction. Available values: 'ltr', 'rtl', 'ttb', 'btt' or use verbose 'left-to-right', 'right-to-left', 'top-to-bottom', 'bottom-to-top' | +| `i18n` | [`{ ariaNextSlide: ...}`](#i18n) | Used to translate and/or change aria labels and additional texts used in the carousel. | +| `gap` | 0 | Used to add gap between the slides. | +| `height` | 'auto' | Carousel track height. | +| `ignoreAnimations` | false | List of animation names to ignore for size calculations. Can be a boolean, string, or array of strings. | +| `preventExcessiveDragging` | false | Prevents unwanted dragging behavior when the carousel reaches its first or last slide. | + ## Slots diff --git a/src/components/Carousel/Carousel.ts b/src/components/Carousel/Carousel.ts index 4d4dbe5..cc12b45 100644 --- a/src/components/Carousel/Carousel.ts +++ b/src/components/Carousel/Carousel.ts @@ -374,7 +374,7 @@ export const Carousel = defineComponent({ // 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 + startPosition.y = 'touches' in event ? event.touches[0].clientY : event.clientYg // Attach event listeners for dragging and drag end @@ -391,9 +391,21 @@ export const Carousel = defineComponent({ const currentX = 'touches' in event ? event.touches[0].clientX : event.clientX const currentY = 'touches' in event ? event.touches[0].clientY : event.clientY + const tmpDraggedX = currentX - startPosition.x + const tmpDraggedY = currentY - startPosition.y + + if (!config.wrapAround && config.preventExcessiveDragging) { + const isAtMinIndex = activeSlideIndex.value === minSlideIndex.value; + const isAtMaxIndex = activeSlideIndex.value === maxSlideIndex.value; + + if ((Math.abs(tmpDraggedX) > Math.abs(tmpDraggedY) ? + (tmpDraggedX > 0 && isAtMinIndex) || (tmpDraggedX < 0 && isAtMaxIndex) : + (tmpDraggedY > 0 && isAtMinIndex) || (tmpDraggedY < 0 && isAtMaxIndex))) return; + } + // Calculate deltas for X and Y axes - dragged.x = currentX - startPosition.x - dragged.y = currentY - startPosition.y + dragged.x = tmpDraggedX + dragged.y = tmpDraggedY const draggedSlides = getDraggedSlidesCount({ isVertical: isVertical.value, diff --git a/src/components/Carousel/carouselProps.ts b/src/components/Carousel/carouselProps.ts index 72b21ac..337a91c 100644 --- a/src/components/Carousel/carouselProps.ts +++ b/src/components/Carousel/carouselProps.ts @@ -121,4 +121,8 @@ export const carouselProps = { return SLIDE_EFFECTS.includes(value) }, }, + preventExcessiveDragging: { + default: false, + type: Boolean + } } diff --git a/src/shared/constants.ts b/src/shared/constants.ts index a44e441..f6870fc 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -61,4 +61,5 @@ export const DEFAULT_CONFIG: CarouselConfig = { i18n: I18N_DEFAULT_CONFIG, ignoreAnimations: false, slideEffect: SLIDE_EFFECTS[0], + preventExcessiveDragging: false } diff --git a/src/shared/types.ts b/src/shared/types.ts index d207100..9fd39da 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -47,6 +47,7 @@ export interface CarouselConfig { i18n: { [key in I18nKeys]?: string } ignoreAnimations: boolean | string[] | string slideEffect: SlideEffect + preventExcessiveDragging: boolean } export type VueClass = string | Record | VueClass[] From f6ca6955cff95625812667de6ff398ae1ac8f98a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armin=20Ov=C4=8Dina?= Date: Fri, 3 Jan 2025 19:25:21 +0100 Subject: [PATCH 2/9] fix: typo --- src/components/Carousel/Carousel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Carousel/Carousel.ts b/src/components/Carousel/Carousel.ts index cc12b45..0ef9f16 100644 --- a/src/components/Carousel/Carousel.ts +++ b/src/components/Carousel/Carousel.ts @@ -374,7 +374,7 @@ export const Carousel = defineComponent({ // 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.clientYg + startPosition.y = 'touches' in event ? event.touches[0].clientY : event.clientY // Attach event listeners for dragging and drag end From b9ae2e582945a29b98199d1c2f01dbca91496fd6 Mon Sep 17 00:00:00 2001 From: Ismail9k Date: Sat, 4 Jan 2025 14:13:16 +0300 Subject: [PATCH 3/9] feat: enhance dragging behavior in Carousel component to prevent excessive sliding --- src/components/Carousel/Carousel.ts | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/components/Carousel/Carousel.ts b/src/components/Carousel/Carousel.ts index 0ef9f16..be9cae4 100644 --- a/src/components/Carousel/Carousel.ts +++ b/src/components/Carousel/Carousel.ts @@ -391,21 +391,9 @@ export const Carousel = defineComponent({ const currentX = 'touches' in event ? event.touches[0].clientX : event.clientX const currentY = 'touches' in event ? event.touches[0].clientY : event.clientY - const tmpDraggedX = currentX - startPosition.x - const tmpDraggedY = currentY - startPosition.y - - if (!config.wrapAround && config.preventExcessiveDragging) { - const isAtMinIndex = activeSlideIndex.value === minSlideIndex.value; - const isAtMaxIndex = activeSlideIndex.value === maxSlideIndex.value; - - if ((Math.abs(tmpDraggedX) > Math.abs(tmpDraggedY) ? - (tmpDraggedX > 0 && isAtMinIndex) || (tmpDraggedX < 0 && isAtMaxIndex) : - (tmpDraggedY > 0 && isAtMinIndex) || (tmpDraggedY < 0 && isAtMaxIndex))) return; - } - // Calculate deltas for X and Y axes - dragged.x = tmpDraggedX - dragged.y = tmpDraggedY + dragged.x = currentX - startPosition.x + dragged.y = currentY - startPosition.y const draggedSlides = getDraggedSlidesCount({ isVertical: isVertical.value, @@ -701,7 +689,18 @@ export const Carousel = defineComponent({ // Include user drag interaction offset const dragOffset = isVertical.value ? dragged.y : dragged.x - const totalOffset = scrolledOffset + dragOffset + let totalOffset = scrolledOffset + dragOffset + + if (!config.wrapAround && config.preventExcessiveDragging) { + const maxSlidingValue = + (slidesCount.value - config.itemsToShow) * effectiveSlideSize.value + + totalOffset = getNumberInRange({ + val: totalOffset, + min: -1 * maxSlidingValue, + max: 0, + }) + } return `translate${translateAxis}(${totalOffset}px)` }) From a33bc86202a71d24b2c844f3295d1ab8f3d962f1 Mon Sep 17 00:00:00 2001 From: Ismail9k Date: Sat, 4 Jan 2025 14:22:58 +0300 Subject: [PATCH 4/9] feat: adjust min and max sliding values in Carousel to improve boundary handling --- src/components/Carousel/Carousel.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/Carousel/Carousel.ts b/src/components/Carousel/Carousel.ts index be9cae4..3512e97 100644 --- a/src/components/Carousel/Carousel.ts +++ b/src/components/Carousel/Carousel.ts @@ -694,11 +694,12 @@ export const Carousel = defineComponent({ if (!config.wrapAround && config.preventExcessiveDragging) { const maxSlidingValue = (slidesCount.value - config.itemsToShow) * effectiveSlideSize.value - + const min = isReversed.value ? 0 : -1 * maxSlidingValue + const max = isReversed.value ? maxSlidingValue : 0 totalOffset = getNumberInRange({ val: totalOffset, - min: -1 * maxSlidingValue, - max: 0, + min, + max, }) } From c4b4546600c93b473de700fb8168d5f883ad5ae9 Mon Sep 17 00:00:00 2001 From: Ismail9k Date: Sat, 4 Jan 2025 14:24:15 +0300 Subject: [PATCH 5/9] docs: lint config file --- docs/config.md | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/docs/config.md b/docs/config.md index 5af54d8..3796afc 100644 --- a/docs/config.md +++ b/docs/config.md @@ -2,27 +2,27 @@ ## Available Props -| Prop | Default | Description | -|----------------------------|----------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `enabled` | true | Controlled weather the carousel is enabled or disabled. | +| Prop | Default | Description | +| -------------------------- | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `enabled` | true | Controlled weather the carousel is enabled or disabled. | | `itemsToShow` | 1 | Count of items to showed per view (can be a fraction). Must be between 1 and the total number of slides. If set to a value less than 1, it defaults to 1. If set to a value greater than the total number of slides, it defaults to the total number of slides. | -| `itemsToScroll` | 1 | Number of slides to be scrolled | -| `wrapAround` | false | Enable infinite scrolling mode. | -| `snapAlign` | 'center' | Controls the carousel position alignment, can be 'start', 'end', 'center-[odd\|even]' | -| `transition` | 300 | Sliding transition time in ms. | -| `autoplay` | 0 | Auto play time in ms. | -| `breakpointMode` | 'viewport' | Determines how the carousel breakpoints are calculated. acceptable values: 'viewport', 'carousel' | -| `breakpoints` | null | An object to pass all the breakpoints settings. | -| `modelValue` | 0 | Index number of the initial slide. | -| `mouseDrag` | true | Toggle mouse dragging | -| `touchDrag` | true | Toggle pointer touch dragging | -| `pauseAutoplayOnHover` | false | Toggle if auto play should pause on mouse hover | -| `dir` | 'ltr' | Controls the carousel direction. Available values: 'ltr', 'rtl', 'ttb', 'btt' or use verbose 'left-to-right', 'right-to-left', 'top-to-bottom', 'bottom-to-top' | -| `i18n` | [`{ ariaNextSlide: ...}`](#i18n) | Used to translate and/or change aria labels and additional texts used in the carousel. | -| `gap` | 0 | Used to add gap between the slides. | -| `height` | 'auto' | Carousel track height. | -| `ignoreAnimations` | false | List of animation names to ignore for size calculations. Can be a boolean, string, or array of strings. | -| `preventExcessiveDragging` | false | Prevents unwanted dragging behavior when the carousel reaches its first or last slide. | +| `itemsToScroll` | 1 | Number of slides to be scrolled | +| `wrapAround` | false | Enable infinite scrolling mode. | +| `snapAlign` | 'center' | Controls the carousel position alignment, can be 'start', 'end', 'center-[odd\|even]' | +| `transition` | 300 | Sliding transition time in ms. | +| `autoplay` | 0 | Auto play time in ms. | +| `breakpointMode` | 'viewport' | Determines how the carousel breakpoints are calculated. acceptable values: 'viewport', 'carousel' | +| `breakpoints` | null | An object to pass all the breakpoints settings. | +| `modelValue` | 0 | Index number of the initial slide. | +| `mouseDrag` | true | Toggle mouse dragging | +| `touchDrag` | true | Toggle pointer touch dragging | +| `pauseAutoplayOnHover` | false | Toggle if auto play should pause on mouse hover | +| `dir` | 'ltr' | Controls the carousel direction. Available values: 'ltr', 'rtl', 'ttb', 'btt' or use verbose 'left-to-right', 'right-to-left', 'top-to-bottom', 'bottom-to-top' | +| `i18n` | [`{ ariaNextSlide: ...}`](#i18n) | Used to translate and/or change aria labels and additional texts used in the carousel. | +| `gap` | 0 | Used to add gap between the slides. | +| `height` | 'auto' | Carousel track height. | +| `ignoreAnimations` | false | List of animation names to ignore for size calculations. Can be a boolean, string, or array of strings. | +| `preventExcessiveDragging` | false | Prevents unwanted dragging behavior when the carousel reaches its first or last slide. | From 9e52864b102ea06f8be4bc31f29995d7fae54a8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armin=20Ov=C4=8Dina?= Date: Sat, 4 Jan 2025 21:36:12 +0100 Subject: [PATCH 6/9] feat: validate `preventExcessiveDragging` carousel prop and log warning --- src/components/Carousel/carouselProps.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/components/Carousel/carouselProps.ts b/src/components/Carousel/carouselProps.ts index 337a91c..347469a 100644 --- a/src/components/Carousel/carouselProps.ts +++ b/src/components/Carousel/carouselProps.ts @@ -123,6 +123,15 @@ export const carouselProps = { }, preventExcessiveDragging: { default: false, - type: Boolean + type: Boolean, + validator(value: boolean, props) { + if (value && props.wrapAround) + console.warn( + '[vue3-carousel warn]: preventExcessiveDragging cannot be used with wrapAround. ' + + 'The preventExcessiveDragging setting will be ignored.' + ); + + return true; + } } } From 42d58e892415e4e91f3516db2679918e1d3670d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armin=20Ov=C4=8Dina?= Date: Sat, 4 Jan 2025 21:36:47 +0100 Subject: [PATCH 7/9] docs: add upcoming version badge for `preventExcessiveDragging` --- docs/config.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/config.md b/docs/config.md index 3796afc..1a859ce 100644 --- a/docs/config.md +++ b/docs/config.md @@ -3,7 +3,7 @@ ## Available Props | Prop | Default | Description | -| -------------------------- | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| -------------------------- | -------------------------------- |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `enabled` | true | Controlled weather the carousel is enabled or disabled. | | `itemsToShow` | 1 | Count of items to showed per view (can be a fraction). Must be between 1 and the total number of slides. If set to a value less than 1, it defaults to 1. If set to a value greater than the total number of slides, it defaults to the total number of slides. | | `itemsToScroll` | 1 | Number of slides to be scrolled | @@ -22,7 +22,7 @@ | `gap` | 0 | Used to add gap between the slides. | | `height` | 'auto' | Carousel track height. | | `ignoreAnimations` | false | List of animation names to ignore for size calculations. Can be a boolean, string, or array of strings. | -| `preventExcessiveDragging` | false | Prevents unwanted dragging behavior when the carousel reaches its first or last slide. | +| `preventExcessiveDragging` | false | Prevents unwanted dragging behavior when the carousel reaches its first or last slide. | From c1c5cec1cc376a14068aa3f46b17c85fa35ebc2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armin=20Ov=C4=8Dina?= Date: Sat, 4 Jan 2025 21:49:13 +0100 Subject: [PATCH 8/9] style: fix implict any of props in validator --- src/components/Carousel/carouselProps.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Carousel/carouselProps.ts b/src/components/Carousel/carouselProps.ts index 347469a..e8d9858 100644 --- a/src/components/Carousel/carouselProps.ts +++ b/src/components/Carousel/carouselProps.ts @@ -124,7 +124,7 @@ export const carouselProps = { preventExcessiveDragging: { default: false, type: Boolean, - validator(value: boolean, props) { + validator(value: boolean, props: CarouselConfig) { if (value && props.wrapAround) console.warn( '[vue3-carousel warn]: preventExcessiveDragging cannot be used with wrapAround. ' + From b49e08ff93cb0a36b2b7b7bb3109431a94b37855 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armin=20Ov=C4=8Dina?= Date: Sun, 5 Jan 2025 10:46:48 +0100 Subject: [PATCH 9/9] build: fix failing checks --- src/components/Carousel/carouselProps.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Carousel/carouselProps.ts b/src/components/Carousel/carouselProps.ts index e8d9858..505db3a 100644 --- a/src/components/Carousel/carouselProps.ts +++ b/src/components/Carousel/carouselProps.ts @@ -124,9 +124,9 @@ export const carouselProps = { preventExcessiveDragging: { default: false, type: Boolean, - validator(value: boolean, props: CarouselConfig) { + validator(value: boolean, props: { wrapAround?: boolean }) { if (value && props.wrapAround) - console.warn( + console.warn( /* eslint-disable-line no-console */ '[vue3-carousel warn]: preventExcessiveDragging cannot be used with wrapAround. ' + 'The preventExcessiveDragging setting will be ignored.' );