@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.
- package/dist/components/Alert.vue.d.ts.map +1 -1
- package/dist/components/Badge.vue.d.ts.map +1 -1
- package/dist/components/Btn.vue.d.ts.map +1 -1
- package/dist/components/Dropdown.vue.d.ts.map +1 -1
- package/dist/components/Image.vue.d.ts.map +1 -1
- package/dist/components/ListItem.vue.d.ts.map +1 -1
- package/dist/components/MapEmbed/Index.vue.d.ts.map +1 -1
- package/dist/components/Pagination.vue.d.ts.map +1 -1
- package/dist/components/Swiper.vue.d.ts.map +1 -1
- package/dist/components/Toast.vue.d.ts.map +1 -1
- package/dist/components/form/index.d.ts.map +1 -1
- package/dist/components/form/inputs/SelectInput.vue.d.ts.map +1 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/layout/AppContent.vue.d.ts.map +1 -1
- package/dist/components/layout/AppLayout.vue.d.ts.map +1 -1
- package/dist/components/layout/AppSidebar.vue.d.ts.map +1 -1
- package/dist/components/layout/Panel.vue.d.ts.map +1 -1
- package/dist/components/layout/Resizable.vue.d.ts.map +1 -1
- package/dist/components/layout/TabsNav.vue.d.ts.map +1 -1
- package/dist/components/layout/appLayoutContext.d.ts +24 -0
- package/dist/components/layout/appLayoutContext.d.ts.map +1 -0
- package/dist/components/layout/index.d.ts.map +1 -1
- package/dist/components/lightbox/Lightbox.vue.d.ts.map +1 -1
- package/dist/composables/index.d.ts.map +1 -1
- package/dist/composables/useDevice.d.ts.map +1 -1
- package/dist/composables/useEscapeKey.d.ts +12 -0
- package/dist/composables/useEscapeKey.d.ts.map +1 -0
- package/dist/composables/useSchemaField.d.ts.map +1 -1
- package/dist/composables/useTheme.d.ts.map +1 -1
- package/dist/form-flow/FormFlow.vue.d.ts.map +1 -1
- package/dist/form-flow/form-flow.d.ts.map +1 -1
- package/dist/index.cjs +203 -207
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +25819 -28870
- package/dist/style.css +1 -1
- package/dist/types/BagelForm.d.ts.map +1 -1
- package/dist/types/BtnOptions.d.ts.map +1 -1
- package/dist/utils/constants.d.ts.map +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/package.json +3 -6
- package/src/components/Alert.vue +34 -14
- package/src/components/Badge.vue +145 -22
- package/src/components/Btn.vue +43 -31
- package/src/components/Dropdown.vue +5 -12
- package/src/components/FilterQuery.vue +1 -1
- package/src/components/Image.vue +3 -2
- package/src/components/JSONSchema.vue +2 -2
- package/src/components/JsonBuilder.vue +1 -1
- package/src/components/ListItem.vue +1 -3
- package/src/components/MapEmbed/Index.vue +10 -9
- package/src/components/NavBar.vue +2 -2
- package/src/components/Spreadsheet/Index.vue +1 -1
- package/src/components/Swiper.vue +3 -1
- package/src/components/Toast.vue +23 -8
- package/src/components/calendar/Index.vue +4 -4
- package/src/components/calendar/views/MonthView.vue +3 -3
- package/src/components/form/index.ts +0 -4
- package/src/components/form/inputs/EmailInput.vue +1 -1
- package/src/components/form/inputs/NumberInput.vue +1 -1
- package/src/components/form/inputs/OTP.vue +2 -2
- package/src/components/form/inputs/SelectInput.vue +3 -3
- package/src/components/form/inputs/TelInput.vue +2 -2
- package/src/components/form/inputs/TextInput.vue +1 -1
- package/src/components/form/inputs/Upload/upload.css +2 -2
- package/src/components/index.ts +2 -6
- package/src/components/layout/AppContent.vue +5 -19
- package/src/components/layout/AppLayout.vue +47 -18
- package/src/components/layout/AppSidebar.vue +16 -33
- package/src/components/layout/Resizable.vue +5 -2
- package/src/components/layout/TabsNav.vue +5 -5
- package/src/components/layout/appLayoutContext.ts +44 -0
- package/src/components/layout/index.ts +2 -0
- package/src/components/lightbox/Lightbox.vue +3 -9
- package/src/composables/index.ts +1 -0
- package/src/composables/useDevice.ts +2 -1
- package/src/composables/useEscapeKey.ts +56 -0
- package/src/composables/useSchemaField.ts +2 -17
- package/src/composables/useTheme.ts +23 -19
- package/src/form-flow/FormFlow.vue +2 -0
- package/src/form-flow/form-flow.ts +7 -0
- package/src/index.ts +0 -2
- package/src/styles/inputs.css +1 -1
- package/src/types/BagelForm.ts +46 -151
- package/src/types/BtnOptions.ts +5 -3
- package/src/utils/constants.ts +7 -0
- package/src/utils/index.ts +19 -3
- package/src/utils/sizeParsing.ts +5 -5
- package/vite.config.ts +5 -1
- package/src/components/Carousel.vue +0 -724
- package/src/components/ImportData.vue +0 -1749
- package/src/components/Pill.vue +0 -150
- package/src/components/Slider.vue +0 -1446
- package/src/components/Title.vue +0 -23
- package/src/components/ToolBar.vue +0 -9
- package/src/components/form/BagelForm.vue +0 -219
- package/src/components/form/BglFieldSet.vue +0 -14
- package/src/components/form/BglMultiStepForm.vue +0 -469
- package/src/components/form/FieldArray.vue +0 -422
- package/src/components/form/useBagelFormState.ts +0 -76
- package/src/composables/useFormField.ts +0 -38
- package/src/dialog/DialogOLD.vue +0 -358
- 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>
|