@bagelink/vue 1.2.65 → 1.2.71
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/experimentalGenTypedRoutes.ts +309 -0
- package/dist/components/Alert.vue.d.ts +2 -0
- package/dist/components/Alert.vue.d.ts.map +1 -1
- package/dist/components/Carousel.vue.d.ts +12 -3
- package/dist/components/Carousel.vue.d.ts.map +1 -1
- package/dist/components/Carousel2.vue.d.ts +89 -0
- package/dist/components/Carousel2.vue.d.ts.map +1 -0
- package/dist/components/form/inputs/EmailInput.vue.d.ts +48 -0
- package/dist/components/form/inputs/EmailInput.vue.d.ts.map +1 -0
- package/dist/components/form/inputs/SelectInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/TelInput.vue.d.ts +60 -0
- package/dist/components/form/inputs/TelInput.vue.d.ts.map +1 -0
- package/dist/components/form/inputs/TextInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/index.d.ts +2 -1
- package/dist/components/form/inputs/index.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/composables/useDevice.d.ts.map +1 -1
- package/dist/composables/useSchemaField.d.ts.map +1 -1
- package/dist/directives/pattern.d.ts.map +1 -1
- package/dist/index.cjs +11302 -10747
- package/dist/index.mjs +11303 -10748
- package/dist/style.css +1822 -1755
- package/dist/utils/BagelFormUtils.d.ts +7 -0
- package/dist/utils/BagelFormUtils.d.ts.map +1 -1
- package/dist/utils/constants.d.ts +1 -0
- package/dist/utils/constants.d.ts.map +1 -1
- package/package.json +7 -3
- package/src/components/Alert.vue +29 -7
- package/src/components/Carousel.vue +8 -0
- package/src/components/Carousel2.vue +1012 -0
- package/src/components/form/inputs/EmailInput.vue +476 -0
- package/src/components/form/inputs/SelectInput.vue +1 -1
- package/src/components/form/inputs/TelInput.vue +210 -350
- package/src/components/form/inputs/TextInput.vue +1 -1
- package/src/components/form/inputs/index.ts +2 -1
- package/src/components/index.ts +2 -1
- package/src/composables/useDevice.ts +1 -0
- package/src/composables/useSchemaField.ts +3 -1
- package/src/directives/pattern.ts +3 -2
- package/src/styles/inputs.css +137 -138
- package/src/styles/layout.css +1466 -1459
- package/src/styles/mobilLayout.css +11 -0
- package/src/utils/BagelFormUtils.ts +24 -0
- package/src/utils/constants.ts +1 -0
- package/src/components/form/inputs/PhoneInput.vue +0 -352
|
@@ -0,0 +1,1012 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { sleep, useDevice } from '@bagelink/vue'
|
|
3
|
+
import { ref, onMounted, onBeforeUnmount, watch, computed, nextTick } from 'vue'
|
|
4
|
+
|
|
5
|
+
type Easing = 'ease-in-out' | 'ease-in' | 'ease-out' | 'linear'
|
|
6
|
+
type AutoplayMode = 'disabled' | 'standard' | 'linear'
|
|
7
|
+
type AutoplayValue = boolean | AutoplayMode
|
|
8
|
+
|
|
9
|
+
interface CarouselOptions {
|
|
10
|
+
duration?: number
|
|
11
|
+
easing?: Easing
|
|
12
|
+
perPage?: number | { [key: number]: number }
|
|
13
|
+
startIndex?: number
|
|
14
|
+
draggable?: boolean
|
|
15
|
+
multipleDrag?: boolean
|
|
16
|
+
threshold?: number
|
|
17
|
+
loop?: boolean
|
|
18
|
+
rtl?: boolean
|
|
19
|
+
autoplay?: AutoplayValue
|
|
20
|
+
autoplayInterval?: number
|
|
21
|
+
autoplaySpeed?: number
|
|
22
|
+
pauseOnHover?: boolean
|
|
23
|
+
dots?: boolean
|
|
24
|
+
slideWidth?: number
|
|
25
|
+
onInit?: () => void
|
|
26
|
+
onChange?: () => void
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface DragState {
|
|
30
|
+
startX: number
|
|
31
|
+
endX: number
|
|
32
|
+
startY: number
|
|
33
|
+
letItGo: boolean | null
|
|
34
|
+
preventClick: boolean
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const props = withDefaults(defineProps<CarouselOptions>(), {
|
|
38
|
+
duration: 200,
|
|
39
|
+
easing: 'ease-out',
|
|
40
|
+
startIndex: 0,
|
|
41
|
+
draggable: true,
|
|
42
|
+
multipleDrag: true,
|
|
43
|
+
threshold: 20,
|
|
44
|
+
loop: true,
|
|
45
|
+
rtl: false,
|
|
46
|
+
autoplay: 'disabled',
|
|
47
|
+
autoplayInterval: 3000,
|
|
48
|
+
autoplaySpeed: 50,
|
|
49
|
+
pauseOnHover: true,
|
|
50
|
+
dots: false,
|
|
51
|
+
slideWidth: 300,
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const emit = defineEmits<{
|
|
55
|
+
(event: 'init'): void
|
|
56
|
+
(event: 'change'): void
|
|
57
|
+
}>()
|
|
58
|
+
|
|
59
|
+
// Basic state refs
|
|
60
|
+
const carouselRef = ref<HTMLElement | null>(null)
|
|
61
|
+
const sliderFrame = ref<HTMLDivElement | null>(null)
|
|
62
|
+
const innerElements = ref<Element[]>([])
|
|
63
|
+
const currentSlide = ref(0)
|
|
64
|
+
const selectorWidth = ref(0)
|
|
65
|
+
const { innerWidth } = useDevice()
|
|
66
|
+
const transformProperty = ref('transform')
|
|
67
|
+
const pointerDown = ref(false)
|
|
68
|
+
|
|
69
|
+
// Drag-related refs
|
|
70
|
+
const drag = ref<DragState>({
|
|
71
|
+
startX: 0,
|
|
72
|
+
endX: 0,
|
|
73
|
+
startY: 0,
|
|
74
|
+
letItGo: null,
|
|
75
|
+
preventClick: false
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
// Add a new variable to track actual dragging state
|
|
79
|
+
const isDragging = ref(false)
|
|
80
|
+
const dragThreshold = 10 // Increase threshold slightly for better detection
|
|
81
|
+
|
|
82
|
+
// Add autoplay-related refs
|
|
83
|
+
const autoplayTimer = ref<number | null>(null)
|
|
84
|
+
const linearAnimationFrame = ref<number | null>(null)
|
|
85
|
+
const isHovering = ref(false)
|
|
86
|
+
const linearOffset = ref(0)
|
|
87
|
+
|
|
88
|
+
// Modified to work with optional parameters and ensure compatibility with Required type
|
|
89
|
+
type ComputedConfig = Omit<Required<CarouselOptions>, 'autoplay'> & { autoplay: AutoplayMode }
|
|
90
|
+
|
|
91
|
+
// perPage calculation - independent of config
|
|
92
|
+
const perPage = computed(() => {
|
|
93
|
+
if (typeof props.perPage === 'number') {
|
|
94
|
+
return props.perPage
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (typeof props.perPage === 'object') {
|
|
98
|
+
return Object.entries(props.perPage)
|
|
99
|
+
.reduce((acc, [viewport, value]) => innerWidth.value >= Number(viewport) ? value : acc, 1)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// If perPage is undefined, calculate based on slideWidth
|
|
103
|
+
return Math.max(1, Math.ceil(selectorWidth.value / props.slideWidth))
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
// Core state
|
|
107
|
+
const config = computed<ComputedConfig>(() => {
|
|
108
|
+
// Convert boolean autoplay values to string mode
|
|
109
|
+
let autoplayMode: AutoplayMode = 'disabled'
|
|
110
|
+
|
|
111
|
+
if (typeof props.autoplay === 'boolean') {
|
|
112
|
+
autoplayMode = props.autoplay ? 'standard' : 'disabled'
|
|
113
|
+
} else {
|
|
114
|
+
autoplayMode = props.autoplay as AutoplayMode
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Calculate perPage fallback based on config
|
|
118
|
+
const calculatedPerPage = perPage.value
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
duration: props.duration,
|
|
122
|
+
easing: props.easing,
|
|
123
|
+
perPage: calculatedPerPage,
|
|
124
|
+
startIndex: props.startIndex,
|
|
125
|
+
draggable: props.draggable,
|
|
126
|
+
multipleDrag: props.multipleDrag,
|
|
127
|
+
threshold: props.threshold,
|
|
128
|
+
loop: props.loop,
|
|
129
|
+
rtl: props.rtl,
|
|
130
|
+
autoplay: autoplayMode,
|
|
131
|
+
autoplayInterval: props.autoplayInterval,
|
|
132
|
+
autoplaySpeed: props.autoplaySpeed,
|
|
133
|
+
pauseOnHover: props.pauseOnHover,
|
|
134
|
+
dots: props.dots,
|
|
135
|
+
slideWidth: props.slideWidth,
|
|
136
|
+
onInit: () => { emit('init') },
|
|
137
|
+
onChange: () => { emit('change') }
|
|
138
|
+
}
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
// Computed property for total number of dots to display
|
|
142
|
+
const totalDots = computed(() => {
|
|
143
|
+
if (!innerElements.value.length) return 0
|
|
144
|
+
return config.value.loop ? innerElements.value.length : Math.ceil(innerElements.value.length - perPage.value + 1)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
// Lifecycle hooks
|
|
148
|
+
onMounted(() => init())
|
|
149
|
+
|
|
150
|
+
onBeforeUnmount(() => {
|
|
151
|
+
stopAutoplay()
|
|
152
|
+
detachEvents()
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
// Watch for changes to props
|
|
156
|
+
watch(() => props, () => {
|
|
157
|
+
// Props changed, might need to rebuild
|
|
158
|
+
resizeHandler()
|
|
159
|
+
}, { deep: true })
|
|
160
|
+
|
|
161
|
+
// Helper functions
|
|
162
|
+
function webkitOrNot(): string {
|
|
163
|
+
const { style } = document.documentElement
|
|
164
|
+
if (typeof style.transform === 'string') {
|
|
165
|
+
return 'transform'
|
|
166
|
+
}
|
|
167
|
+
return 'WebkitTransform'
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Event handlers
|
|
171
|
+
function attachEvents(): void {
|
|
172
|
+
window.addEventListener('resize', resizeHandler)
|
|
173
|
+
|
|
174
|
+
if (config.value.draggable && carouselRef.value) {
|
|
175
|
+
const eventOptions = { passive: true }
|
|
176
|
+
const touchMoveOptions = { passive: false }
|
|
177
|
+
const mouseMoveOptions = { passive: false } // Non-passive option for mouse move
|
|
178
|
+
const mouseDownOptions = { passive: false } // Non-passive option for mouse down
|
|
179
|
+
|
|
180
|
+
// Touch events
|
|
181
|
+
carouselRef.value.addEventListener('touchstart', touchstartHandler, eventOptions)
|
|
182
|
+
carouselRef.value.addEventListener('touchend', touchendHandler, eventOptions)
|
|
183
|
+
carouselRef.value.addEventListener('touchmove', touchmoveHandler, touchMoveOptions)
|
|
184
|
+
|
|
185
|
+
// Mouse events
|
|
186
|
+
carouselRef.value.addEventListener('mousedown', mousedownHandler, mouseDownOptions)
|
|
187
|
+
carouselRef.value.addEventListener('mouseup', mouseupHandler, eventOptions)
|
|
188
|
+
carouselRef.value.addEventListener('mouseleave', mouseleaveHandler, eventOptions)
|
|
189
|
+
carouselRef.value.addEventListener('mousemove', mousemoveHandler, mouseMoveOptions)
|
|
190
|
+
|
|
191
|
+
// Prevent default dragging of images and links
|
|
192
|
+
carouselRef.value.addEventListener('dragstart', dragstartHandler)
|
|
193
|
+
|
|
194
|
+
// Click
|
|
195
|
+
carouselRef.value.addEventListener('click', clickHandler)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Add mouse enter/leave events for pauseOnHover functionality
|
|
199
|
+
if (config.value.autoplay !== 'disabled' && config.value.pauseOnHover && carouselRef.value) {
|
|
200
|
+
carouselRef.value.addEventListener('mouseenter', mouseenterHandler)
|
|
201
|
+
carouselRef.value.addEventListener('mouseleave', mouseleaveAutoplayHandler)
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function detachEvents(): void {
|
|
206
|
+
window.removeEventListener('resize', resizeHandler)
|
|
207
|
+
|
|
208
|
+
if (carouselRef.value) {
|
|
209
|
+
carouselRef.value.removeEventListener('touchstart', touchstartHandler)
|
|
210
|
+
carouselRef.value.removeEventListener('touchend', touchendHandler)
|
|
211
|
+
carouselRef.value.removeEventListener('touchmove', touchmoveHandler)
|
|
212
|
+
carouselRef.value.removeEventListener('mousedown', mousedownHandler)
|
|
213
|
+
carouselRef.value.removeEventListener('mouseup', mouseupHandler)
|
|
214
|
+
carouselRef.value.removeEventListener('mouseleave', mouseleaveHandler)
|
|
215
|
+
carouselRef.value.removeEventListener('mousemove', mousemoveHandler)
|
|
216
|
+
carouselRef.value.removeEventListener('dragstart', dragstartHandler)
|
|
217
|
+
carouselRef.value.removeEventListener('click', clickHandler)
|
|
218
|
+
carouselRef.value.removeEventListener('mouseenter', mouseenterHandler)
|
|
219
|
+
carouselRef.value.removeEventListener('mouseleave', mouseleaveAutoplayHandler)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Core functionality
|
|
224
|
+
async function init(): Promise<void> {
|
|
225
|
+
if (!carouselRef.value) return
|
|
226
|
+
|
|
227
|
+
transformProperty.value = webkitOrNot()
|
|
228
|
+
|
|
229
|
+
// Add a small delay to ensure DOM is ready
|
|
230
|
+
await sleep(10)
|
|
231
|
+
|
|
232
|
+
// Create references
|
|
233
|
+
innerElements.value = Array.from(carouselRef.value.children)
|
|
234
|
+
|
|
235
|
+
if (!innerElements.value.length) {
|
|
236
|
+
console.warn('No carousel items found during initialization')
|
|
237
|
+
return
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
selectorWidth.value = carouselRef.value.offsetWidth
|
|
241
|
+
|
|
242
|
+
// Set current slide
|
|
243
|
+
currentSlide.value = config.value.loop
|
|
244
|
+
? config.value.startIndex % innerElements.value.length
|
|
245
|
+
: Math.max(0, Math.min(config.value.startIndex, innerElements.value.length - perPage.value))
|
|
246
|
+
|
|
247
|
+
// Hide overflow
|
|
248
|
+
carouselRef.value.style.overflow = 'hidden'
|
|
249
|
+
|
|
250
|
+
// Set RTL direction if needed
|
|
251
|
+
carouselRef.value.style.direction = config.value.rtl ? 'rtl' : 'ltr'
|
|
252
|
+
|
|
253
|
+
// Attach events
|
|
254
|
+
attachEvents()
|
|
255
|
+
|
|
256
|
+
await nextTick()
|
|
257
|
+
|
|
258
|
+
// Build slider frame
|
|
259
|
+
buildSliderFrame()
|
|
260
|
+
|
|
261
|
+
// Start autoplay if enabled
|
|
262
|
+
if (config.value.autoplay !== 'disabled') {
|
|
263
|
+
startAutoplay()
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Call onInit callback
|
|
267
|
+
config.value.onInit()
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function buildSliderFrame(): void {
|
|
271
|
+
if (!carouselRef.value) return
|
|
272
|
+
|
|
273
|
+
// Ensure we have elements to build the slider
|
|
274
|
+
if (!innerElements.value.length) {
|
|
275
|
+
console.warn('No carousel items found')
|
|
276
|
+
return
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const widthItem = selectorWidth.value / perPage.value
|
|
280
|
+
const itemsToBuild = config.value.loop
|
|
281
|
+
? innerElements.value.length + (2 * perPage.value)
|
|
282
|
+
: innerElements.value.length
|
|
283
|
+
|
|
284
|
+
// Create frame
|
|
285
|
+
if (!sliderFrame.value) {
|
|
286
|
+
sliderFrame.value = document.createElement('div')
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
sliderFrame.value.style.width = `${widthItem * itemsToBuild}px`
|
|
290
|
+
enableTransition()
|
|
291
|
+
|
|
292
|
+
if (config.value.draggable && carouselRef.value) {
|
|
293
|
+
carouselRef.value.style.cursor = '-webkit-grab'
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Create document fragment for slides
|
|
297
|
+
const docFragment = document.createDocumentFragment()
|
|
298
|
+
|
|
299
|
+
// Add clones before original elements if loop is true
|
|
300
|
+
if (config.value.loop && innerElements.value.length > 0) {
|
|
301
|
+
for (let i = innerElements.value.length - perPage.value; i < innerElements.value.length; i++) {
|
|
302
|
+
// Ensure the index is valid
|
|
303
|
+
if (i >= 0 && i < innerElements.value.length) {
|
|
304
|
+
const element = buildSliderFrameItem(innerElements.value[i].cloneNode(true) as Element)
|
|
305
|
+
docFragment.appendChild(element)
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Add original elements
|
|
311
|
+
for (let i = 0; i < innerElements.value.length; i++) {
|
|
312
|
+
const element = buildSliderFrameItem(innerElements.value[i])
|
|
313
|
+
docFragment.appendChild(element)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Add clones after original elements if loop is true
|
|
317
|
+
if (config.value.loop && innerElements.value.length > 0) {
|
|
318
|
+
for (let i = 0; i < perPage.value; i++) {
|
|
319
|
+
// Ensure the index is valid
|
|
320
|
+
if (i >= 0 && i < innerElements.value.length) {
|
|
321
|
+
const element = buildSliderFrameItem(innerElements.value[i].cloneNode(true) as Element)
|
|
322
|
+
docFragment.appendChild(element)
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Clear selector and append the frame
|
|
328
|
+
if (sliderFrame.value) {
|
|
329
|
+
sliderFrame.value.innerHTML = ''
|
|
330
|
+
sliderFrame.value.appendChild(docFragment)
|
|
331
|
+
|
|
332
|
+
if (carouselRef.value) {
|
|
333
|
+
carouselRef.value.innerHTML = ''
|
|
334
|
+
carouselRef.value.appendChild(sliderFrame.value)
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Go to current slide
|
|
339
|
+
slideToCurrent()
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function buildSliderFrameItem(elm: Element): HTMLElement {
|
|
343
|
+
const elementContainer = document.createElement('div')
|
|
344
|
+
elementContainer.style.cssFloat = config.value.rtl ? 'right' : 'left'
|
|
345
|
+
elementContainer.style.float = config.value.rtl ? 'right' : 'left'
|
|
346
|
+
|
|
347
|
+
const percentage = config.value.loop
|
|
348
|
+
? 100 / (innerElements.value.length + (perPage.value * 2))
|
|
349
|
+
: 100 / innerElements.value.length
|
|
350
|
+
|
|
351
|
+
elementContainer.style.width = `${percentage}%`
|
|
352
|
+
elementContainer.appendChild(elm)
|
|
353
|
+
|
|
354
|
+
return elementContainer
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function slideToCurrent(enableTransitionFlag?: boolean): void {
|
|
358
|
+
const currentSlideValue = config.value.loop
|
|
359
|
+
? currentSlide.value + perPage.value
|
|
360
|
+
: currentSlide.value
|
|
361
|
+
|
|
362
|
+
const offset = (config.value.rtl ? 1 : -1) * currentSlideValue * (selectorWidth.value / perPage.value)
|
|
363
|
+
|
|
364
|
+
if (enableTransitionFlag && sliderFrame.value) {
|
|
365
|
+
// Use requestAnimationFrame for smooth transitions
|
|
366
|
+
requestAnimationFrame(() => {
|
|
367
|
+
requestAnimationFrame(() => {
|
|
368
|
+
enableTransition()
|
|
369
|
+
if (sliderFrame.value) {
|
|
370
|
+
sliderFrame.value.style[transformProperty.value as any] = `translate3d(${offset}px, 0, 0)`
|
|
371
|
+
}
|
|
372
|
+
})
|
|
373
|
+
})
|
|
374
|
+
} else if (sliderFrame.value) {
|
|
375
|
+
sliderFrame.value.style[transformProperty.value as any] = `translate3d(${offset}px, 0, 0)`
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function prev(howManySlides: number = 1): void {
|
|
380
|
+
if (innerElements.value.length <= perPage.value) {
|
|
381
|
+
return
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const beforeChange = currentSlide.value
|
|
385
|
+
|
|
386
|
+
if (config.value.loop) {
|
|
387
|
+
const isNewIndexClone = currentSlide.value - howManySlides < 0
|
|
388
|
+
|
|
389
|
+
if (isNewIndexClone) {
|
|
390
|
+
disableTransition()
|
|
391
|
+
|
|
392
|
+
const mirrorSlideIndex = currentSlide.value + innerElements.value.length
|
|
393
|
+
const mirrorSlideIndexOffset = perPage.value
|
|
394
|
+
const moveTo = mirrorSlideIndex + mirrorSlideIndexOffset
|
|
395
|
+
const offset = (config.value.rtl ? 1 : -1) * moveTo * (selectorWidth.value / perPage.value)
|
|
396
|
+
const dragDistance = config.value.draggable ? drag.value.endX - drag.value.startX : 0
|
|
397
|
+
|
|
398
|
+
if (sliderFrame.value) {
|
|
399
|
+
sliderFrame.value.style[transformProperty.value as any] = `translate3d(${offset + dragDistance}px, 0, 0)`
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
currentSlide.value = mirrorSlideIndex - howManySlides
|
|
403
|
+
} else {
|
|
404
|
+
currentSlide.value = currentSlide.value - howManySlides
|
|
405
|
+
}
|
|
406
|
+
} else {
|
|
407
|
+
currentSlide.value = Math.max(currentSlide.value - howManySlides, 0)
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (beforeChange !== currentSlide.value) {
|
|
411
|
+
slideToCurrent(config.value.loop)
|
|
412
|
+
config.value.onChange()
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function next(howManySlides: number = 1): void {
|
|
417
|
+
if (innerElements.value.length <= perPage.value) {
|
|
418
|
+
return
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const beforeChange = currentSlide.value
|
|
422
|
+
|
|
423
|
+
if (config.value.loop) {
|
|
424
|
+
const isNewIndexClone = currentSlide.value + howManySlides > innerElements.value.length - perPage.value
|
|
425
|
+
|
|
426
|
+
if (isNewIndexClone) {
|
|
427
|
+
disableTransition()
|
|
428
|
+
|
|
429
|
+
const mirrorSlideIndex = currentSlide.value - innerElements.value.length
|
|
430
|
+
const mirrorSlideIndexOffset = perPage.value
|
|
431
|
+
const moveTo = mirrorSlideIndex + mirrorSlideIndexOffset
|
|
432
|
+
const offset = (config.value.rtl ? 1 : -1) * moveTo * (selectorWidth.value / perPage.value)
|
|
433
|
+
const dragDistance = config.value.draggable ? drag.value.endX - drag.value.startX : 0
|
|
434
|
+
|
|
435
|
+
if (sliderFrame.value) {
|
|
436
|
+
sliderFrame.value.style[transformProperty.value as any] = `translate3d(${offset + dragDistance}px, 0, 0)`
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
currentSlide.value = mirrorSlideIndex + howManySlides
|
|
440
|
+
} else {
|
|
441
|
+
currentSlide.value = currentSlide.value + howManySlides
|
|
442
|
+
}
|
|
443
|
+
} else {
|
|
444
|
+
currentSlide.value = Math.min(currentSlide.value + howManySlides, innerElements.value.length - perPage.value)
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (beforeChange !== currentSlide.value) {
|
|
448
|
+
slideToCurrent(config.value.loop)
|
|
449
|
+
config.value.onChange()
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function goTo(index: number): void {
|
|
454
|
+
if (innerElements.value.length <= perPage.value) {
|
|
455
|
+
return
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const beforeChange = currentSlide.value
|
|
459
|
+
|
|
460
|
+
currentSlide.value = config.value.loop
|
|
461
|
+
? index % innerElements.value.length
|
|
462
|
+
: Math.min(Math.max(index, 0), innerElements.value.length - perPage.value)
|
|
463
|
+
|
|
464
|
+
if (beforeChange !== currentSlide.value) {
|
|
465
|
+
slideToCurrent()
|
|
466
|
+
config.value.onChange()
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function disableTransition(): void {
|
|
471
|
+
if (sliderFrame.value) {
|
|
472
|
+
sliderFrame.value.style.webkitTransition = `all 0ms ${config.value.easing}`
|
|
473
|
+
sliderFrame.value.style.transition = `all 0ms ${config.value.easing}`
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function enableTransition(): void {
|
|
478
|
+
if (sliderFrame.value) {
|
|
479
|
+
sliderFrame.value.style.webkitTransition = `all ${config.value.duration}ms ${config.value.easing}`
|
|
480
|
+
sliderFrame.value.style.transition = `all ${config.value.duration}ms ${config.value.easing}`
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function updateAfterDrag(): void {
|
|
485
|
+
const movement = (config.value.rtl ? -1 : 1) * (drag.value.endX - drag.value.startX)
|
|
486
|
+
const movementDistance = Math.abs(movement)
|
|
487
|
+
const howManySliderToSlide = config.value.multipleDrag
|
|
488
|
+
? Math.ceil(movementDistance / (selectorWidth.value / perPage.value))
|
|
489
|
+
: 1
|
|
490
|
+
|
|
491
|
+
const slideToNegativeClone = movement > 0 && currentSlide.value - howManySliderToSlide < 0
|
|
492
|
+
const slideToPositiveClone = movement < 0
|
|
493
|
+
&& currentSlide.value + howManySliderToSlide > innerElements.value.length - perPage.value
|
|
494
|
+
|
|
495
|
+
if (movement > 0 && movementDistance > config.value.threshold && innerElements.value.length > perPage.value) {
|
|
496
|
+
prev(howManySliderToSlide)
|
|
497
|
+
} else if (movement < 0
|
|
498
|
+
&& movementDistance > config.value.threshold
|
|
499
|
+
&& innerElements.value.length > perPage.value) {
|
|
500
|
+
next(howManySliderToSlide)
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
slideToCurrent(slideToNegativeClone || slideToPositiveClone)
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function resizeHandler(): void {
|
|
507
|
+
// Recalculate current slide
|
|
508
|
+
if (currentSlide.value + perPage.value > innerElements.value.length) {
|
|
509
|
+
currentSlide.value = innerElements.value.length <= perPage.value
|
|
510
|
+
? 0
|
|
511
|
+
: innerElements.value.length - perPage.value
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (carouselRef.value) {
|
|
515
|
+
selectorWidth.value = carouselRef.value.offsetWidth
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
buildSliderFrame()
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function clearDrag(): void {
|
|
522
|
+
drag.value = {
|
|
523
|
+
startX: 0,
|
|
524
|
+
endX: 0,
|
|
525
|
+
startY: 0,
|
|
526
|
+
letItGo: null,
|
|
527
|
+
preventClick: drag.value.preventClick
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Touch event handlers
|
|
532
|
+
function touchstartHandler(e: TouchEvent): void {
|
|
533
|
+
const target = e.target as HTMLElement
|
|
534
|
+
const ignoreTags = ['TEXTAREA', 'OPTION', 'INPUT', 'SELECT'].includes(target.nodeName)
|
|
535
|
+
|
|
536
|
+
if (ignoreTags) {
|
|
537
|
+
return
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Pause autoplay during touch interaction
|
|
541
|
+
stopAutoplay()
|
|
542
|
+
|
|
543
|
+
e.stopPropagation()
|
|
544
|
+
pointerDown.value = true
|
|
545
|
+
isDragging.value = false // Reset dragging state
|
|
546
|
+
drag.value.startX = e.touches[0].pageX
|
|
547
|
+
drag.value.startY = e.touches[0].pageY
|
|
548
|
+
|
|
549
|
+
// Mark link-related elements for potential preventClick
|
|
550
|
+
const isLink = target.tagName === 'A' || target.closest('a')
|
|
551
|
+
const isImage = target.tagName === 'IMG'
|
|
552
|
+
if (isLink || isImage) {
|
|
553
|
+
drag.value.preventClick = false
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function touchendHandler(e: TouchEvent): void {
|
|
558
|
+
e.stopPropagation()
|
|
559
|
+
|
|
560
|
+
// If we were dragging, prevent clicks by adding a short-lived capture event listener
|
|
561
|
+
if (isDragging.value) {
|
|
562
|
+
// Add a temporary, one-time capture event listener to block the next click
|
|
563
|
+
document.addEventListener('click', function preventClickAfterDrag(event) {
|
|
564
|
+
event.preventDefault()
|
|
565
|
+
event.stopPropagation()
|
|
566
|
+
event.stopImmediatePropagation() // This is crucial to stop other handlers from executing
|
|
567
|
+
|
|
568
|
+
// Remove this listener after it's been triggered once
|
|
569
|
+
document.removeEventListener('click', preventClickAfterDrag, true)
|
|
570
|
+
}, { capture: true, once: true })
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
pointerDown.value = false
|
|
574
|
+
isDragging.value = false
|
|
575
|
+
enableTransition()
|
|
576
|
+
|
|
577
|
+
if (drag.value.endX) {
|
|
578
|
+
updateAfterDrag()
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
clearDrag()
|
|
582
|
+
|
|
583
|
+
// Restart autoplay after touch interaction
|
|
584
|
+
if (config.value.autoplay !== 'disabled') {
|
|
585
|
+
startAutoplay()
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function touchmoveHandler(e: TouchEvent): void {
|
|
590
|
+
e.stopPropagation()
|
|
591
|
+
|
|
592
|
+
if (drag.value.letItGo === null) {
|
|
593
|
+
drag.value.letItGo = Math.abs(drag.value.startY - e.touches[0].pageY)
|
|
594
|
+
< Math.abs(drag.value.startX - e.touches[0].pageX)
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if (pointerDown.value && drag.value.letItGo) {
|
|
598
|
+
e.preventDefault()
|
|
599
|
+
drag.value.endX = e.touches[0].pageX
|
|
600
|
+
|
|
601
|
+
// Calculate movement distance
|
|
602
|
+
const dragDistance = Math.abs(drag.value.endX - drag.value.startX)
|
|
603
|
+
|
|
604
|
+
// If dragged more than the threshold, mark as a real drag operation
|
|
605
|
+
if (dragDistance > dragThreshold) {
|
|
606
|
+
isDragging.value = true
|
|
607
|
+
drag.value.preventClick = true
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
if (sliderFrame.value) {
|
|
611
|
+
disableTransition()
|
|
612
|
+
|
|
613
|
+
const currentSlideValue = config.value.loop
|
|
614
|
+
? currentSlide.value + perPage.value
|
|
615
|
+
: currentSlide.value
|
|
616
|
+
|
|
617
|
+
const currentOffset = currentSlideValue * (selectorWidth.value / perPage.value)
|
|
618
|
+
const dragOffset = drag.value.endX - drag.value.startX
|
|
619
|
+
const offset = config.value.rtl
|
|
620
|
+
? currentOffset + dragOffset
|
|
621
|
+
: currentOffset - dragOffset
|
|
622
|
+
|
|
623
|
+
sliderFrame.value.style[transformProperty.value as any]
|
|
624
|
+
= `translate3d(${(config.value.rtl ? 1 : -1) * offset}px, 0, 0)`
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// Mouse event handlers
|
|
630
|
+
function mousedownHandler(e: MouseEvent): void {
|
|
631
|
+
const target = e.target as HTMLElement
|
|
632
|
+
const ignoreTags = ['TEXTAREA', 'OPTION', 'INPUT', 'SELECT'].includes(target.nodeName)
|
|
633
|
+
|
|
634
|
+
if (ignoreTags) return
|
|
635
|
+
|
|
636
|
+
// Pause autoplay during mouse interaction
|
|
637
|
+
stopAutoplay()
|
|
638
|
+
|
|
639
|
+
e.preventDefault()
|
|
640
|
+
e.stopPropagation()
|
|
641
|
+
pointerDown.value = true
|
|
642
|
+
isDragging.value = false // Reset dragging state on mousedown
|
|
643
|
+
drag.value.startX = e.pageX
|
|
644
|
+
|
|
645
|
+
// Mark link-related elements for potential preventClick
|
|
646
|
+
const isLink = target.tagName === 'A' || target.closest('a')
|
|
647
|
+
const isImage = target.tagName === 'IMG'
|
|
648
|
+
if (isLink || isImage) {
|
|
649
|
+
drag.value.preventClick = false // Start with false, will be set to true if actual dragging occurs
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function mouseupHandler(e: MouseEvent): void {
|
|
654
|
+
e.stopPropagation()
|
|
655
|
+
|
|
656
|
+
// If we were dragging, prevent clicks by adding a short-lived capture event listener
|
|
657
|
+
if (isDragging.value) {
|
|
658
|
+
// Add a temporary, one-time capture event listener to block the next click
|
|
659
|
+
document.addEventListener('click', function preventClickAfterDrag(event) {
|
|
660
|
+
event.preventDefault()
|
|
661
|
+
event.stopPropagation()
|
|
662
|
+
event.stopImmediatePropagation() // This is crucial to stop other handlers from executing
|
|
663
|
+
|
|
664
|
+
// Remove this listener after it's been triggered once
|
|
665
|
+
document.removeEventListener('click', preventClickAfterDrag, true)
|
|
666
|
+
}, { capture: true, once: true })
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
pointerDown.value = false
|
|
670
|
+
isDragging.value = false
|
|
671
|
+
|
|
672
|
+
if (carouselRef.value) {
|
|
673
|
+
carouselRef.value.style.cursor = '-webkit-grab'
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
enableTransition()
|
|
677
|
+
|
|
678
|
+
if (drag.value.endX) {
|
|
679
|
+
updateAfterDrag()
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
clearDrag()
|
|
683
|
+
|
|
684
|
+
// Restart autoplay after mouse interaction if not hovering
|
|
685
|
+
if (config.value.autoplay !== 'disabled' && !isHovering.value) {
|
|
686
|
+
startAutoplay()
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function mousemoveHandler(e: MouseEvent): void {
|
|
691
|
+
e.preventDefault()
|
|
692
|
+
|
|
693
|
+
if (pointerDown.value) {
|
|
694
|
+
drag.value.endX = e.pageX
|
|
695
|
+
|
|
696
|
+
// Calculate movement distance
|
|
697
|
+
const dragDistance = Math.abs(drag.value.endX - drag.value.startX)
|
|
698
|
+
|
|
699
|
+
// If dragged more than the threshold, mark as a real drag operation
|
|
700
|
+
if (dragDistance > dragThreshold) {
|
|
701
|
+
isDragging.value = true
|
|
702
|
+
drag.value.preventClick = true
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
if (carouselRef.value) {
|
|
706
|
+
carouselRef.value.style.cursor = '-webkit-grabbing'
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
if (sliderFrame.value) {
|
|
710
|
+
disableTransition()
|
|
711
|
+
|
|
712
|
+
const currentSlideValue = config.value.loop
|
|
713
|
+
? currentSlide.value + perPage.value
|
|
714
|
+
: currentSlide.value
|
|
715
|
+
|
|
716
|
+
const currentOffset = currentSlideValue * (selectorWidth.value / perPage.value)
|
|
717
|
+
const dragOffset = drag.value.endX - drag.value.startX
|
|
718
|
+
const offset = config.value.rtl
|
|
719
|
+
? currentOffset + dragOffset
|
|
720
|
+
: currentOffset - dragOffset
|
|
721
|
+
|
|
722
|
+
sliderFrame.value.style[transformProperty.value as any]
|
|
723
|
+
= `translate3d(${(config.value.rtl ? 1 : -1) * offset}px, 0, 0)`
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
function mouseleaveHandler(e: MouseEvent): void {
|
|
729
|
+
if (pointerDown.value) {
|
|
730
|
+
pointerDown.value = false
|
|
731
|
+
|
|
732
|
+
if (carouselRef.value) {
|
|
733
|
+
carouselRef.value.style.cursor = '-webkit-grab'
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
drag.value.endX = e.pageX
|
|
737
|
+
drag.value.preventClick = false
|
|
738
|
+
enableTransition()
|
|
739
|
+
updateAfterDrag()
|
|
740
|
+
clearDrag()
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
function clickHandler(e: MouseEvent): void {
|
|
745
|
+
if (drag.value.preventClick || isDragging.value) {
|
|
746
|
+
e.preventDefault()
|
|
747
|
+
e.stopPropagation()
|
|
748
|
+
e.stopImmediatePropagation() // This stops other handlers from firing
|
|
749
|
+
drag.value.preventClick = false
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
drag.value.preventClick = false
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// Add this new function to handle dragstart events
|
|
756
|
+
function dragstartHandler(e: DragEvent): void {
|
|
757
|
+
// Prevent default drag behavior for images and links
|
|
758
|
+
if (config.value.draggable) {
|
|
759
|
+
const target = e.target as HTMLElement
|
|
760
|
+
const isDraggableElement = target.tagName === 'IMG'
|
|
761
|
+
|| target.tagName === 'A'
|
|
762
|
+
|| target.parentElement?.tagName === 'A'
|
|
763
|
+
|
|
764
|
+
if (isDraggableElement) {
|
|
765
|
+
e.preventDefault()
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// Public API
|
|
771
|
+
function remove(index: number): void {
|
|
772
|
+
if (index < 0 || index >= innerElements.value.length) {
|
|
773
|
+
throw new Error('Item to remove doesn\'t exist 😭')
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// Shift sliderFrame back by one if needed
|
|
777
|
+
const lowerIndex = index < currentSlide.value
|
|
778
|
+
const lastItem = currentSlide.value + perPage.value - 1 === index
|
|
779
|
+
|
|
780
|
+
if (lowerIndex || lastItem) {
|
|
781
|
+
currentSlide.value--
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
innerElements.value.splice(index, 1)
|
|
785
|
+
buildSliderFrame()
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
function insert(item: Element, index: number): void {
|
|
789
|
+
if (index < 0 || index > innerElements.value.length + 1) {
|
|
790
|
+
throw new Error('Unable to insert at this index 😭')
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
if (innerElements.value.includes(item)) {
|
|
794
|
+
throw new Error('The same item in a carousel? Really? Nope 😭')
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// Avoid shifting content
|
|
798
|
+
const shouldItShift = index <= currentSlide.value && innerElements.value.length > 0
|
|
799
|
+
|
|
800
|
+
if (shouldItShift) {
|
|
801
|
+
currentSlide.value++
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
innerElements.value.splice(index, 0, item)
|
|
805
|
+
buildSliderFrame()
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
function prepend(item: Element): void {
|
|
809
|
+
insert(item, 0)
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
function append(item: Element): void {
|
|
813
|
+
insert(item, innerElements.value.length)
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
function destroy(restoreMarkup: boolean = false): void {
|
|
817
|
+
detachEvents()
|
|
818
|
+
|
|
819
|
+
if (carouselRef.value) {
|
|
820
|
+
carouselRef.value.style.cursor = 'auto'
|
|
821
|
+
|
|
822
|
+
if (restoreMarkup) {
|
|
823
|
+
const slides = document.createDocumentFragment()
|
|
824
|
+
|
|
825
|
+
for (let i = 0; i < innerElements.value.length; i++) {
|
|
826
|
+
slides.appendChild(innerElements.value[i])
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
carouselRef.value.innerHTML = ''
|
|
830
|
+
carouselRef.value.appendChild(slides)
|
|
831
|
+
carouselRef.value.removeAttribute('style')
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// Add after init()
|
|
837
|
+
async function startAutoplay(): Promise<void> {
|
|
838
|
+
stopAutoplay() // Clear any existing timers first
|
|
839
|
+
|
|
840
|
+
if (config.value.autoplay === 'standard') {
|
|
841
|
+
autoplayTimer.value = window.setInterval(() => {
|
|
842
|
+
if (!isHovering.value) {
|
|
843
|
+
next()
|
|
844
|
+
}
|
|
845
|
+
}, config.value.autoplayInterval)
|
|
846
|
+
} else if (config.value.autoplay === 'linear') {
|
|
847
|
+
startLinearAutoplay()
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
function stopAutoplay(): void {
|
|
852
|
+
if (autoplayTimer.value !== null) {
|
|
853
|
+
clearInterval(autoplayTimer.value)
|
|
854
|
+
autoplayTimer.value = null
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
if (linearAnimationFrame.value !== null) {
|
|
858
|
+
cancelAnimationFrame(linearAnimationFrame.value)
|
|
859
|
+
linearAnimationFrame.value = null
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
function startLinearAutoplay(): void {
|
|
864
|
+
// Reset the linear offset
|
|
865
|
+
linearOffset.value = 0
|
|
866
|
+
let lastTimestamp = 0
|
|
867
|
+
|
|
868
|
+
// Animation function
|
|
869
|
+
const animate = (timestamp: number) => {
|
|
870
|
+
if (!lastTimestamp) lastTimestamp = timestamp
|
|
871
|
+
|
|
872
|
+
// Calculate time delta and convert to pixels based on speed
|
|
873
|
+
const elapsed = timestamp - lastTimestamp
|
|
874
|
+
lastTimestamp = timestamp
|
|
875
|
+
|
|
876
|
+
// If hovering or not visible, just continue the animation loop without changing position
|
|
877
|
+
if (isHovering.value) {
|
|
878
|
+
linearAnimationFrame.value = requestAnimationFrame(animate)
|
|
879
|
+
return
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// Calculate how many pixels to move based on time elapsed and speed
|
|
883
|
+
const pixelsToMove = (elapsed / 1000) * config.value.autoplaySpeed
|
|
884
|
+
linearOffset.value += pixelsToMove
|
|
885
|
+
|
|
886
|
+
// Calculate total width of all items
|
|
887
|
+
const itemWidth = selectorWidth.value / perPage.value
|
|
888
|
+
const totalWidth = itemWidth * innerElements.value.length
|
|
889
|
+
|
|
890
|
+
// If we've scrolled past the entire content, reset
|
|
891
|
+
if (linearOffset.value >= totalWidth) {
|
|
892
|
+
if (config.value.loop) {
|
|
893
|
+
linearOffset.value = 0
|
|
894
|
+
} else {
|
|
895
|
+
// If not looping, stop at the end
|
|
896
|
+
linearOffset.value = totalWidth - itemWidth
|
|
897
|
+
stopAutoplay()
|
|
898
|
+
return
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// Apply the offset using transforms
|
|
903
|
+
if (sliderFrame.value) {
|
|
904
|
+
const direction = config.value.rtl ? 1 : -1
|
|
905
|
+
sliderFrame.value.style.transition = 'none' // Smooth scrolling, no easing
|
|
906
|
+
sliderFrame.value.style[transformProperty.value as any]
|
|
907
|
+
= `translate3d(${direction * linearOffset.value}px, 0, 0)`
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
// Update current slide based on the offset
|
|
911
|
+
const newSlide = Math.floor(linearOffset.value / itemWidth) % innerElements.value.length
|
|
912
|
+
if (newSlide !== currentSlide.value) {
|
|
913
|
+
currentSlide.value = newSlide
|
|
914
|
+
config.value.onChange()
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// Continue animation loop
|
|
918
|
+
linearAnimationFrame.value = requestAnimationFrame(animate)
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// Start the animation
|
|
922
|
+
linearAnimationFrame.value = requestAnimationFrame(animate)
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// Event handlers for autoplay pausing
|
|
926
|
+
function mouseenterHandler(): void {
|
|
927
|
+
isHovering.value = true
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
function mouseleaveAutoplayHandler(): void {
|
|
931
|
+
isHovering.value = false
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
// Add new public methods for autoplay control
|
|
935
|
+
function pauseAutoplay(): void {
|
|
936
|
+
stopAutoplay()
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
function resumeAutoplay(): void {
|
|
940
|
+
if (config.value.autoplay !== 'disabled') {
|
|
941
|
+
startAutoplay()
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// Expose public methods
|
|
946
|
+
defineExpose({
|
|
947
|
+
prev,
|
|
948
|
+
next,
|
|
949
|
+
goTo,
|
|
950
|
+
remove,
|
|
951
|
+
insert,
|
|
952
|
+
prepend,
|
|
953
|
+
append,
|
|
954
|
+
destroy,
|
|
955
|
+
currentSlide,
|
|
956
|
+
pauseAutoplay,
|
|
957
|
+
resumeAutoplay,
|
|
958
|
+
})
|
|
959
|
+
</script>
|
|
960
|
+
|
|
961
|
+
<template>
|
|
962
|
+
<div class="carousel-wrapper">
|
|
963
|
+
<div ref="carouselRef" class="carousel-container">
|
|
964
|
+
<slot />
|
|
965
|
+
</div>
|
|
966
|
+
<div v-if="props.dots && totalDots > 1" class="carousel-dots">
|
|
967
|
+
<button
|
|
968
|
+
v-for="i in totalDots"
|
|
969
|
+
:key="i"
|
|
970
|
+
type="button"
|
|
971
|
+
class="carousel-dot" :class="[{ active: (i - 1) === currentSlide }]"
|
|
972
|
+
:aria-label="`Go to slide ${i}`"
|
|
973
|
+
@click="goTo(i - 1)"
|
|
974
|
+
/>
|
|
975
|
+
</div>
|
|
976
|
+
</div>
|
|
977
|
+
</template>
|
|
978
|
+
|
|
979
|
+
<style scoped>
|
|
980
|
+
.carousel-wrapper {
|
|
981
|
+
position: relative;
|
|
982
|
+
width: 100%;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
.carousel-container {
|
|
986
|
+
margin: 0 auto;
|
|
987
|
+
overflow: hidden;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
.carousel-dots {
|
|
991
|
+
display: flex;
|
|
992
|
+
justify-content: center;
|
|
993
|
+
gap: 8px;
|
|
994
|
+
margin-top: 16px;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
.carousel-dot {
|
|
998
|
+
width: 12px;
|
|
999
|
+
height: 12px;
|
|
1000
|
+
border-radius: 50px;
|
|
1001
|
+
background-color: #ccc;
|
|
1002
|
+
border: none;
|
|
1003
|
+
padding: 0;
|
|
1004
|
+
cursor: pointer;
|
|
1005
|
+
transition: all 0.3s ease-in-out;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
.carousel-dot.active {
|
|
1009
|
+
background-color: #333;
|
|
1010
|
+
width: 26px;
|
|
1011
|
+
}
|
|
1012
|
+
</style>
|