@bagelink/vue 1.14.15 → 1.15.0

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.
Files changed (102) hide show
  1. package/dist/components/Alert.vue.d.ts.map +1 -1
  2. package/dist/components/Badge.vue.d.ts.map +1 -1
  3. package/dist/components/Btn.vue.d.ts.map +1 -1
  4. package/dist/components/Dropdown.vue.d.ts.map +1 -1
  5. package/dist/components/Image.vue.d.ts.map +1 -1
  6. package/dist/components/ListItem.vue.d.ts.map +1 -1
  7. package/dist/components/MapEmbed/Index.vue.d.ts.map +1 -1
  8. package/dist/components/Pagination.vue.d.ts.map +1 -1
  9. package/dist/components/Swiper.vue.d.ts.map +1 -1
  10. package/dist/components/Toast.vue.d.ts.map +1 -1
  11. package/dist/components/form/index.d.ts.map +1 -1
  12. package/dist/components/form/inputs/SelectInput.vue.d.ts.map +1 -1
  13. package/dist/components/index.d.ts.map +1 -1
  14. package/dist/components/layout/AppContent.vue.d.ts.map +1 -1
  15. package/dist/components/layout/AppLayout.vue.d.ts.map +1 -1
  16. package/dist/components/layout/AppSidebar.vue.d.ts.map +1 -1
  17. package/dist/components/layout/Panel.vue.d.ts.map +1 -1
  18. package/dist/components/layout/Resizable.vue.d.ts.map +1 -1
  19. package/dist/components/layout/TabsNav.vue.d.ts.map +1 -1
  20. package/dist/components/layout/appLayoutContext.d.ts +24 -0
  21. package/dist/components/layout/appLayoutContext.d.ts.map +1 -0
  22. package/dist/components/layout/index.d.ts.map +1 -1
  23. package/dist/components/lightbox/Lightbox.vue.d.ts.map +1 -1
  24. package/dist/composables/index.d.ts.map +1 -1
  25. package/dist/composables/useDevice.d.ts.map +1 -1
  26. package/dist/composables/useEscapeKey.d.ts +12 -0
  27. package/dist/composables/useEscapeKey.d.ts.map +1 -0
  28. package/dist/composables/useSchemaField.d.ts.map +1 -1
  29. package/dist/composables/useTheme.d.ts.map +1 -1
  30. package/dist/form-flow/FormFlow.vue.d.ts.map +1 -1
  31. package/dist/form-flow/form-flow.d.ts.map +1 -1
  32. package/dist/index.cjs +203 -207
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.mjs +25819 -28870
  35. package/dist/style.css +1 -1
  36. package/dist/types/BagelForm.d.ts.map +1 -1
  37. package/dist/types/BtnOptions.d.ts.map +1 -1
  38. package/dist/utils/constants.d.ts.map +1 -1
  39. package/dist/utils/index.d.ts.map +1 -1
  40. package/package.json +3 -6
  41. package/src/components/Alert.vue +34 -14
  42. package/src/components/Badge.vue +145 -22
  43. package/src/components/Btn.vue +43 -31
  44. package/src/components/Dropdown.vue +5 -12
  45. package/src/components/FilterQuery.vue +1 -1
  46. package/src/components/Image.vue +3 -2
  47. package/src/components/JSONSchema.vue +2 -2
  48. package/src/components/JsonBuilder.vue +1 -1
  49. package/src/components/ListItem.vue +1 -3
  50. package/src/components/MapEmbed/Index.vue +10 -9
  51. package/src/components/NavBar.vue +2 -2
  52. package/src/components/Spreadsheet/Index.vue +1 -1
  53. package/src/components/Swiper.vue +3 -1
  54. package/src/components/Toast.vue +23 -8
  55. package/src/components/calendar/Index.vue +4 -4
  56. package/src/components/calendar/views/MonthView.vue +3 -3
  57. package/src/components/form/index.ts +0 -4
  58. package/src/components/form/inputs/EmailInput.vue +1 -1
  59. package/src/components/form/inputs/NumberInput.vue +1 -1
  60. package/src/components/form/inputs/OTP.vue +2 -2
  61. package/src/components/form/inputs/SelectInput.vue +3 -3
  62. package/src/components/form/inputs/TelInput.vue +2 -2
  63. package/src/components/form/inputs/TextInput.vue +1 -1
  64. package/src/components/form/inputs/Upload/upload.css +2 -2
  65. package/src/components/index.ts +2 -6
  66. package/src/components/layout/AppContent.vue +5 -19
  67. package/src/components/layout/AppLayout.vue +47 -18
  68. package/src/components/layout/AppSidebar.vue +16 -33
  69. package/src/components/layout/Resizable.vue +5 -2
  70. package/src/components/layout/TabsNav.vue +5 -5
  71. package/src/components/layout/appLayoutContext.ts +44 -0
  72. package/src/components/layout/index.ts +2 -0
  73. package/src/components/lightbox/Lightbox.vue +3 -9
  74. package/src/composables/index.ts +1 -0
  75. package/src/composables/useDevice.ts +2 -1
  76. package/src/composables/useEscapeKey.ts +56 -0
  77. package/src/composables/useSchemaField.ts +2 -17
  78. package/src/composables/useTheme.ts +23 -19
  79. package/src/form-flow/FormFlow.vue +2 -0
  80. package/src/form-flow/form-flow.ts +7 -0
  81. package/src/index.ts +0 -2
  82. package/src/styles/inputs.css +1 -1
  83. package/src/types/BagelForm.ts +46 -151
  84. package/src/types/BtnOptions.ts +5 -3
  85. package/src/utils/constants.ts +7 -0
  86. package/src/utils/index.ts +19 -3
  87. package/src/utils/sizeParsing.ts +5 -5
  88. package/vite.config.ts +5 -1
  89. package/src/components/Carousel.vue +0 -724
  90. package/src/components/ImportData.vue +0 -1749
  91. package/src/components/Pill.vue +0 -150
  92. package/src/components/Slider.vue +0 -1446
  93. package/src/components/Title.vue +0 -23
  94. package/src/components/ToolBar.vue +0 -9
  95. package/src/components/form/BagelForm.vue +0 -219
  96. package/src/components/form/BglFieldSet.vue +0 -14
  97. package/src/components/form/BglMultiStepForm.vue +0 -469
  98. package/src/components/form/FieldArray.vue +0 -422
  99. package/src/components/form/useBagelFormState.ts +0 -76
  100. package/src/composables/useFormField.ts +0 -38
  101. package/src/dialog/DialogOLD.vue +0 -358
  102. package/src/utils/BagelFormUtils.ts +0 -684
