Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix issues after latest updates #440

Merged
merged 9 commits into from
Dec 7, 2024
6 changes: 3 additions & 3 deletions docs/api/methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,15 @@ Update all the slide related date includes:
- `maxSlide`
- `minSlide`

## initDefaultConfig()
## ~~initDefaultConfig()~~ <Badge type="danger" text="This method is deprecated"/>

Init carousel default configurations
~~Init carousel default configurations~~

## restartCarousel()

Restart the carousel settings and data, internally it calls:

- `initDefaultConfig`
- ~~`initDefaultConfig`~~
- `resetAutoplay`
- `updateBreakpointsConfig`
- `updateSlidesData`
Expand Down
68 changes: 43 additions & 25 deletions playground/App.vue
Original file line number Diff line number Diff line change
@@ -1,3 +1,38 @@
<script setup lang="ts">
import '@/theme.css'
import {
Carousel as VueCarousel,
Slide as CarouselSlide,
Pagination as CarouselPagination,
Navigation as CarouselNavigation,
} from '@/index'
import { ref } from 'vue'

import { DIR_MAP, SNAP_ALIGN_OPTIONS } from '@/shared/constants'

const carouselWrapper = ref<HTMLDivElement | null>(null)
const currentSlide = ref(0)
const snapAlign = ref('center')
const itemsToScroll = ref(1)
const itemsToShow = ref(1)
const autoplay = ref()
const wrapAround = ref(true)
const height = ref('200')
const dir = ref('left-to-right')

const handleRestartAnimation = () => {
if (!carouselWrapper.value) return

carouselWrapper.value.classList.remove('pop-in')
void carouselWrapper.value.offsetWidth
carouselWrapper.value.classList.add('pop-in')
}

const handelButtonClick = () => {
alert('Button clicked')
}
</script>

<template>
<div>
<fieldset>
Expand Down Expand Up @@ -26,8 +61,11 @@
/></label>
<label><input type="checkbox" v-model="wrapAround" />Wrap Around</label>
<label>Current slide: <input type="number" v-model="currentSlide" /></label>

<button @click="handleRestartAnimation">Restart animation</button>
</fieldset>
<div class="carousel-wrapper">

<div class="carousel-wrapper pop-in" ref="carouselWrapper">
<VueCarousel
v-model="currentSlide"
:items-to-show="itemsToShow"
Expand All @@ -41,7 +79,9 @@
:snap-align="snapAlign"
>
<CarouselSlide v-for="i in 10" :key="i" v-slot="{ isActive, isClone }">
<div class="carousel__item">{{ i }}<button>This is a button</button></div>
<div class="carousel__item">
{{ i }}<button @click="handelButtonClick">This is a button</button>
</div>
</CarouselSlide>
<template #addons>
<CarouselPagination />
Expand All @@ -52,28 +92,6 @@
</div>
</template>

<script setup lang="ts">
import '@/theme.css'
import {
Carousel as VueCarousel,
Slide as CarouselSlide,
Pagination as CarouselPagination,
Navigation as CarouselNavigation,
} from '@/index'
import { ref } from 'vue'

import { DIR_MAP, SNAP_ALIGN_OPTIONS } from '@/shared/constants'

const currentSlide = ref(0)
const snapAlign = ref('center')
const itemsToScroll = ref(1)
const itemsToShow = ref(1)
const autoplay = ref()
const wrapAround = ref(true)
const height = ref('200')
const dir = ref('left-to-right')
</script>

<style lang="css">
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
Expand Down Expand Up @@ -105,7 +123,7 @@ fieldset {
}
}

.carousel-wrapper {
.pop-in {
animation: pop-in 3s;
}

Expand Down
55 changes: 18 additions & 37 deletions src/components/Carousel/Carousel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
getMinSlideIndex,
mapNumberToRange,
getScrolledIndex,
getTransformValues,
} from '@/utils'

import {
Expand Down Expand Up @@ -117,6 +118,8 @@ export const Carousel = defineComponent({
const isReversed = computed(() => ['rtl', 'btt'].includes(normalizedDir.value))
const isVertical = computed(() => ['ttb', 'btt'].includes(normalizedDir.value))

const clonedSlidesCount = computed(() => config.itemsToShow + 1)

function updateBreakpointsConfig(): void {
// Determine the width source based on the 'breakpointMode' config
const widthSource =
Expand Down Expand Up @@ -146,53 +149,32 @@ export const Carousel = defineComponent({
updateSlideSize()
})

function parseTransform(el: HTMLElement) {
const transform = window.getComputedStyle(el).transform

//add sanity check
return transform
.split(/[(,)]/)
.slice(1, -1)
.map(function (v) {
return parseFloat(v)
})
}

const totalGap = computed(() => (config.itemsToShow - 1) * config.gap)
const transformElements = shallowReactive<Array<HTMLElement>>([])
const transformElements = shallowReactive<Set<HTMLElement>>(new Set())

/**
* Setup functions
*/
function updateSlideSize(): void {
if (!viewport.value) return
let multiplierWidth = 1
let multiplierHeight = 1
transformElements.forEach((el) => {
const transformArr = parseTransform(el)
const transformArr = getTransformValues(el)

if (transformArr.length == 6) {
if (transformArr.length === 6) {
multiplierWidth *= transformArr[0]
multiplierHeight *= transformArr[3]
}
})

// Calculate size based on orientation

if (isVertical.value) {
if (config.height !== 'auto') {
let height
if (typeof config.height === 'string') {
height =
parseInt(config.height).toString() !== config.height
const height =
typeof config.height === 'string' && isNaN(parseInt(config.height))
? viewport.value.getBoundingClientRect().height
: parseInt(config.height)
} else {
height = config.height
}
: parseInt(config.height as string)

slideSize.value =
(height / multiplierHeight - totalGap.value) / config.itemsToShow
slideSize.value = (height - totalGap.value) / config.itemsToShow
}
} else {
const width = viewport.value.getBoundingClientRect().width
Expand Down Expand Up @@ -221,8 +203,8 @@ export const Carousel = defineComponent({

const setAnimationInterval = (event: AnimationEvent | TransitionEvent) => {
const target = event.target as HTMLElement
if (target && !transformElements.includes(target)) {
transformElements.push(target)
if (target) {
transformElements.add(target)
}
if (!animationInterval) {
const stepAnimation = () => {
Expand All @@ -237,12 +219,9 @@ export const Carousel = defineComponent({
const finishAnimation = (event: AnimationEvent | TransitionEvent) => {
const target = event.target as HTMLElement
if (target) {
const found = transformElements.indexOf(target)
if (found >= 0) {
transformElements.splice(found, 1)
}
transformElements.delete(target)
}
if (animationInterval && transformElements.length === 0) {
if (animationInterval && transformElements.size === 0) {
cancelAnimationFrame(animationInterval)
updateSlideSize()
}
Expand Down Expand Up @@ -393,6 +372,8 @@ export const Carousel = defineComponent({
})

function handleDragEnd(): void {
handleDragging.cancel()

// Determine the active axis and direction multiplier
const dragAxis = isVertical.value ? 'y' : 'x'
const directionMultiplier = isReversed.value ? -1 : 1
Expand Down Expand Up @@ -645,7 +626,7 @@ export const Carousel = defineComponent({
*/
const trackTransform: ComputedRef<string> = computed(() => {
// Calculate the scrolled index with wrapping offset if applicable
const cloneOffset = config.wrapAround ? config.itemsToShow : 0
const cloneOffset = config.wrapAround ? clonedSlidesCount.value : 0

// Determine direction multiplier for orientation
const directionMultiplier = isReversed.value ? -1 : 1
Expand Down Expand Up @@ -683,7 +664,7 @@ export const Carousel = defineComponent({
const addonsElements = slotAddons?.(data) || []

if (config.wrapAround) {
const toShow = Math.ceil(config.itemsToShow)
const toShow = Math.ceil(clonedSlidesCount.value)
const slidesBefore = slides.slice(-toShow).map(({ vnode }, index: number) =>
cloneVNode(vnode, {
index: -slides.length + toShow + index,
Expand Down
9 changes: 9 additions & 0 deletions src/utils/getTransformValues.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export function getTransformValues(el: HTMLElement) {
const { transform } = window.getComputedStyle(el)

//add sanity check
return transform
.split(/[(,)]/)
.slice(1, -1)
.map((v) => parseFloat(v))
}
1 change: 1 addition & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from './getNumberInRange'
export * from './mapNumberToRange'
export * from './i18nFormatter'
export * from './throttle'
export * from './getTransformValues'
20 changes: 17 additions & 3 deletions src/utils/throttle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,20 @@
* @param fn - The function to throttle.
* @param ms - The number of milliseconds to wait for the throttled function to be called again
*/
export function throttle<Args extends Array<unknown>>(fn: (...args: Args) => void, ms = 0): (...args: Args) => void {
export function throttle<Args extends Array<unknown>>(
fn: (...args: Args) => void,
ms = 0
): { (...args: Args): void; cancel: () => void } {
let isThrottled = false
let start = 0
let frameId: number | null = null

return function (...args: Args) {
function throttled(...args: Args) {
if (isThrottled) return

isThrottled = true
const step = () => {
requestAnimationFrame((time) => {
frameId = requestAnimationFrame((time) => {
const elapsed = time - start
if (elapsed > ms) {
start = time
Expand All @@ -26,4 +30,14 @@ export function throttle<Args extends Array<unknown>>(fn: (...args: Args) => voi
}
step()
}

throttled.cancel = () => {
if (frameId) {
cancelAnimationFrame(frameId)
frameId = null
isThrottled = false
}
}

return throttled
}
2 changes: 2 additions & 0 deletions tests/integration/__snapshots__/carousel.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ exports[`Wrap around Carousel.ts > It renders wrapAround correctly 1`] = `
"<section class="carousel is-ltr" dir="ltr" aria-label="Gallery" tabindex="0">
<div class="carousel__viewport">
<ol class="carousel__track" style="transform: translateX(0px);">
<li style="width: 33.333333333333336%;" class="carousel__slide carousel__slide--clone" aria-hidden="true">6 <input type="text" tabindex="-1"></li>
<li style="width: 33.333333333333336%;" class="carousel__slide carousel__slide--clone" aria-hidden="true">7 <input type="text" tabindex="-1"></li>
<li style="width: 33.333333333333336%;" class="carousel__slide carousel__slide--clone" aria-hidden="true">8 <input type="text" tabindex="-1"></li>
<li style="width: 33.333333333333336%;" class="carousel__slide carousel__slide--clone" aria-hidden="true">9 <input type="text" tabindex="-1"></li>
Expand All @@ -19,6 +20,7 @@ exports[`Wrap around Carousel.ts > It renders wrapAround correctly 1`] = `
<li style="width: 33.333333333333336%;" class="carousel__slide carousel__slide--clone carousel__slide--visible carousel__slide--next" aria-hidden="true">1 <input type="text" tabindex="-1"></li>
<li style="width: 33.333333333333336%;" class="carousel__slide carousel__slide--clone" aria-hidden="true">2 <input type="text" tabindex="-1"></li>
<li style="width: 33.333333333333336%;" class="carousel__slide carousel__slide--clone" aria-hidden="true">3 <input type="text" tabindex="-1"></li>
<li style="width: 33.333333333333336%;" class="carousel__slide carousel__slide--clone" aria-hidden="true">4 <input type="text" tabindex="-1"></li>
</ol>
</div><button type="button" class="carousel__prev" aria-label="Navigate to previous slide" title="Navigate to previous slide"><svg class="carousel__icon" viewBox="0 0 24 24" role="img" aria-label="Arrow pointing to the left">
<title>Arrow pointing to the left</title>
Expand Down
Loading