@@ -1,724 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
3
-
4
- defineOptions({ name: 'BagelCarousel' })
5
-
6
- const props = defineProps({
7
- autoHeight: { type: Boolean, default: false },
8
- allowScroll: { type: Boolean, default: true },
9
- freeDrag: { type: Boolean, default: true },
10
- items: { type: Number, default: 4 },
11
- index: { type: Number, default: 0 },
12
- rtl: { type: Boolean, default: false },
13
- autoplay: { type: Boolean, default: false },
14
- autoPlaySpeed: { type: Number, default: 4000 },
15
- dots: { type: Boolean, default: false },
16
- loop: { type: Boolean, default: false },
17
- })
18
-
19
- const emit = defineEmits(['update:index'])
20
-
21
- // Constants
22
- const ANIMATION_TIMINGS = {
23
- TOUCH: 150,
24
- MOBILE: 420,
25
- DESKTOP: 840,
26
- MAX_DURATION: 300,
27
- BASE_DURATION: 150,
28
- DURATION_PER_PANEL: 50,
29
- } as const
30
-
31
- const THRESHOLDS = {
32
- DRAG: 20,
33
- TOUCH: 5,
34
- SWIPE_PERCENT: 0.1,
35
- VELOCITY: 0.15,
36
- WHEEL_PERCENT: 0.3,
37
- } as const
38
-
39
- // Add gap constant
40
- const GAP_PERCENT = 1
41
-
42
- // Template refs and state
43
- const bglSlider = ref<HTMLElement | undefined>()
44
- const isSliderAvailable = computed(() => !!bglSlider.value)
45
-
46
- const itemCount = ref(props.items)
47
- const activeSlideIndex = ref(props.index)
48
- const slideCount = ref(0)
49
- const yHeight = ref('auto')
50
-
51
- // Interaction state
52
- const isDragging = ref(false)
53
- const isPressed = ref(false)
54
- const startX = ref(0)
55
- const startY = ref(0)
56
- const translateX = ref(0)
57
- const lastX = ref(0)
58
- const lastTime = ref(0)
59
- const accumulatedDeltaX = ref(0)
60
-
61
- let timeout: ReturnType<typeof setTimeout> | undefined
62
- let wheelTimeout: ReturnType<typeof setTimeout> | undefined
63
- let autoPlayInterval: ReturnType<typeof setInterval> | undefined
64
-
65
- // Add interface for velocity tracking
66
- interface VelocitySample {
67
- time: number
68
- position: number
69
- }
70
-
71
- const velocityTracker = ref<VelocitySample[]>([])
72
- const VELOCITY_SAMPLE_DURATION = 100 // ms to track velocity
73
-
74
- function getAverageVelocity(): number {
75
- if (velocityTracker.value.length < 2) { return 0 }
76
-
77
- const now = Date.now()
78
- // Only consider samples within our sample duration
79
- const recentSamples = velocityTracker.value.filter((sample: VelocitySample) => now - sample.time < VELOCITY_SAMPLE_DURATION)
80
-
81
- if (recentSamples.length < 2) { return 0 }
82
-
83
- const [first] = recentSamples
84
- const last = recentSamples[recentSamples.length - 1]
85
- const timeDelta = last.time - first.time
86
-
87
- if (timeDelta === 0) { return 0 }
88
-
89
- return (last.position - first.position) / timeDelta
90
- }
91
-
92
- // Transform helpers
93
- function getCurrentTransform(): number {
94
- if (!bglSlider.value) { return 0 }
95
- const { transform } = bglSlider.value.style
96
- const value = transform ? Number.parseInt(transform.replace(/[^-\d.]/g, ''), 10) : 0
97
- return props.rtl ? -value : value
98
- }
99
-
100
- function setTransform(value: number) {
101
- if (!bglSlider.value) { return }
102
- const rtlValue = props.rtl ? -value : value
103
- bglSlider.value.style.transform = `translateX(${rtlValue}px)`
104
- }
105
-
106
- function easeInOutQuad(t: number) {
107
- return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t
108
- }
109
-
110
- function animateTransform(start: number, end: number, duration: number) {
111
- const startTime = performance.now()
112
-
113
- function animate(currentTime: number) {
114
- if (!isSliderAvailable.value) { return }
115
- const timeElapsed = currentTime - startTime
116
- const progress = Math.min(timeElapsed / duration, 1)
117
- const currentTransform = start + (end - start) * easeInOutQuad(progress)
118
- setTransform(currentTransform)
119
- if (progress < 1) { requestAnimationFrame(animate) }
120
- }
121
-
122
- requestAnimationFrame(animate)
123
- }
124
-
125
- // Silent jump (no animation) — used for loop resets
126
- function jumpToSlide(index: number) {
127
- countSlides()
128
- if (!isSliderAvailable.value || !bglSlider.value || index < 0 || index >= slideCount.value) { return }
129
-
130
- const containerWidth = bglSlider.value.offsetWidth
131
- const gapWidth = (containerWidth * GAP_PERCENT) / 100
132
- const totalGapWidth = (itemCount.value - 1) * gapWidth
133
- const singleItemWidth = (containerWidth - totalGapWidth) / itemCount.value
134
- const gapsBeforeSlide = index * gapWidth
135
- const targetTransform = -(singleItemWidth * index + gapsBeforeSlide)
136
-
137
- setTransform(targetTransform)
138
- activeSlideIndex.value = index
139
- }
140
-
141
- // Navigation
142
- function goToSlide(index: number, isTouchNav = false) {
143
- countSlides()
144
- if (!isSliderAvailable.value || !bglSlider.value || index < 0 || index >= slideCount.value) { return }
145
-
146
- const containerWidth = bglSlider.value.offsetWidth
147
- const gapWidth = (containerWidth * GAP_PERCENT) / 100
148
- const totalGapWidth = (itemCount.value - 1) * gapWidth
149
- const singleItemWidth = (containerWidth - totalGapWidth) / itemCount.value
150
-
151
- // Account for all gaps before this slide
152
- const gapsBeforeSlide = index * gapWidth
153
- const targetTransform = -(singleItemWidth * index + gapsBeforeSlide)
154
-
155
- const currentTransform = getCurrentTransform()
156
- const duration = isTouchNav
157
- ? ANIMATION_TIMINGS.TOUCH
158
- : window.innerWidth < 600
159
- ? ANIMATION_TIMINGS.MOBILE
160
- : ANIMATION_TIMINGS.DESKTOP
161
-
162
- animateTransform(currentTransform, targetTransform, duration)
163
- activeSlideIndex.value = index
164
- updateHeight()
165
- }
166
-
167
- function next() {
168
- if (!isSliderAvailable.value) { return }
169
- countSlides()
170
- // In RTL, next and prev are reversed
171
- const nextIndex = props.rtl
172
- ? (activeSlideIndex.value - 1 + slideCount.value) % slideCount.value
173
- : (activeSlideIndex.value + 1) % slideCount.value
174
- goToSlide(nextIndex)
175
- }
176
-
177
- function prev() {
178
- if (!isSliderAvailable.value) { return }
179
- countSlides()
180
- // In RTL, next and prev are reversed
181
- const prevIndex = props.rtl
182
- ? (activeSlideIndex.value + 1) % slideCount.value
183
- : (activeSlideIndex.value - 1 + slideCount.value) % slideCount.value
184
- goToSlide(prevIndex)
185
- }
186
-
187
- // Height management
188
- function calcHeight() {
189
- if (!isSliderAvailable.value || !bglSlider.value) { return }
190
- try {
191
- const activeSlide = bglSlider.value.children[activeSlideIndex.value]
192
- if (!activeSlide || !(activeSlide instanceof HTMLElement)) { return }
193
- const children = Array.from(activeSlide.children) as HTMLElement[]
194
- const totalHeight = children.reduce((sum, el) => sum + el.clientHeight, 0)
195
- yHeight.value = `${totalHeight}px`
196
- } catch (error) {
197
- console.error('Error calculating height:', error)
198
- }
199
- }
200
-
201
- function updateHeight() {
202
- if (!props.autoHeight) { return }
203
- setTimeout(calcHeight, 200)
204
- }
205
-
206
- // Slide management
207
- function countSlides() {
208
- if (!isSliderAvailable.value || !bglSlider.value) { return }
209
- slideCount.value = bglSlider.value.children.length
210
- }
211
-
212
- // Event handlers
213
- function handleSlideChange() {
214
- if (props.index !== activeSlideIndex.value) {
215
- emit('update:index', activeSlideIndex.value)
216
- }
217
- }
218
-
219
- function handleResize() {
220
- if (!isSliderAvailable.value || !bglSlider.value) { return }
221
- itemCount.value = window.innerWidth < 600
222
- ? 1
223
- : window.innerWidth < 991
224
- ? Math.min(props.items, 2)
225
- : props.items
226
-
227
- const containerWidth = bglSlider.value.offsetWidth
228
- const gapWidth = (containerWidth * GAP_PERCENT) / 100
229
- const totalGapWidth = (itemCount.value - 1) * gapWidth
230
- const singleItemWidth = (containerWidth - totalGapWidth) / itemCount.value
231
- const gapsBeforeSlide = activeSlideIndex.value * gapWidth
232
- const targetTransform = -(singleItemWidth * activeSlideIndex.value + gapsBeforeSlide)
233
-
234
- setTransform(targetTransform)
235
- updateHeight()
236
- }
237
-
238
- function clearAutoplay() {
239
- if (autoPlayInterval) { clearInterval(autoPlayInterval) }
240
- }
241
-
242
- // Mouse events
243
- function preventDefaultClick(e: MouseEvent) {
244
- if (isDragging.value) {
245
- e.preventDefault()
246
- e.stopPropagation()
247
- }
248
- }
249
-
250
- async function disableDrag() {
251
- await nextTick()
252
- if (!isSliderAvailable.value || !bglSlider.value) { return }
253
-
254
- const elements = Array.from(bglSlider.value.querySelectorAll('img, a')) as HTMLElement[]
255
- elements.forEach((el) => {
256
- el.setAttribute('draggable', 'false')
257
- // Add click prevention to interactive elements
258
- el.removeEventListener('click', preventDefaultClick, true)
259
- el.addEventListener('click', preventDefaultClick, true)
260
- })
261
-
262
- const slides = Array.from(bglSlider.value.children)
263
- slides.forEach((slide) => {
264
- const element = slide as HTMLElement
265
- element.setAttribute('draggable', 'false')
266
- // Add click prevention to slides
267
- element.removeEventListener('click', preventDefaultClick, true)
268
- element.addEventListener('click', preventDefaultClick, true)
269
- })
270
- }
271
-
272
- function startDrag(e: MouseEvent) {
273
- e.stopPropagation()
274
- if (e.button !== 0 || !props.freeDrag || !isSliderAvailable.value) { return }
275
-
276
- clearAutoplay()
277
- startX.value = e.pageX
278
- translateX.value = getCurrentTransform()
279
- isPressed.value = true
280
- isDragging.value = false // Reset dragging state on start
281
-
282
- document.addEventListener('mousemove', onDrag)
283
- document.addEventListener('mouseup', endDrag)
284
- }
285
-
286
- function onDrag(e: MouseEvent) {
287
- if (!isSliderAvailable.value || !bglSlider.value || !isPressed.value) { return }
288
-
289
- const x = e.pageX
290
- const distance = x - startX.value
291
-
292
- if (Math.abs(distance) > THRESHOLDS.DRAG) { isDragging.value = true }
293
- if (isDragging.value) {
294
- const newTranslate = translateX.value + (props.rtl ? -distance : distance)
295
- const maxTranslate = 0
296
-
297
- const containerWidth = bglSlider.value.offsetWidth
298
- const gapWidth = (containerWidth * GAP_PERCENT) / 100
299
- const totalGapWidth = (itemCount.value - 1) * gapWidth
300
- const singleItemWidth = (containerWidth - totalGapWidth) / itemCount.value
301
- const minTranslate = -(singleItemWidth * (slideCount.value - 1) + gapWidth * (slideCount.value - 1))
302
-
303
- const boundedTranslate = Math.max(minTranslate, Math.min(maxTranslate, newTranslate))
304
- setTransform(boundedTranslate)
305
- }
306
- }
307
-
308
- function endDrag() {
309
- isPressed.value = false
310
- document.removeEventListener('mousemove', onDrag)
311
- document.removeEventListener('mouseup', endDrag)
312
-
313
- if (!isSliderAvailable.value || !bglSlider.value) { return }
314
-
315
- const currentTransform = getCurrentTransform()
316
- const singleItemWidth = bglSlider.value.offsetWidth / itemCount.value
317
- const currentPanel = -currentTransform / singleItemWidth
318
-
319
- const totalDragDistance = currentTransform - translateX.value
320
- const dragPercentage = Math.abs(totalDragDistance) / singleItemWidth
321
-
322
- let targetPanel = currentPanel
323
- if (dragPercentage > THRESHOLDS.SWIPE_PERCENT) {
324
- const distnace = totalDragDistance > 0 ? -1 : 1
325
- targetPanel = Math.floor(currentPanel) + (distnace < 0 ? 0 : 1)
326
- } else {
327
- targetPanel = Math.round(currentPanel)
328
- }
329
-
330
- targetPanel = Math.max(0, Math.min(targetPanel, slideCount.value - 1))
331
- goToSlide(targetPanel, true)
332
-
333
- // Keep isDragging true for a short period to prevent click
334
- if (isDragging.value) {
335
- setTimeout(() => {
336
- isDragging.value = false
337
- }, 10) // Short timeout to ensure click event is handled first
338
- }
339
- }
340
-
341
- // Add a new state variable for tracking scroll direction
342
- const hasScrollDirectionLock = ref(false)
343
- const isHorizontalScroll = ref(false)
344
-
345
- function onTouchStart(e: TouchEvent) {
346
- if (!props.freeDrag || !isSliderAvailable.value) { return }
347
-
348
- const target = e.target as HTMLElement
349
- const isInteractive = target.matches('button, a, input, select, textarea, [role="button"]')
350
- || target.closest('button, a, input, select, textarea, [role="button"]')
351
-
352
- if (isInteractive === false || isInteractive === null) {
353
- // Don't prevent default immediately - let's wait to see the direction
354
- clearAutoplay()
355
- const [touch] = e.touches
356
- startX.value = touch.pageX
357
- startY.value = touch.pageY
358
- lastX.value = touch.pageX
359
- lastTime.value = Date.now()
360
- translateX.value = getCurrentTransform()
361
- velocityTracker.value = [{ time: lastTime.value, position: lastX.value }]
362
- isPressed.value = true
363
- hasScrollDirectionLock.value = false
364
-
365
- // Ensure no transition is active when starting touch
366
- if (bglSlider.value) {
367
- bglSlider.value.style.transition = 'none'
368
- }
369
- }
370
- }
371
-
372
- function onTouchMove(e: TouchEvent) {
373
- if (!isSliderAvailable.value || !bglSlider.value || !isPressed.value) { return }
374
-
375
- const [touch] = e.touches
376
- const x = touch.pageX
377
- const y = touch.pageY
378
- const deltaX = x - startX.value
379
- const deltaY = y - startY.value
380
-
381
- // Determine scroll direction if we haven't already
382
- if (!hasScrollDirectionLock.value) {
383
- // Only lock in a direction if there's significant movement
384
- if (Math.abs(deltaX) > 5 || Math.abs(deltaY) > 5) {
385
- isHorizontalScroll.value = Math.abs(deltaX) > Math.abs(deltaY)
386
- hasScrollDirectionLock.value = true
387
-
388
- // If it's vertical scrolling, release the touch capture
389
- if (!isHorizontalScroll.value) {
390
- isPressed.value = false
391
- return
392
- }
393
- }
394
- // Wait for more movement before deciding
395
- return
396
- }
397
-
398
- // If we've determined this is a vertical scroll, don't handle the touch
399
- if (!isHorizontalScroll.value) { return }
400
-
401
- // Update velocity tracking
402
- const now = Date.now()
403
- velocityTracker.value.push({ time: now, position: x })
404
- lastX.value = x
405
-
406
- // Keep only recent samples
407
- const cutoffTime = now - VELOCITY_SAMPLE_DURATION
408
- velocityTracker.value = velocityTracker.value.filter((sample: VelocitySample) => sample.time >= cutoffTime)
409
-
410
- if (Math.abs(deltaX) > THRESHOLDS.TOUCH) {
411
- isDragging.value = true
412
- e.preventDefault() // Only prevent default if we're sure it's a horizontal drag
413
- }
414
-
415
- if (isDragging.value) {
416
- const containerWidth = bglSlider.value.offsetWidth
417
- const gapWidth = (containerWidth * GAP_PERCENT) / 100
418
- const totalGapWidth = (itemCount.value - 1) * gapWidth
419
- const singleItemWidth = (containerWidth - totalGapWidth) / itemCount.value
420
- const totalWidth = (singleItemWidth * slideCount.value) + (gapWidth * (slideCount.value - 1))
421
-
422
- // Calculate the new position based on touch movement
423
- let newTranslate = translateX.value + (props.rtl ? -deltaX : deltaX)
424
-
425
- // Add smooth resistance when pulling beyond bounds
426
- if (newTranslate > 0) {
427
- newTranslate *= 0.5 // Softer resistance at start
428
- } else if (newTranslate < -totalWidth + containerWidth) {
429
- const overDrag = -totalWidth + containerWidth - newTranslate
430
- newTranslate = -totalWidth + containerWidth - (overDrag * 0.5) // Softer resistance at end
431
- }
432
-
433
- // Apply the transform immediately without any easing
434
- setTransform(newTranslate)
435
- }
436
- }
437
-
438
- function onTouchEnd() {
439
- if (!isSliderAvailable.value || !bglSlider.value || !isPressed.value || !isHorizontalScroll.value) {
440
- isPressed.value = false
441
- hasScrollDirectionLock.value = false
442
- return
443
- }
444
-
445
- const containerWidth = bglSlider.value.offsetWidth
446
- const gapWidth = (containerWidth * GAP_PERCENT) / 100
447
- const totalGapWidth = (itemCount.value - 1) * gapWidth
448
- const singleItemWidth = (containerWidth - totalGapWidth) / itemCount.value
449
- const totalSlideWidth = singleItemWidth + gapWidth
450
-
451
- const currentTransform = getCurrentTransform()
452
-
453
- // Normalize the transform value to handle overscroll
454
- let normalizedTransform = currentTransform
455
- if (currentTransform > 0) {
456
- normalizedTransform = 0
457
- } else {
458
- const minTransform = -(singleItemWidth * (slideCount.value - 1) + gapWidth * (slideCount.value - 1))
459
- if (currentTransform < minTransform) {
460
- normalizedTransform = minTransform
461
- }
462
- }
463
-
464
- const currentPanel = Math.abs(normalizedTransform / totalSlideWidth)
465
-
466
- const totalSwipeDistance = lastX.value - startX.value
467
- const swipePercentage = Math.abs(totalSwipeDistance) / totalSlideWidth
468
-
469
- // Get final velocity from tracker
470
- const finalVelocity = getAverageVelocity()
471
-
472
- let targetPanel = currentPanel
473
- let velocityDirection = 0
474
-
475
- // Determine direction based on velocity and distance
476
- if (Math.abs(finalVelocity) > THRESHOLDS.VELOCITY) {
477
- velocityDirection = finalVelocity < 0 ? 1 : -1
478
- }
479
-
480
- const isMobile = window.innerWidth < 600
481
-
482
- if (swipePercentage > THRESHOLDS.SWIPE_PERCENT || Math.abs(finalVelocity) > THRESHOLDS.VELOCITY) {
483
- const direction = swipePercentage > THRESHOLDS.SWIPE_PERCENT
484
- ? (totalSwipeDistance > 0 ? -1 : 1)
485
- : velocityDirection
486
-
487
- // Apply RTL correction to direction
488
- const rtlDirection = props.rtl ? -direction : direction
489
-
490
- if (isMobile) {
491
- // On mobile, only move one slide at a time
492
- targetPanel = activeSlideIndex.value + rtlDirection
493
- } else {
494
- // On desktop, keep the current behavior
495
- targetPanel = Math.round(currentPanel) + rtlDirection
496
- }
497
- } else {
498
- // If the swipe wasn't strong enough, snap back to the current slide
499
- targetPanel = isMobile ? activeSlideIndex.value : Math.round(currentPanel)
500
- }
501
-
502
- targetPanel = Math.max(0, Math.min(targetPanel, slideCount.value - 1))
503
- activeSlideIndex.value = targetPanel
504
-
505
- // Adjust animation duration based on velocity and distance to travel
506
- const distance = Math.abs(targetPanel - currentPanel)
507
- const velocityFactor = Math.min(Math.abs(finalVelocity) * 1000, 1)
508
- const duration = Math.min(
509
- ANIMATION_TIMINGS.BASE_DURATION * (1 - velocityFactor * 0.5) + (distance * ANIMATION_TIMINGS.DURATION_PER_PANEL),
510
- ANIMATION_TIMINGS.MAX_DURATION
511
- )
512
-
513
- // Re-enable transitions for the snap animation
514
- bglSlider.value.style.transition = `transform ${duration}ms cubic-bezier(0.4, 0, 0.2, 1)`
515
-
516
- const gapsBeforeSlide = targetPanel * gapWidth
517
- const targetTransform = -(singleItemWidth * targetPanel + gapsBeforeSlide)
518
- setTransform(targetTransform)
519
-
520
- // Clear transition after animation
521
- setTimeout(() => {
522
- if (bglSlider.value) { bglSlider.value.style.transition = 'none' }
523
- }, duration)
524
-
525
- velocityTracker.value = []
526
- isDragging.value = false
527
- hasScrollDirectionLock.value = false
528
- }
529
-
530
- // Wheel events
531
- function onWheel(e: WheelEvent) {
532
- if (!props.allowScroll || !isSliderAvailable.value || !bglSlider.value || isPressed.value || isDragging.value) { return }
533
-
534
- const isHorizontal = Math.abs(e.deltaX) > Math.abs(e.deltaY)
535
- if (!isHorizontal) { return }
536
-
537
- clearAutoplay()
538
- if (wheelTimeout) { clearTimeout(wheelTimeout) }
539
-
540
- // Reverse the delta direction for RTL mode
541
- accumulatedDeltaX.value += props.rtl ? -e.deltaX : e.deltaX
542
-
543
- wheelTimeout = setTimeout(() => {
544
- accumulatedDeltaX.value = 0
545
- }, 50)
546
-
547
- const singleItemWidth = bglSlider.value.offsetWidth / itemCount.value
548
- const moveThreshold = singleItemWidth * THRESHOLDS.WHEEL_PERCENT
549
-
550
- if (Math.abs(accumulatedDeltaX.value) > moveThreshold) {
551
- const direction = accumulatedDeltaX.value > 0 ? 1 : -1
552
- const nextPanel = Math.max(0, Math.min(activeSlideIndex.value + direction, slideCount.value - 1))
553
-
554
- if (nextPanel !== activeSlideIndex.value) {
555
- goToSlide(nextPanel, true)
556
- accumulatedDeltaX.value = 0
557
- }
558
- }
559
- }
560
-
561
- // Lifecycle
562
- onMounted(() => {
563
- window.addEventListener('resize', handleResize)
564
- updateHeight()
565
- handleResize()
566
- countSlides()
567
- disableDrag()
568
-
569
- if (bglSlider.value) {
570
- slideCount.value = bglSlider.value.children.length
571
- }
572
-
573
- // If starting at a non-zero index, jump to it (for loop mode)
574
- if (props.index > 0) {
575
- jumpToSlide(props.index)
576
- }
577
-
578
- if (props.autoplay) {
579
- autoPlayInterval = setInterval(next, props.autoPlaySpeed)
580
- }
581
- })
582
-
583
- onUnmounted(() => {
584
- if (isSliderAvailable.value && bglSlider.value) {
585
- Array.from(bglSlider.value.children).forEach((child) => {
586
- const element = child as HTMLElement
587
- element.removeEventListener('click', preventDefaultClick)
588
- })
589
- }
590
- window.removeEventListener('resize', handleResize)
591
- document.removeEventListener('mousemove', onDrag)
592
- document.removeEventListener('mouseup', endDrag)
593
- clearAutoplay()
594
- if (timeout) { clearTimeout(timeout) }
595
- if (wheelTimeout) { clearTimeout(wheelTimeout) }
596
- })
597
-
598
- // Watchers
599
- watch(() => props.index, (newIndex: number) => { goToSlide(newIndex) })
600
- watch(() => activeSlideIndex.value, handleSlideChange)
601
-
602
- defineExpose({
603
- goToSlide,
604
- jumpToSlide,
605
- next,
606
- prev,
607
- countSlides,
608
- clearAutoplay,
609
- })
610
- </script>
611
-
612
- <template>
613
- <div class="BglCarousel" :dir="rtl ? 'rtl' : 'ltr'">
614
- <div
615
- ref="bglSlider" class="bgl-slider"
616
- :class="{ dragging: isDragging, clicking: isPressed, [`slides-${itemCount}`]: true, grab: freeDrag && slideCount > 1, autoHeight }"
617
- :style="{ '--item-count': itemCount, 'height': yHeight }" @mousedown="startDrag" @mouseover="clearAutoplay"
618
- @focusin="clearAutoplay" @touchstart="onTouchStart" @touchmove="onTouchMove" @touchend="onTouchEnd"
619
- @wheel="onWheel"
620
- >
621
- <slot />
622
- </div>
623
- <div v-if="props.dots && slideCount > 1" class="dots">
624
- <span
625
- v-for="i in slideCount" :key="i" class="dot" :class="{ current: activeSlideIndex === i - 1 }"
626
- @click="goToSlide(i - 1)"
627
- />
628
- </div>
629
- <div class="navigation-buttons" :class="{ rtl }">
630
- <span class="prev" @click="prev">
631
- <slot name="prev" :index="activeSlideIndex" :prev="prev" />
632
- </span>
633
- <span class="next" @click="next">
634
- <slot name="next" :index="activeSlideIndex" :next="next" />
635
- </span>
636
- </div>
637
- </div>
638
- </template>
639
-
640
- <style scoped>
641
- .BglCarousel {
642
- position: relative;
643
- width: 100%;
644
- overflow: hidden;
645
- touch-action: pan-y pinch-zoom;
646
- /* Allow vertical scrolling */
647
- }
648
-
649
- .bgl-slider {
650
- display: flex;
651
- position: relative;
652
- width: 100%;
653
- touch-action: pan-y pinch-zoom;
654
- /* Allow vertical scrolling */
655
- will-change: transform;
656
- transform: translateX(0);
657
- gap: 1%;
658
- transition: none;
659
- -webkit-user-select: none;
660
- user-select: none;
661
- }
662
-
663
- .bgl-slider>* {
664
- flex: 0 0 calc((100% - (var(--item-count) - 1) * 1%) / var(--item-count));
665
- width: calc((100% - (var(--item-count) - 1) * 1%) / var(--item-count));
666
- min-width: calc((100% - (var(--item-count) - 1) * 1%) / var(--item-count));
667
- position: relative;
668
- overflow: hidden;
669
- }
670
-
671
- .bgl-slider.slides-1>* {
672
- flex: 0 0 100%;
673
- width: 100%;
674
- min-width: 100%;
675
- }
676
-
677
- .dragging .bgl-slider>* {
678
- pointer-events: none;
679
- user-select: none;
680
- }
681
-
682
- .autoHeight {
683
- transition: height ease 0.7s;
684
- }
685
-
686
- .dots {
687
- display: flex;
688
- justify-content: center;
689
- align-items: center;
690
- width: 100%;
691
- margin-top: 1rem;
692
- gap: 8px;
693
- }
694
-
695
- .dot {
696
- height: 10px;
697
- width: 10px;
698
- border-radius: 50%;
699
- background-color: var(--bgl-black);
700
- opacity: 0.4;
701
- transition: opacity 0.3s ease;
702
- cursor: pointer;
703
- }
704
-
705
- .dot:hover {
706
- opacity: 0.6;
707
- }
708
-
709
- .dot.current {
710
- opacity: 0.8;
711
- }
712
-
713
- .navigation-buttons {
714
- display: flex;
715
- justify-content: center;
716
- align-items: center;
717
- gap: 1rem;
718
- margin-top: 1rem;
719
- }
720
-
721
- .navigation-buttons.rtl {
722
- flex-direction: row-reverse;
723
- }
724
- </style>