@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,1446 +0,0 @@
|
|
|
1
|
-
<!-- eslint-disable vue/multi-word-component-names -->
|
|
2
|
-
<script setup lang="ts">
|
|
3
|
-
import { sleep, useDevice } from '@bagelink/vue'
|
|
4
|
-
import { ref, onMounted, onBeforeUnmount, watch, computed, nextTick, onUpdated } from 'vue'
|
|
5
|
-
|
|
6
|
-
type Easing = 'ease-in-out' | 'ease-in' | 'ease-out' | 'linear'
|
|
7
|
-
type AutoplayMode = 'disabled' | 'standard' | 'linear'
|
|
8
|
-
type AutoplayValue = boolean | AutoplayMode
|
|
9
|
-
|
|
10
|
-
interface CarouselOptions {
|
|
11
|
-
speed?: number
|
|
12
|
-
easing?: Easing
|
|
13
|
-
perPage?: number | { [key: number]: number }
|
|
14
|
-
slideWidth?: number
|
|
15
|
-
slideGap?: number
|
|
16
|
-
startIndex?: number
|
|
17
|
-
draggable?: boolean
|
|
18
|
-
multipleDrag?: boolean
|
|
19
|
-
threshold?: number
|
|
20
|
-
loop?: boolean
|
|
21
|
-
rtl?: boolean
|
|
22
|
-
autoplay?: AutoplayValue
|
|
23
|
-
autoplayInterval?: number
|
|
24
|
-
autoplaySpeed?: number
|
|
25
|
-
pauseOnHover?: boolean
|
|
26
|
-
dots?: boolean
|
|
27
|
-
onInit?: () => void
|
|
28
|
-
onChange?: () => void
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
interface DragState {
|
|
32
|
-
startX: number
|
|
33
|
-
endX: number
|
|
34
|
-
startY: number
|
|
35
|
-
letItGo: boolean | null
|
|
36
|
-
preventClick: boolean
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const props = withDefaults(defineProps<CarouselOptions>(), {
|
|
40
|
-
speed: 350,
|
|
41
|
-
easing: 'ease-out',
|
|
42
|
-
startIndex: 0,
|
|
43
|
-
draggable: true,
|
|
44
|
-
multipleDrag: true,
|
|
45
|
-
threshold: 20,
|
|
46
|
-
loop: true,
|
|
47
|
-
rtl: false,
|
|
48
|
-
autoplay: 'disabled',
|
|
49
|
-
autoplayInterval: 5000,
|
|
50
|
-
pauseOnHover: true,
|
|
51
|
-
dots: false,
|
|
52
|
-
slideWidth: 300,
|
|
53
|
-
slideGap: 1
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
const emit = defineEmits<{
|
|
57
|
-
(event: 'init'): void
|
|
58
|
-
(event: 'change'): void
|
|
59
|
-
}>()
|
|
60
|
-
|
|
61
|
-
// Basic state refs
|
|
62
|
-
const carouselRef = ref<HTMLElement | null>(null)
|
|
63
|
-
const sliderFrame = ref<HTMLDivElement | null>(null)
|
|
64
|
-
const innerElements = ref<Element[]>([])
|
|
65
|
-
const currentSlide = ref(0)
|
|
66
|
-
const selectorWidth = ref(0)
|
|
67
|
-
const { innerWidth } = useDevice()
|
|
68
|
-
const transformProperty = ref('transform')
|
|
69
|
-
const pointerDown = ref(false)
|
|
70
|
-
|
|
71
|
-
// Add a flag to track if we're in the middle of initializing or rebuilding
|
|
72
|
-
const isBuilding = ref(false)
|
|
73
|
-
|
|
74
|
-
// Drag-related refs
|
|
75
|
-
const drag = ref<DragState>({
|
|
76
|
-
startX: 0,
|
|
77
|
-
endX: 0,
|
|
78
|
-
startY: 0,
|
|
79
|
-
letItGo: null,
|
|
80
|
-
preventClick: false
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
// Add a new variable to track actual dragging state
|
|
84
|
-
const isDragging = ref(false)
|
|
85
|
-
const dragThreshold = 10 // Increase threshold slightly for better detection
|
|
86
|
-
|
|
87
|
-
// Add autoplay-related refs
|
|
88
|
-
const autoplayTimer = ref<number | null>(null)
|
|
89
|
-
const linearAnimationFrame = ref<number | null>(null)
|
|
90
|
-
const isHovering = ref(false)
|
|
91
|
-
const linearOffset = ref(0)
|
|
92
|
-
|
|
93
|
-
// Add a counter to track rebuild attempts with no items
|
|
94
|
-
const emptyRebuildAttempts = ref(0)
|
|
95
|
-
const MAX_EMPTY_REBUILD_ATTEMPTS = 3
|
|
96
|
-
|
|
97
|
-
// Modified to work with optional parameters and ensure compatibility with Required type
|
|
98
|
-
type ComputedConfig = Omit<Required<CarouselOptions>, 'autoplay'> & { autoplay: AutoplayMode }
|
|
99
|
-
|
|
100
|
-
// perPage calculation - independent of config
|
|
101
|
-
const perPage = computed(() => {
|
|
102
|
-
if (typeof props.perPage === 'number') {
|
|
103
|
-
return props.perPage
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (typeof props.perPage === 'object') {
|
|
107
|
-
return Object.entries(props.perPage)
|
|
108
|
-
.reduce((acc, [viewport, value]) => innerWidth.value >= Number(viewport) ? value : acc, 1)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// If perPage is undefined, calculate based on slideWidth
|
|
112
|
-
return Math.max(1, Math.ceil(selectorWidth.value / props.slideWidth))
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
// Core state
|
|
116
|
-
const config = computed<ComputedConfig>(() => {
|
|
117
|
-
// Convert boolean autoplay values to string mode
|
|
118
|
-
let autoplayMode: AutoplayMode = 'disabled'
|
|
119
|
-
|
|
120
|
-
if (typeof props.autoplay === 'boolean') {
|
|
121
|
-
autoplayMode = props.autoplay ? 'standard' : 'disabled'
|
|
122
|
-
} else {
|
|
123
|
-
autoplayMode = props.autoplay as AutoplayMode
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Calculate perPage fallback based on config
|
|
127
|
-
const calculatedPerPage = perPage.value
|
|
128
|
-
|
|
129
|
-
return {
|
|
130
|
-
speed: props.speed,
|
|
131
|
-
easing: props.easing,
|
|
132
|
-
perPage: calculatedPerPage,
|
|
133
|
-
startIndex: props.startIndex,
|
|
134
|
-
draggable: props.draggable,
|
|
135
|
-
multipleDrag: props.multipleDrag,
|
|
136
|
-
threshold: props.threshold,
|
|
137
|
-
loop: props.loop,
|
|
138
|
-
rtl: props.rtl,
|
|
139
|
-
autoplay: autoplayMode,
|
|
140
|
-
autoplayInterval: props.autoplayInterval,
|
|
141
|
-
autoplaySpeed: props.speed / 4,
|
|
142
|
-
pauseOnHover: props.pauseOnHover,
|
|
143
|
-
dots: props.dots,
|
|
144
|
-
slideWidth: props.slideWidth,
|
|
145
|
-
slideGap: props.slideGap,
|
|
146
|
-
onInit: () => { emit('init') },
|
|
147
|
-
onChange: () => { emit('change') }
|
|
148
|
-
}
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
// Computed property for total number of dots to display
|
|
152
|
-
const totalDots = computed(() => {
|
|
153
|
-
if (!innerElements.value.length) { return 0 }
|
|
154
|
-
return config.value.loop ? innerElements.value.length : Math.ceil(innerElements.value.length - perPage.value + 1)
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
// Add MutationObserver ref
|
|
158
|
-
const slotObserver = ref<MutationObserver | null>(null)
|
|
159
|
-
|
|
160
|
-
// Add a flag to track component mounted state
|
|
161
|
-
const isMounted = ref(false)
|
|
162
|
-
|
|
163
|
-
// Add a reference to the slot container
|
|
164
|
-
const slotContainer = ref<HTMLDivElement | null>(null)
|
|
165
|
-
|
|
166
|
-
// Lifecycle hooks
|
|
167
|
-
onMounted(() => {
|
|
168
|
-
// Set the mounted flag first
|
|
169
|
-
isMounted.value = true
|
|
170
|
-
|
|
171
|
-
// Wait a tick to ensure slot content is rendered
|
|
172
|
-
nextTick(() => {
|
|
173
|
-
init()
|
|
174
|
-
})
|
|
175
|
-
})
|
|
176
|
-
|
|
177
|
-
onBeforeUnmount(() => {
|
|
178
|
-
// Clean up
|
|
179
|
-
isMounted.value = false
|
|
180
|
-
stopAutoplay()
|
|
181
|
-
detachEvents()
|
|
182
|
-
|
|
183
|
-
// Remove the observer
|
|
184
|
-
if (slotObserver.value) {
|
|
185
|
-
slotObserver.value.disconnect()
|
|
186
|
-
slotObserver.value = null
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Clear DOM content
|
|
190
|
-
if (carouselRef.value) {
|
|
191
|
-
carouselRef.value.innerHTML = ''
|
|
192
|
-
}
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
// Add an onUpdated hook that checks for slot content changes
|
|
196
|
-
onUpdated(() => {
|
|
197
|
-
// Only check if we're already initialized
|
|
198
|
-
if (isMounted.value && !isBuilding.value && emptyRebuildAttempts.value < MAX_EMPTY_REBUILD_ATTEMPTS) {
|
|
199
|
-
// Check if slot content changed
|
|
200
|
-
if (slotContainer.value) {
|
|
201
|
-
const slotElements = Array.from(slotContainer.value.children)
|
|
202
|
-
|
|
203
|
-
// Only update if count changed or first initialization
|
|
204
|
-
if (slotElements.length !== innerElements.value.length || innerElements.value.length === 0) {
|
|
205
|
-
// Clone the elements from slot
|
|
206
|
-
innerElements.value = slotElements.map(el => el.cloneNode(true) as Element)
|
|
207
|
-
|
|
208
|
-
// Schedule rebuild
|
|
209
|
-
nextTick(() => {
|
|
210
|
-
updateSlider()
|
|
211
|
-
})
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
})
|
|
216
|
-
|
|
217
|
-
// Watch for changes to props
|
|
218
|
-
watch(() => props, () => {
|
|
219
|
-
// Props changed, might need to rebuild
|
|
220
|
-
resizeHandler()
|
|
221
|
-
}, { deep: true })
|
|
222
|
-
|
|
223
|
-
// Helper functions
|
|
224
|
-
function webkitOrNot(): string {
|
|
225
|
-
const { style } = document.documentElement
|
|
226
|
-
if (typeof style.transform === 'string') {
|
|
227
|
-
return 'transform'
|
|
228
|
-
}
|
|
229
|
-
return 'WebkitTransform'
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Event handlers
|
|
233
|
-
function attachEvents(): void {
|
|
234
|
-
window.addEventListener('resize', resizeHandler)
|
|
235
|
-
|
|
236
|
-
if (config.value.draggable && carouselRef.value) {
|
|
237
|
-
const eventOptions = { passive: true }
|
|
238
|
-
const touchMoveOptions = { passive: false }
|
|
239
|
-
const mouseMoveOptions = { passive: false } // Non-passive option for mouse move
|
|
240
|
-
const mouseDownOptions = { passive: false } // Non-passive option for mouse down
|
|
241
|
-
|
|
242
|
-
// Touch events
|
|
243
|
-
carouselRef.value.addEventListener('touchstart', touchstartHandler, eventOptions)
|
|
244
|
-
carouselRef.value.addEventListener('touchend', touchendHandler, eventOptions)
|
|
245
|
-
carouselRef.value.addEventListener('touchmove', touchmoveHandler, touchMoveOptions)
|
|
246
|
-
|
|
247
|
-
// Mouse events
|
|
248
|
-
carouselRef.value.addEventListener('mousedown', mousedownHandler, mouseDownOptions)
|
|
249
|
-
carouselRef.value.addEventListener('mouseup', mouseupHandler, eventOptions)
|
|
250
|
-
carouselRef.value.addEventListener('mouseleave', mouseleaveHandler, eventOptions)
|
|
251
|
-
carouselRef.value.addEventListener('mousemove', mousemoveHandler, mouseMoveOptions)
|
|
252
|
-
|
|
253
|
-
// Prevent default dragging of images and links
|
|
254
|
-
carouselRef.value.addEventListener('dragstart', dragstartHandler)
|
|
255
|
-
|
|
256
|
-
// Click
|
|
257
|
-
carouselRef.value.addEventListener('click', clickHandler)
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Add mouse enter/leave events for pauseOnHover functionality
|
|
261
|
-
if (config.value.autoplay !== 'disabled' && config.value.pauseOnHover && carouselRef.value) {
|
|
262
|
-
carouselRef.value.addEventListener('mouseenter', mouseenterHandler)
|
|
263
|
-
carouselRef.value.addEventListener('mouseleave', mouseleaveAutoplayHandler)
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
function detachEvents(): void {
|
|
268
|
-
window.removeEventListener('resize', resizeHandler)
|
|
269
|
-
|
|
270
|
-
if (carouselRef.value) {
|
|
271
|
-
carouselRef.value.removeEventListener('touchstart', touchstartHandler)
|
|
272
|
-
carouselRef.value.removeEventListener('touchend', touchendHandler)
|
|
273
|
-
carouselRef.value.removeEventListener('touchmove', touchmoveHandler)
|
|
274
|
-
carouselRef.value.removeEventListener('mousedown', mousedownHandler)
|
|
275
|
-
carouselRef.value.removeEventListener('mouseup', mouseupHandler)
|
|
276
|
-
carouselRef.value.removeEventListener('mouseleave', mouseleaveHandler)
|
|
277
|
-
carouselRef.value.removeEventListener('mousemove', mousemoveHandler)
|
|
278
|
-
carouselRef.value.removeEventListener('dragstart', dragstartHandler)
|
|
279
|
-
carouselRef.value.removeEventListener('click', clickHandler)
|
|
280
|
-
carouselRef.value.removeEventListener('mouseenter', mouseenterHandler)
|
|
281
|
-
carouselRef.value.removeEventListener('mouseleave', mouseleaveAutoplayHandler)
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// Core functionality
|
|
286
|
-
async function init(): Promise<void> {
|
|
287
|
-
if (!carouselRef.value || !slotContainer.value || !isMounted.value) { return }
|
|
288
|
-
|
|
289
|
-
transformProperty.value = webkitOrNot()
|
|
290
|
-
|
|
291
|
-
// Add a small delay to ensure DOM is ready
|
|
292
|
-
await sleep(10)
|
|
293
|
-
|
|
294
|
-
// Create references by cloning slot content
|
|
295
|
-
innerElements.value = Array.from(slotContainer.value.children).map(
|
|
296
|
-
el => el.cloneNode(true) as Element
|
|
297
|
-
)
|
|
298
|
-
|
|
299
|
-
if (!innerElements.value.length) {
|
|
300
|
-
console.warn('No carousel items found during initialization')
|
|
301
|
-
return
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
selectorWidth.value = carouselRef.value.offsetWidth
|
|
305
|
-
|
|
306
|
-
// Set current slide
|
|
307
|
-
currentSlide.value = config.value.loop
|
|
308
|
-
? config.value.startIndex % innerElements.value.length
|
|
309
|
-
: Math.max(0, Math.min(config.value.startIndex, innerElements.value.length - perPage.value))
|
|
310
|
-
|
|
311
|
-
// Hide overflow
|
|
312
|
-
carouselRef.value.style.overflow = 'hidden'
|
|
313
|
-
|
|
314
|
-
// Set RTL direction if needed
|
|
315
|
-
carouselRef.value.style.direction = config.value.rtl ? 'rtl' : 'ltr'
|
|
316
|
-
|
|
317
|
-
// Attach events
|
|
318
|
-
attachEvents()
|
|
319
|
-
|
|
320
|
-
// Set up the MutationObserver to watch for slot changes
|
|
321
|
-
setupSlotObserver()
|
|
322
|
-
|
|
323
|
-
// Build slider frame
|
|
324
|
-
buildSliderFrame()
|
|
325
|
-
|
|
326
|
-
// Start autoplay if enabled
|
|
327
|
-
if (config.value.autoplay !== 'disabled') {
|
|
328
|
-
startAutoplay()
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// Call onInit callback
|
|
332
|
-
config.value.onInit()
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
function buildSliderFrame(): void {
|
|
336
|
-
if (!carouselRef.value || !isMounted.value) { return }
|
|
337
|
-
|
|
338
|
-
// Set building flag to prevent interactions during rebuild
|
|
339
|
-
isBuilding.value = true
|
|
340
|
-
|
|
341
|
-
try {
|
|
342
|
-
// Ensure we have elements to build the slider
|
|
343
|
-
if (!innerElements.value.length) {
|
|
344
|
-
console.warn('No carousel items found')
|
|
345
|
-
emptyRebuildAttempts.value++
|
|
346
|
-
isBuilding.value = false
|
|
347
|
-
return
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// Reset counter since we have elements
|
|
351
|
-
emptyRebuildAttempts.value = 0
|
|
352
|
-
|
|
353
|
-
const originalCount = innerElements.value.length
|
|
354
|
-
const widthItem = selectorWidth.value / perPage.value
|
|
355
|
-
|
|
356
|
-
// Calculate how many times to repeat items to fill big screens
|
|
357
|
-
const minItemsNeeded = Math.max(perPage.value * 3, 20) // Ensure we have enough content for big screens
|
|
358
|
-
const repetitions = Math.max(1, Math.ceil(minItemsNeeded / originalCount))
|
|
359
|
-
const totalMainItems = originalCount * repetitions
|
|
360
|
-
|
|
361
|
-
// Ultra-aggressive buffer size for really big screens
|
|
362
|
-
const bufferSize = Math.max(perPage.value * 8, 20) // Even more buffer for huge screens
|
|
363
|
-
const itemsToBuild = config.value.loop
|
|
364
|
-
? totalMainItems + (2 * bufferSize)
|
|
365
|
-
: totalMainItems
|
|
366
|
-
|
|
367
|
-
// Create a clean slate for the slider
|
|
368
|
-
carouselRef.value.innerHTML = ''
|
|
369
|
-
|
|
370
|
-
// Create or recreate the slider frame
|
|
371
|
-
if (!sliderFrame.value) {
|
|
372
|
-
sliderFrame.value = document.createElement('div')
|
|
373
|
-
} else {
|
|
374
|
-
// Clear existing content
|
|
375
|
-
while (sliderFrame.value.firstChild) {
|
|
376
|
-
sliderFrame.value.removeChild(sliderFrame.value.firstChild)
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
sliderFrame.value.style.width = `${widthItem * itemsToBuild}px`
|
|
381
|
-
enableTransition()
|
|
382
|
-
|
|
383
|
-
if (config.value.draggable) {
|
|
384
|
-
carouselRef.value.style.cursor = '-webkit-grab'
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
// Create a fragment to hold all slider content
|
|
388
|
-
const docFragment = document.createDocumentFragment()
|
|
389
|
-
|
|
390
|
-
// Add clones before original items (for loop mode)
|
|
391
|
-
if (config.value.loop && innerElements.value.length > 0) {
|
|
392
|
-
for (let i = originalCount - bufferSize; i < originalCount; i++) {
|
|
393
|
-
if (i >= 0 && i < originalCount) {
|
|
394
|
-
const original = innerElements.value[i]
|
|
395
|
-
const clone = original.cloneNode(true) as Element
|
|
396
|
-
|
|
397
|
-
if (clone instanceof HTMLElement) {
|
|
398
|
-
clone.setAttribute('data-clone', 'before')
|
|
399
|
-
copyAttributesAndEvents(original, clone)
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
const wrapped = buildSliderFrameItem(clone)
|
|
403
|
-
docFragment.appendChild(wrapped)
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
// Add main items repeated multiple times to fill big screens
|
|
409
|
-
for (let rep = 0; rep < repetitions; rep++) {
|
|
410
|
-
for (let i = 0; i < originalCount; i++) {
|
|
411
|
-
const element = innerElements.value[i]
|
|
412
|
-
const clone = element.cloneNode(true) as Element
|
|
413
|
-
|
|
414
|
-
if (clone instanceof HTMLElement) {
|
|
415
|
-
clone.setAttribute('data-repetition', rep.toString())
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
const wrapped = buildSliderFrameItem(clone)
|
|
419
|
-
docFragment.appendChild(wrapped)
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
// Add clones after original items (for loop mode)
|
|
424
|
-
if (config.value.loop && innerElements.value.length > 0) {
|
|
425
|
-
for (let i = 0; i < bufferSize; i++) {
|
|
426
|
-
if (i >= 0 && i < originalCount) {
|
|
427
|
-
const original = innerElements.value[i]
|
|
428
|
-
const clone = original.cloneNode(true) as Element
|
|
429
|
-
|
|
430
|
-
if (clone instanceof HTMLElement) {
|
|
431
|
-
clone.setAttribute('data-clone', 'after')
|
|
432
|
-
copyAttributesAndEvents(original, clone)
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
const wrapped = buildSliderFrameItem(clone)
|
|
436
|
-
docFragment.appendChild(wrapped)
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
// Append all content to the slider frame
|
|
442
|
-
sliderFrame.value.appendChild(docFragment)
|
|
443
|
-
|
|
444
|
-
// Add the slider frame to the carousel
|
|
445
|
-
carouselRef.value.appendChild(sliderFrame.value)
|
|
446
|
-
|
|
447
|
-
// Update currentSlide if needed to stay within bounds
|
|
448
|
-
if (currentSlide.value >= innerElements.value.length) {
|
|
449
|
-
currentSlide.value = Math.max(0, innerElements.value.length - 1)
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
// Set initial position
|
|
453
|
-
slideToCurrent()
|
|
454
|
-
} catch (error) {
|
|
455
|
-
console.error('Error building slider frame:', error)
|
|
456
|
-
} finally {
|
|
457
|
-
// Always make sure to reset the building flag
|
|
458
|
-
isBuilding.value = false
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// Helper function to copy attributes and events from original to clone
|
|
463
|
-
function copyAttributesAndEvents(original: Element, clone: Element): void {
|
|
464
|
-
if (!(original instanceof HTMLElement) || !(clone instanceof HTMLElement)) {
|
|
465
|
-
return
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// Copy all attributes
|
|
469
|
-
const allAttrs = original.attributes
|
|
470
|
-
for (let i = 0; i < allAttrs.length; i++) {
|
|
471
|
-
const attr = allAttrs[i]
|
|
472
|
-
if (attr.name !== 'id') { // Skip id to avoid duplicate IDs
|
|
473
|
-
clone.setAttribute(attr.name, attr.value)
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
// For Vue-specific elements, add a custom click handler that relays to the original
|
|
478
|
-
clone.addEventListener('click', (e) => {
|
|
479
|
-
// Check if original has a click handler
|
|
480
|
-
if (typeof (original as any).click === 'function') {
|
|
481
|
-
// We need to simulate the click on the original element
|
|
482
|
-
// Create and dispatch a new click event on the original element
|
|
483
|
-
const clickEvent = new MouseEvent('click', {
|
|
484
|
-
bubbles: true,
|
|
485
|
-
cancelable: true,
|
|
486
|
-
view: window
|
|
487
|
-
})
|
|
488
|
-
original.dispatchEvent(clickEvent)
|
|
489
|
-
|
|
490
|
-
// Stop propagation of the clone's event
|
|
491
|
-
e.stopPropagation()
|
|
492
|
-
}
|
|
493
|
-
})
|
|
494
|
-
|
|
495
|
-
// Special handling for links
|
|
496
|
-
if (original.tagName === 'A' && clone.tagName === 'A') {
|
|
497
|
-
const originalHref = original.getAttribute('href')
|
|
498
|
-
if (originalHref) {
|
|
499
|
-
clone.addEventListener('click', (e) => {
|
|
500
|
-
e.preventDefault()
|
|
501
|
-
|
|
502
|
-
// Get original onClick behavior
|
|
503
|
-
if (original.onclick) {
|
|
504
|
-
original.onclick(new PointerEvent('click'))
|
|
505
|
-
} else {
|
|
506
|
-
// Default behavior - navigate like original link would
|
|
507
|
-
const targetBlank = original.getAttribute('target') === '_blank'
|
|
508
|
-
if (targetBlank) {
|
|
509
|
-
window.open(originalHref, '_blank')
|
|
510
|
-
} else {
|
|
511
|
-
window.location.href = originalHref
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
})
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
function buildSliderFrameItem(elm: Element): HTMLElement {
|
|
520
|
-
const elementContainer = document.createElement('div')
|
|
521
|
-
elementContainer.style.cssFloat = config.value.rtl ? 'right' : 'left'
|
|
522
|
-
elementContainer.style.float = config.value.rtl ? 'right' : 'left'
|
|
523
|
-
elementContainer.style.padding = config.value.slideGap ? `${config.value.slideGap / 2}rem` : '0'
|
|
524
|
-
|
|
525
|
-
// Calculate total items including repetitions for big screens
|
|
526
|
-
const originalCount = innerElements.value.length
|
|
527
|
-
const minItemsNeeded = Math.max(perPage.value * 3, 20)
|
|
528
|
-
const repetitions = Math.max(1, Math.ceil(minItemsNeeded / originalCount))
|
|
529
|
-
const totalMainItems = originalCount * repetitions
|
|
530
|
-
|
|
531
|
-
// Ultra-aggressive buffer size - must match buildSliderFrame buffer size
|
|
532
|
-
const itemBufferSize = Math.max(perPage.value * 8, 20) // Even more buffer for huge screens
|
|
533
|
-
const percentage = config.value.loop
|
|
534
|
-
? 100 / (totalMainItems + (2 * itemBufferSize))
|
|
535
|
-
: 100 / totalMainItems
|
|
536
|
-
|
|
537
|
-
elementContainer.style.width = `${percentage}%`
|
|
538
|
-
elementContainer.appendChild(elm)
|
|
539
|
-
return elementContainer
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
function slideToCurrent(enableTransitionFlag?: boolean): void {
|
|
543
|
-
try {
|
|
544
|
-
// Ensure component is still mounted
|
|
545
|
-
if (!isMounted.value || !innerElements.value.length || !sliderFrame.value) { return }
|
|
546
|
-
|
|
547
|
-
// Ultra-aggressive buffer size - must match buildSliderFrame buffer size
|
|
548
|
-
const slideBufferSize = Math.max(perPage.value * 8, 20) // Even more buffer for huge screens
|
|
549
|
-
const currentSlideValue = config.value.loop
|
|
550
|
-
? currentSlide.value + slideBufferSize
|
|
551
|
-
: currentSlide.value
|
|
552
|
-
|
|
553
|
-
const offset = (config.value.rtl ? 1 : -1) * currentSlideValue * (selectorWidth.value / perPage.value)
|
|
554
|
-
|
|
555
|
-
if (enableTransitionFlag && sliderFrame.value) {
|
|
556
|
-
// Use requestAnimationFrame for smooth transitions
|
|
557
|
-
requestAnimationFrame(() => {
|
|
558
|
-
// Check again that we're still mounted and have the frame
|
|
559
|
-
if (!isMounted.value || !sliderFrame.value) { return }
|
|
560
|
-
|
|
561
|
-
requestAnimationFrame(() => {
|
|
562
|
-
if (!isMounted.value || !sliderFrame.value) { return }
|
|
563
|
-
|
|
564
|
-
enableTransition()
|
|
565
|
-
sliderFrame.value.style[transformProperty.value as any] = `translate3d(${offset}px, 0, 0)`
|
|
566
|
-
})
|
|
567
|
-
})
|
|
568
|
-
} else if (sliderFrame.value) {
|
|
569
|
-
sliderFrame.value.style[transformProperty.value as any] = `translate3d(${offset}px, 0, 0)`
|
|
570
|
-
}
|
|
571
|
-
} catch (error) {
|
|
572
|
-
console.error('Error in slideToCurrent:', error)
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
function prev(howManySlides: number = 1): void {
|
|
577
|
-
if (!isMounted.value || isBuilding.value || !innerElements.value.length) { return }
|
|
578
|
-
|
|
579
|
-
const beforeChange = currentSlide.value
|
|
580
|
-
|
|
581
|
-
if (config.value.loop) {
|
|
582
|
-
const isNewIndexClone = currentSlide.value - howManySlides < 0
|
|
583
|
-
|
|
584
|
-
if (isNewIndexClone) {
|
|
585
|
-
disableTransition()
|
|
586
|
-
|
|
587
|
-
const mirrorSlideIndex = currentSlide.value + innerElements.value.length
|
|
588
|
-
const mirrorSlideIndexOffset = perPage.value
|
|
589
|
-
const moveTo = mirrorSlideIndex + mirrorSlideIndexOffset
|
|
590
|
-
const offset = (config.value.rtl ? 1 : -1) * moveTo * (selectorWidth.value / perPage.value)
|
|
591
|
-
const dragDistance = config.value.draggable ? drag.value.endX - drag.value.startX : 0
|
|
592
|
-
|
|
593
|
-
if (sliderFrame.value) {
|
|
594
|
-
sliderFrame.value.style[transformProperty.value as any] = `translate3d(${offset + dragDistance}px, 0, 0)`
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
currentSlide.value = mirrorSlideIndex - howManySlides
|
|
598
|
-
} else {
|
|
599
|
-
currentSlide.value -= howManySlides
|
|
600
|
-
}
|
|
601
|
-
} else {
|
|
602
|
-
currentSlide.value = Math.max(currentSlide.value - howManySlides, 0)
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
if (beforeChange !== currentSlide.value) {
|
|
606
|
-
slideToCurrent(config.value.loop)
|
|
607
|
-
config.value.onChange()
|
|
608
|
-
}
|
|
609
|
-
if (config.value.autoplay !== 'disabled') {
|
|
610
|
-
startAutoplay()
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
function next(howManySlides: number = 1): void {
|
|
615
|
-
if (!isMounted.value || isBuilding.value || !innerElements.value.length) { return }
|
|
616
|
-
|
|
617
|
-
const beforeChange = currentSlide.value
|
|
618
|
-
|
|
619
|
-
if (config.value.loop) {
|
|
620
|
-
const isNewIndexClone = currentSlide.value + howManySlides > innerElements.value.length - perPage.value
|
|
621
|
-
|
|
622
|
-
if (isNewIndexClone) {
|
|
623
|
-
disableTransition()
|
|
624
|
-
|
|
625
|
-
const mirrorSlideIndex = currentSlide.value - innerElements.value.length
|
|
626
|
-
const mirrorSlideIndexOffset = perPage.value
|
|
627
|
-
const moveTo = mirrorSlideIndex + mirrorSlideIndexOffset
|
|
628
|
-
const offset = (config.value.rtl ? 1 : -1) * moveTo * (selectorWidth.value / perPage.value)
|
|
629
|
-
const dragDistance = config.value.draggable ? drag.value.endX - drag.value.startX : 0
|
|
630
|
-
|
|
631
|
-
if (sliderFrame.value) {
|
|
632
|
-
sliderFrame.value.style[transformProperty.value as any] = `translate3d(${offset + dragDistance}px, 0, 0)`
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
currentSlide.value = mirrorSlideIndex + howManySlides
|
|
636
|
-
} else {
|
|
637
|
-
currentSlide.value += howManySlides
|
|
638
|
-
}
|
|
639
|
-
} else {
|
|
640
|
-
currentSlide.value = Math.min(currentSlide.value + howManySlides, innerElements.value.length - perPage.value)
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
if (beforeChange !== currentSlide.value) {
|
|
644
|
-
slideToCurrent(config.value.loop)
|
|
645
|
-
config.value.onChange()
|
|
646
|
-
}
|
|
647
|
-
if (config.value.autoplay !== 'disabled') {
|
|
648
|
-
startAutoplay()
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
function goTo(index: number): void {
|
|
653
|
-
if (innerElements.value.length <= perPage.value) {
|
|
654
|
-
return
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
const beforeChange = currentSlide.value
|
|
658
|
-
|
|
659
|
-
currentSlide.value = config.value.loop
|
|
660
|
-
? index % innerElements.value.length
|
|
661
|
-
: Math.min(Math.max(index, 0), innerElements.value.length - perPage.value)
|
|
662
|
-
|
|
663
|
-
if (beforeChange !== currentSlide.value) {
|
|
664
|
-
slideToCurrent()
|
|
665
|
-
config.value.onChange()
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
function disableTransition(): void {
|
|
670
|
-
if (sliderFrame.value) {
|
|
671
|
-
sliderFrame.value.style.webkitTransition = `all 0ms ${config.value.easing}`
|
|
672
|
-
sliderFrame.value.style.transition = `all 0ms ${config.value.easing}`
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
function enableTransition(): void {
|
|
677
|
-
if (sliderFrame.value) {
|
|
678
|
-
sliderFrame.value.style.webkitTransition = `all ${config.value.speed}ms ${config.value.easing}`
|
|
679
|
-
sliderFrame.value.style.transition = `all ${config.value.speed}ms ${config.value.easing}`
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
function updateAfterDrag(): void {
|
|
684
|
-
if (!isMounted.value) { return }
|
|
685
|
-
|
|
686
|
-
const movement = (config.value.rtl ? -1 : 1) * (drag.value.endX - drag.value.startX)
|
|
687
|
-
const movementDistance = Math.abs(movement)
|
|
688
|
-
const howManySliderToSlide = config.value.multipleDrag
|
|
689
|
-
? Math.ceil(movementDistance / (selectorWidth.value / perPage.value))
|
|
690
|
-
: 1
|
|
691
|
-
|
|
692
|
-
const slideToNegativeClone = movement > 0 && currentSlide.value - howManySliderToSlide < 0
|
|
693
|
-
const slideToPositiveClone = movement < 0
|
|
694
|
-
&& currentSlide.value + howManySliderToSlide > innerElements.value.length - perPage.value
|
|
695
|
-
|
|
696
|
-
if (movement > 0 && movementDistance > config.value.threshold && innerElements.value.length > perPage.value) {
|
|
697
|
-
prev(howManySliderToSlide)
|
|
698
|
-
} else if (movement < 0
|
|
699
|
-
&& movementDistance > config.value.threshold
|
|
700
|
-
&& innerElements.value.length > perPage.value) {
|
|
701
|
-
next(howManySliderToSlide)
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
slideToCurrent(slideToNegativeClone || slideToPositiveClone)
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
function resizeHandler(): void {
|
|
708
|
-
// Recalculate current slide
|
|
709
|
-
if (currentSlide.value + perPage.value > innerElements.value.length) {
|
|
710
|
-
currentSlide.value = innerElements.value.length <= perPage.value
|
|
711
|
-
? 0
|
|
712
|
-
: innerElements.value.length - perPage.value
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
if (carouselRef.value) {
|
|
716
|
-
selectorWidth.value = carouselRef.value.offsetWidth
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
buildSliderFrame()
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
function clearDrag(): void {
|
|
723
|
-
drag.value = {
|
|
724
|
-
startX: 0,
|
|
725
|
-
endX: 0,
|
|
726
|
-
startY: 0,
|
|
727
|
-
letItGo: null,
|
|
728
|
-
preventClick: drag.value.preventClick
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
// Touch event handlers
|
|
733
|
-
function touchstartHandler(e: TouchEvent): void {
|
|
734
|
-
if (isBuilding.value || !isMounted.value) { return }
|
|
735
|
-
|
|
736
|
-
const target = e.target as HTMLElement
|
|
737
|
-
const ignoreTags = ['TEXTAREA', 'OPTION', 'INPUT', 'SELECT'].includes(target.nodeName)
|
|
738
|
-
|
|
739
|
-
if (ignoreTags) {
|
|
740
|
-
return
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
// Pause autoplay during touch interaction
|
|
744
|
-
stopAutoplay()
|
|
745
|
-
|
|
746
|
-
e.stopPropagation()
|
|
747
|
-
pointerDown.value = true
|
|
748
|
-
isDragging.value = false // Reset dragging state
|
|
749
|
-
drag.value.startX = e.touches[0].pageX
|
|
750
|
-
drag.value.startY = e.touches[0].pageY
|
|
751
|
-
|
|
752
|
-
// Mark link-related elements for potential preventClick
|
|
753
|
-
const isLink = target.tagName === 'A' || target.closest('a')
|
|
754
|
-
const isImage = target.tagName === 'IMG'
|
|
755
|
-
if (isLink || isImage) {
|
|
756
|
-
drag.value.preventClick = false
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
// When starting drag during linear autoplay, save the current position
|
|
760
|
-
if (config.value.autoplay === 'linear' && sliderFrame.value) {
|
|
761
|
-
// Get the current transform value and save it to apply during dragging
|
|
762
|
-
const currentTransform = getComputedStyle(sliderFrame.value)[transformProperty.value as any]
|
|
763
|
-
if (currentTransform && currentTransform !== 'none') {
|
|
764
|
-
try {
|
|
765
|
-
// Parse the transform matrix to get the current X position
|
|
766
|
-
const matrix = new DOMMatrix(currentTransform)
|
|
767
|
-
const currentX = matrix.m41
|
|
768
|
-
|
|
769
|
-
// Convert position to slides and update currentSlide
|
|
770
|
-
const itemWidth = selectorWidth.value / perPage.value
|
|
771
|
-
if (config.value.loop) {
|
|
772
|
-
// Adjust for the perPage offset in loop mode
|
|
773
|
-
const totalPosition = Math.abs(currentX)
|
|
774
|
-
const slidePosition = Math.round(totalPosition / itemWidth) - perPage.value
|
|
775
|
-
currentSlide.value = slidePosition >= 0
|
|
776
|
-
? slidePosition % innerElements.value.length
|
|
777
|
-
: (innerElements.value.length + (slidePosition % innerElements.value.length)) % innerElements.value.length
|
|
778
|
-
} else {
|
|
779
|
-
// In non-loop mode, just calculate the current slide
|
|
780
|
-
const slidePosition = Math.round(Math.abs(currentX) / itemWidth)
|
|
781
|
-
currentSlide.value = Math.min(Math.max(slidePosition, 0), innerElements.value.length - perPage.value)
|
|
782
|
-
}
|
|
783
|
-
} catch (e) {
|
|
784
|
-
console.error('Error parsing transform matrix:', e)
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
// Stop the animation immediately
|
|
789
|
-
stopAutoplay()
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
function touchendHandler(e: TouchEvent): void {
|
|
794
|
-
if (isBuilding.value || !isMounted.value) { return }
|
|
795
|
-
|
|
796
|
-
e.stopPropagation()
|
|
797
|
-
|
|
798
|
-
// If we were dragging, prevent clicks by adding a short-lived capture event listener
|
|
799
|
-
if (isDragging.value) {
|
|
800
|
-
// Add a temporary, one-time capture event listener to block the next click
|
|
801
|
-
document.addEventListener('click', function preventClickAfterDrag(event) {
|
|
802
|
-
event.preventDefault()
|
|
803
|
-
event.stopPropagation()
|
|
804
|
-
event.stopImmediatePropagation() // This is crucial to stop other handlers from executing
|
|
805
|
-
|
|
806
|
-
// Remove this listener after it's been triggered once
|
|
807
|
-
document.removeEventListener('click', preventClickAfterDrag, true)
|
|
808
|
-
}, { capture: true, once: true })
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
pointerDown.value = false
|
|
812
|
-
isDragging.value = false
|
|
813
|
-
enableTransition()
|
|
814
|
-
|
|
815
|
-
if (drag.value.endX) {
|
|
816
|
-
updateAfterDrag()
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
clearDrag()
|
|
820
|
-
|
|
821
|
-
// Restart autoplay after touch interaction
|
|
822
|
-
if (config.value.autoplay !== 'disabled' && !isHovering.value && isMounted.value) {
|
|
823
|
-
startAutoplay()
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
function touchmoveHandler(e: TouchEvent): void {
|
|
828
|
-
if (isBuilding.value || !isMounted.value) { return }
|
|
829
|
-
|
|
830
|
-
e.stopPropagation()
|
|
831
|
-
|
|
832
|
-
if (drag.value.letItGo === null) {
|
|
833
|
-
drag.value.letItGo = Math.abs(drag.value.startY - e.touches[0].pageY)
|
|
834
|
-
< Math.abs(drag.value.startX - e.touches[0].pageX)
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
if (pointerDown.value && drag.value.letItGo) {
|
|
838
|
-
e.preventDefault()
|
|
839
|
-
drag.value.endX = e.touches[0].pageX
|
|
840
|
-
|
|
841
|
-
// Calculate movement distance
|
|
842
|
-
const dragDistance = Math.abs(drag.value.endX - drag.value.startX)
|
|
843
|
-
|
|
844
|
-
// If dragged more than the threshold, mark as a real drag operation
|
|
845
|
-
if (dragDistance > dragThreshold) {
|
|
846
|
-
isDragging.value = true
|
|
847
|
-
drag.value.preventClick = true
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
if (sliderFrame.value) {
|
|
851
|
-
disableTransition()
|
|
852
|
-
|
|
853
|
-
const currentSlideValue = config.value.loop
|
|
854
|
-
? currentSlide.value + perPage.value
|
|
855
|
-
: currentSlide.value
|
|
856
|
-
|
|
857
|
-
const currentOffset = currentSlideValue * (selectorWidth.value / perPage.value)
|
|
858
|
-
const dragOffset = drag.value.endX - drag.value.startX
|
|
859
|
-
const offset = config.value.rtl
|
|
860
|
-
? currentOffset + dragOffset
|
|
861
|
-
: currentOffset - dragOffset
|
|
862
|
-
|
|
863
|
-
sliderFrame.value.style[transformProperty.value as any]
|
|
864
|
-
= `translate3d(${(config.value.rtl ? 1 : -1) * offset}px, 0, 0)`
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
// Mouse event handlers
|
|
870
|
-
function mousedownHandler(e: MouseEvent): void {
|
|
871
|
-
if (isBuilding.value || !isMounted.value) { return }
|
|
872
|
-
|
|
873
|
-
const target = e.target as HTMLElement
|
|
874
|
-
const ignoreTags = ['TEXTAREA', 'OPTION', 'INPUT', 'SELECT'].includes(target.nodeName)
|
|
875
|
-
|
|
876
|
-
if (ignoreTags) { return }
|
|
877
|
-
|
|
878
|
-
// Pause autoplay during mouse interaction
|
|
879
|
-
stopAutoplay()
|
|
880
|
-
|
|
881
|
-
e.preventDefault()
|
|
882
|
-
e.stopPropagation()
|
|
883
|
-
pointerDown.value = true
|
|
884
|
-
isDragging.value = false // Reset dragging state on mousedown
|
|
885
|
-
drag.value.startX = e.pageX
|
|
886
|
-
|
|
887
|
-
// Mark link-related elements for potential preventClick
|
|
888
|
-
const isLink = target.tagName === 'A' || target.closest('a')
|
|
889
|
-
const isImage = target.tagName === 'IMG'
|
|
890
|
-
if (isLink || isImage) {
|
|
891
|
-
drag.value.preventClick = false // Start with false, will be set to true if actual dragging occurs
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
// When starting drag during linear autoplay, save the current position
|
|
895
|
-
if (config.value.autoplay === 'linear' && sliderFrame.value) {
|
|
896
|
-
// Get the current transform value and save it to apply during dragging
|
|
897
|
-
const currentTransform = getComputedStyle(sliderFrame.value)[transformProperty.value as any]
|
|
898
|
-
if (currentTransform && currentTransform !== 'none') {
|
|
899
|
-
try {
|
|
900
|
-
// Parse the transform matrix to get the current X position
|
|
901
|
-
const matrix = new DOMMatrix(currentTransform)
|
|
902
|
-
const currentX = matrix.m41
|
|
903
|
-
|
|
904
|
-
// Convert position to slides and update currentSlide
|
|
905
|
-
const itemWidth = selectorWidth.value / perPage.value
|
|
906
|
-
if (config.value.loop) {
|
|
907
|
-
// Adjust for the perPage offset in loop mode
|
|
908
|
-
const totalPosition = Math.abs(currentX)
|
|
909
|
-
const slidePosition = Math.round(totalPosition / itemWidth) - perPage.value
|
|
910
|
-
currentSlide.value = slidePosition >= 0
|
|
911
|
-
? slidePosition % innerElements.value.length
|
|
912
|
-
: (innerElements.value.length + (slidePosition % innerElements.value.length)) % innerElements.value.length
|
|
913
|
-
} else {
|
|
914
|
-
// In non-loop mode, just calculate the current slide
|
|
915
|
-
const slidePosition = Math.round(Math.abs(currentX) / itemWidth)
|
|
916
|
-
currentSlide.value = Math.min(Math.max(slidePosition, 0), innerElements.value.length - perPage.value)
|
|
917
|
-
}
|
|
918
|
-
} catch (e) {
|
|
919
|
-
console.error('Error parsing transform matrix:', e)
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
// Stop the animation immediately
|
|
924
|
-
stopAutoplay()
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
function mouseupHandler(e: MouseEvent): void {
|
|
929
|
-
if (isBuilding.value || !isMounted.value) { return }
|
|
930
|
-
|
|
931
|
-
e.stopPropagation()
|
|
932
|
-
|
|
933
|
-
// If we were dragging, prevent clicks by adding a short-lived capture event listener
|
|
934
|
-
if (isDragging.value) {
|
|
935
|
-
// Add a temporary, one-time capture event listener to block the next click
|
|
936
|
-
document.addEventListener('click', function preventClickAfterDrag(event) {
|
|
937
|
-
event.preventDefault()
|
|
938
|
-
event.stopPropagation()
|
|
939
|
-
event.stopImmediatePropagation() // This is crucial to stop other handlers from executing
|
|
940
|
-
|
|
941
|
-
// Remove this listener after it's been triggered once
|
|
942
|
-
document.removeEventListener('click', preventClickAfterDrag, true)
|
|
943
|
-
}, { capture: true, once: true })
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
pointerDown.value = false
|
|
947
|
-
isDragging.value = false
|
|
948
|
-
|
|
949
|
-
if (carouselRef.value) {
|
|
950
|
-
carouselRef.value.style.cursor = '-webkit-grab'
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
enableTransition()
|
|
954
|
-
|
|
955
|
-
if (drag.value.endX) {
|
|
956
|
-
updateAfterDrag()
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
clearDrag()
|
|
960
|
-
|
|
961
|
-
// Restart autoplay after mouse interaction if not hovering
|
|
962
|
-
if (config.value.autoplay !== 'disabled' && !isHovering.value && isMounted.value) {
|
|
963
|
-
startAutoplay()
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
function mousemoveHandler(e: MouseEvent): void {
|
|
968
|
-
if (isBuilding.value || !isMounted.value) { return }
|
|
969
|
-
|
|
970
|
-
e.preventDefault()
|
|
971
|
-
|
|
972
|
-
if (pointerDown.value) {
|
|
973
|
-
drag.value.endX = e.pageX
|
|
974
|
-
|
|
975
|
-
// Calculate movement distance
|
|
976
|
-
const dragDistance = Math.abs(drag.value.endX - drag.value.startX)
|
|
977
|
-
|
|
978
|
-
// If dragged more than the threshold, mark as a real drag operation
|
|
979
|
-
if (dragDistance > dragThreshold) {
|
|
980
|
-
isDragging.value = true
|
|
981
|
-
drag.value.preventClick = true
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
if (carouselRef.value) {
|
|
985
|
-
carouselRef.value.style.cursor = '-webkit-grabbing'
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
if (sliderFrame.value) {
|
|
989
|
-
disableTransition()
|
|
990
|
-
|
|
991
|
-
const currentSlideValue = config.value.loop
|
|
992
|
-
? currentSlide.value + perPage.value
|
|
993
|
-
: currentSlide.value
|
|
994
|
-
|
|
995
|
-
const currentOffset = currentSlideValue * (selectorWidth.value / perPage.value)
|
|
996
|
-
const dragOffset = drag.value.endX - drag.value.startX
|
|
997
|
-
const offset = config.value.rtl
|
|
998
|
-
? currentOffset + dragOffset
|
|
999
|
-
: currentOffset - dragOffset
|
|
1000
|
-
|
|
1001
|
-
sliderFrame.value.style[transformProperty.value as any]
|
|
1002
|
-
= `translate3d(${(config.value.rtl ? 1 : -1) * offset}px, 0, 0)`
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
function mouseleaveHandler(e: MouseEvent): void {
|
|
1008
|
-
if (isBuilding.value || !isMounted.value) { return }
|
|
1009
|
-
|
|
1010
|
-
if (pointerDown.value) {
|
|
1011
|
-
pointerDown.value = false
|
|
1012
|
-
|
|
1013
|
-
if (carouselRef.value) {
|
|
1014
|
-
carouselRef.value.style.cursor = '-webkit-grab'
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
drag.value.endX = e.pageX
|
|
1018
|
-
drag.value.preventClick = false
|
|
1019
|
-
enableTransition()
|
|
1020
|
-
updateAfterDrag()
|
|
1021
|
-
clearDrag()
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
function clickHandler(e: MouseEvent): void {
|
|
1026
|
-
if (isBuilding.value || !isMounted.value) { return }
|
|
1027
|
-
|
|
1028
|
-
if (drag.value.preventClick || isDragging.value) {
|
|
1029
|
-
e.preventDefault()
|
|
1030
|
-
e.stopPropagation()
|
|
1031
|
-
e.stopImmediatePropagation() // This stops other handlers from firing
|
|
1032
|
-
drag.value.preventClick = false
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
drag.value.preventClick = false
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
|
-
// Add this new function to handle dragstart events
|
|
1039
|
-
function dragstartHandler(e: DragEvent): void {
|
|
1040
|
-
if (isBuilding.value || !isMounted.value) { return } // Prevent interaction during build
|
|
1041
|
-
|
|
1042
|
-
// Prevent default drag behavior for images and links
|
|
1043
|
-
if (config.value.draggable) {
|
|
1044
|
-
const target = e.target as HTMLElement
|
|
1045
|
-
const isDraggableElement = target.tagName === 'IMG'
|
|
1046
|
-
|| target.tagName === 'A'
|
|
1047
|
-
|| target.parentElement?.tagName === 'A'
|
|
1048
|
-
|
|
1049
|
-
if (isDraggableElement) {
|
|
1050
|
-
e.preventDefault()
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
|
-
// Public API
|
|
1056
|
-
function remove(index: number): void {
|
|
1057
|
-
if (index < 0 || index >= innerElements.value.length) {
|
|
1058
|
-
throw new Error('Item to remove doesn\'t exist 😭')
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
// Shift sliderFrame back by one if needed
|
|
1062
|
-
const lowerIndex = index < currentSlide.value
|
|
1063
|
-
const lastItem = currentSlide.value + perPage.value - 1 === index
|
|
1064
|
-
|
|
1065
|
-
if (lowerIndex || lastItem) {
|
|
1066
|
-
currentSlide.value--
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
innerElements.value.splice(index, 1)
|
|
1070
|
-
buildSliderFrame()
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
function insert(item: Element, index: number): void {
|
|
1074
|
-
if (index < 0 || index > innerElements.value.length + 1) {
|
|
1075
|
-
throw new Error('Unable to insert at this index 😭')
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
if (innerElements.value.includes(item)) {
|
|
1079
|
-
throw new Error('The same item in a carousel? Really? Nope 😭')
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
// Avoid shifting content
|
|
1083
|
-
const shouldItShift = index <= currentSlide.value && innerElements.value.length > 0
|
|
1084
|
-
|
|
1085
|
-
if (shouldItShift) {
|
|
1086
|
-
currentSlide.value++
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
|
-
innerElements.value.splice(index, 0, item)
|
|
1090
|
-
buildSliderFrame()
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
function prepend(item: Element): void {
|
|
1094
|
-
insert(item, 0)
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
function append(item: Element): void {
|
|
1098
|
-
insert(item, innerElements.value.length)
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
function destroy(restoreMarkup: boolean = false): void {
|
|
1102
|
-
detachEvents()
|
|
1103
|
-
|
|
1104
|
-
if (carouselRef.value) {
|
|
1105
|
-
carouselRef.value.style.cursor = 'auto'
|
|
1106
|
-
|
|
1107
|
-
if (restoreMarkup) {
|
|
1108
|
-
const slides = document.createDocumentFragment()
|
|
1109
|
-
|
|
1110
|
-
for (let i = 0; i < innerElements.value.length; i++) {
|
|
1111
|
-
slides.appendChild(innerElements.value[i])
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
carouselRef.value.innerHTML = ''
|
|
1115
|
-
carouselRef.value.appendChild(slides)
|
|
1116
|
-
carouselRef.value.removeAttribute('style')
|
|
1117
|
-
}
|
|
1118
|
-
}
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
// Add after init()
|
|
1122
|
-
async function startAutoplay(): Promise<void> {
|
|
1123
|
-
if (!isMounted.value) { return } // Don't start if not mounted
|
|
1124
|
-
|
|
1125
|
-
stopAutoplay() // Clear any existing timers first
|
|
1126
|
-
|
|
1127
|
-
if (config.value.autoplay === 'standard') {
|
|
1128
|
-
autoplayTimer.value = window.setInterval(() => {
|
|
1129
|
-
// Check if component is still mounted before advancing
|
|
1130
|
-
if (isMounted.value && !isHovering.value && !isBuilding.value) {
|
|
1131
|
-
next()
|
|
1132
|
-
}
|
|
1133
|
-
}, config.value.autoplayInterval)
|
|
1134
|
-
} else if (config.value.autoplay === 'linear') {
|
|
1135
|
-
startLinearAutoplay()
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
|
-
function stopAutoplay(): void {
|
|
1140
|
-
if (autoplayTimer.value !== null) {
|
|
1141
|
-
clearInterval(autoplayTimer.value)
|
|
1142
|
-
autoplayTimer.value = null
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
if (linearAnimationFrame.value !== null) {
|
|
1146
|
-
cancelAnimationFrame(linearAnimationFrame.value)
|
|
1147
|
-
linearAnimationFrame.value = null
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
|
-
function startLinearAutoplay(): void {
|
|
1152
|
-
if (!isMounted.value || !innerElements.value.length) { return }
|
|
1153
|
-
|
|
1154
|
-
// Reset the linear offset to the current slide position
|
|
1155
|
-
const itemWidth = selectorWidth.value / perPage.value
|
|
1156
|
-
linearOffset.value = currentSlide.value * itemWidth
|
|
1157
|
-
let lastTimestamp = 0
|
|
1158
|
-
|
|
1159
|
-
// Animation function
|
|
1160
|
-
const animate = (timestamp: number) => {
|
|
1161
|
-
// Always verify that the component is still mounted
|
|
1162
|
-
if (!isMounted.value || !sliderFrame.value) {
|
|
1163
|
-
stopAutoplay()
|
|
1164
|
-
return
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
|
-
if (!lastTimestamp) { lastTimestamp = timestamp }
|
|
1168
|
-
|
|
1169
|
-
// Calculate time delta and convert to pixels based on speed
|
|
1170
|
-
const elapsed = timestamp - lastTimestamp
|
|
1171
|
-
lastTimestamp = timestamp
|
|
1172
|
-
|
|
1173
|
-
// If hovering or not visible, just continue the animation loop without changing position
|
|
1174
|
-
if (isHovering.value) {
|
|
1175
|
-
linearAnimationFrame.value = requestAnimationFrame(animate)
|
|
1176
|
-
return
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
// Calculate how many pixels to move based on time elapsed and speed
|
|
1180
|
-
const pixelsToMove = (elapsed / 1000) * config.value.autoplaySpeed
|
|
1181
|
-
linearOffset.value += pixelsToMove
|
|
1182
|
-
|
|
1183
|
-
// Calculate total width of all items
|
|
1184
|
-
const totalWidth = itemWidth * innerElements.value.length
|
|
1185
|
-
|
|
1186
|
-
// If we've scrolled past the entire content, reset
|
|
1187
|
-
if (linearOffset.value >= totalWidth) {
|
|
1188
|
-
if (config.value.loop) {
|
|
1189
|
-
linearOffset.value = 0
|
|
1190
|
-
} else {
|
|
1191
|
-
// If not looping, stop at the end
|
|
1192
|
-
linearOffset.value = totalWidth - itemWidth
|
|
1193
|
-
stopAutoplay()
|
|
1194
|
-
}
|
|
1195
|
-
}
|
|
1196
|
-
|
|
1197
|
-
// Apply the offset using transforms
|
|
1198
|
-
if (sliderFrame.value && !isBuilding.value) {
|
|
1199
|
-
const direction = config.value.rtl ? 1 : -1
|
|
1200
|
-
sliderFrame.value.style.transition = 'none' // Smooth scrolling, no easing
|
|
1201
|
-
|
|
1202
|
-
// In loop mode, we need to offset by perPage
|
|
1203
|
-
let displayOffset = linearOffset.value
|
|
1204
|
-
if (config.value.loop) {
|
|
1205
|
-
displayOffset = linearOffset.value + (perPage.value * itemWidth)
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1208
|
-
sliderFrame.value.style[transformProperty.value as any]
|
|
1209
|
-
= `translate3d(${direction * displayOffset}px, 0, 0)`
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
|
-
// Update current slide based on the offset
|
|
1213
|
-
const newSlide = Math.floor(linearOffset.value / itemWidth) % innerElements.value.length
|
|
1214
|
-
if (newSlide !== currentSlide.value) {
|
|
1215
|
-
currentSlide.value = newSlide
|
|
1216
|
-
config.value.onChange()
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
// Continue animation loop only if mounted
|
|
1220
|
-
if (isMounted.value) {
|
|
1221
|
-
linearAnimationFrame.value = requestAnimationFrame(animate)
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
|
-
// Start the animation only if mounted
|
|
1226
|
-
if (isMounted.value) {
|
|
1227
|
-
linearAnimationFrame.value = requestAnimationFrame(animate)
|
|
1228
|
-
}
|
|
1229
|
-
}
|
|
1230
|
-
|
|
1231
|
-
// Event handlers for autoplay pausing
|
|
1232
|
-
function mouseenterHandler(): void {
|
|
1233
|
-
isHovering.value = true
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
|
-
function mouseleaveAutoplayHandler(): void {
|
|
1237
|
-
isHovering.value = false
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
// Add new public methods for autoplay control
|
|
1241
|
-
function pauseAutoplay(): void {
|
|
1242
|
-
stopAutoplay()
|
|
1243
|
-
}
|
|
1244
|
-
|
|
1245
|
-
function resumeAutoplay(): void {
|
|
1246
|
-
if (config.value.autoplay !== 'disabled') {
|
|
1247
|
-
startAutoplay()
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1251
|
-
// Add function to set up the MutationObserver
|
|
1252
|
-
function setupSlotObserver(): void {
|
|
1253
|
-
if (!slotContainer.value || slotObserver.value) { return }
|
|
1254
|
-
|
|
1255
|
-
// Create a new MutationObserver
|
|
1256
|
-
slotObserver.value = new MutationObserver((mutations) => {
|
|
1257
|
-
// Skip if building or over rebuild limit
|
|
1258
|
-
if (isBuilding.value || !isMounted.value
|
|
1259
|
-
|| emptyRebuildAttempts.value >= MAX_EMPTY_REBUILD_ATTEMPTS) {
|
|
1260
|
-
return
|
|
1261
|
-
}
|
|
1262
|
-
|
|
1263
|
-
let needsRebuild = false
|
|
1264
|
-
|
|
1265
|
-
for (const mutation of mutations) {
|
|
1266
|
-
if (mutation.type === 'childList') {
|
|
1267
|
-
needsRebuild = true
|
|
1268
|
-
break
|
|
1269
|
-
}
|
|
1270
|
-
}
|
|
1271
|
-
|
|
1272
|
-
if (needsRebuild) {
|
|
1273
|
-
nextTick(() => {
|
|
1274
|
-
if (isMounted.value && slotContainer.value) {
|
|
1275
|
-
// Update by cloning the new content
|
|
1276
|
-
innerElements.value = Array.from(slotContainer.value.children).map(
|
|
1277
|
-
el => el.cloneNode(true) as Element
|
|
1278
|
-
)
|
|
1279
|
-
|
|
1280
|
-
updateSlider()
|
|
1281
|
-
}
|
|
1282
|
-
})
|
|
1283
|
-
}
|
|
1284
|
-
})
|
|
1285
|
-
|
|
1286
|
-
// Start observing
|
|
1287
|
-
slotObserver.value.observe(slotContainer.value, {
|
|
1288
|
-
childList: true,
|
|
1289
|
-
subtree: false,
|
|
1290
|
-
attributes: false,
|
|
1291
|
-
characterData: false
|
|
1292
|
-
})
|
|
1293
|
-
}
|
|
1294
|
-
|
|
1295
|
-
// Modify updateSlider to safely handle DOM references and always use slotContainer
|
|
1296
|
-
function updateSlider(): void {
|
|
1297
|
-
if (!carouselRef.value || !slotContainer.value || isBuilding.value || !isMounted.value) { return }
|
|
1298
|
-
|
|
1299
|
-
// Prevent too many empty rebuild attempts
|
|
1300
|
-
if (emptyRebuildAttempts.value >= MAX_EMPTY_REBUILD_ATTEMPTS) {
|
|
1301
|
-
console.warn('Too many rebuild attempts with no items, stopping')
|
|
1302
|
-
return
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
|
-
try {
|
|
1306
|
-
// Temporarily disconnect observer to avoid recursive calls
|
|
1307
|
-
if (slotObserver.value) {
|
|
1308
|
-
slotObserver.value.disconnect()
|
|
1309
|
-
}
|
|
1310
|
-
|
|
1311
|
-
// Clone the slot content always
|
|
1312
|
-
const slotElements = Array.from(slotContainer.value.children)
|
|
1313
|
-
.map(el => el.cloneNode(true) as Element)
|
|
1314
|
-
|
|
1315
|
-
// Only proceed if we have elements to work with or if we need to clear for the first time
|
|
1316
|
-
if (slotElements.length > 0 || (innerElements.value.length > 0 && emptyRebuildAttempts.value === 0)) {
|
|
1317
|
-
// Update elements with clones of slot content
|
|
1318
|
-
innerElements.value = slotElements
|
|
1319
|
-
|
|
1320
|
-
// Adjust current slide if needed
|
|
1321
|
-
if (currentSlide.value >= innerElements.value.length) {
|
|
1322
|
-
currentSlide.value = Math.max(0, innerElements.value.length - 1)
|
|
1323
|
-
}
|
|
1324
|
-
// Stop autoplay during rebuild to prevent errors
|
|
1325
|
-
const wasAutoplay = config.value.autoplay !== 'disabled'
|
|
1326
|
-
if (wasAutoplay) {
|
|
1327
|
-
stopAutoplay()
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
|
-
// Rebuild the slider frame
|
|
1331
|
-
buildSliderFrame()
|
|
1332
|
-
|
|
1333
|
-
// Resume autoplay if it was active and not hovering
|
|
1334
|
-
if (wasAutoplay && !isHovering.value && innerElements.value.length > 0) {
|
|
1335
|
-
// Small delay for stability
|
|
1336
|
-
setTimeout(() => {
|
|
1337
|
-
startAutoplay()
|
|
1338
|
-
}, 50)
|
|
1339
|
-
}
|
|
1340
|
-
}
|
|
1341
|
-
} catch (error) {
|
|
1342
|
-
console.error('Error updating slider:', error)
|
|
1343
|
-
} finally {
|
|
1344
|
-
// Always reconnect observer to the slot container
|
|
1345
|
-
if (slotObserver.value && slotContainer.value) {
|
|
1346
|
-
slotObserver.value.observe(slotContainer.value, {
|
|
1347
|
-
childList: true,
|
|
1348
|
-
subtree: false,
|
|
1349
|
-
attributes: false,
|
|
1350
|
-
characterData: false
|
|
1351
|
-
})
|
|
1352
|
-
}
|
|
1353
|
-
}
|
|
1354
|
-
}
|
|
1355
|
-
|
|
1356
|
-
// Update resetSlider to use slotContainer instead of carouselRef
|
|
1357
|
-
function resetSlider(): void {
|
|
1358
|
-
// Reset the counter to allow rebuilding again
|
|
1359
|
-
emptyRebuildAttempts.value = 0
|
|
1360
|
-
|
|
1361
|
-
// Check if we have content now
|
|
1362
|
-
if (slotContainer.value) {
|
|
1363
|
-
const slotElements = Array.from(slotContainer.value.children)
|
|
1364
|
-
.map(el => el.cloneNode(true) as Element)
|
|
1365
|
-
|
|
1366
|
-
if (slotElements.length > 0) {
|
|
1367
|
-
// We have content now, rebuild
|
|
1368
|
-
innerElements.value = slotElements
|
|
1369
|
-
nextTick(() => { buildSliderFrame() })
|
|
1370
|
-
}
|
|
1371
|
-
}
|
|
1372
|
-
}
|
|
1373
|
-
|
|
1374
|
-
// Expose public methods
|
|
1375
|
-
defineExpose({
|
|
1376
|
-
prev,
|
|
1377
|
-
next,
|
|
1378
|
-
goTo,
|
|
1379
|
-
remove,
|
|
1380
|
-
insert,
|
|
1381
|
-
prepend,
|
|
1382
|
-
append,
|
|
1383
|
-
destroy,
|
|
1384
|
-
currentSlide,
|
|
1385
|
-
pauseAutoplay,
|
|
1386
|
-
resumeAutoplay,
|
|
1387
|
-
updateSlider,
|
|
1388
|
-
resetSlider,
|
|
1389
|
-
})
|
|
1390
|
-
</script>
|
|
1391
|
-
|
|
1392
|
-
<template>
|
|
1393
|
-
<div class="carousel-wrapper">
|
|
1394
|
-
<!-- Hidden container to hold the original slot content -->
|
|
1395
|
-
<div ref="slotContainer" class="carousel-slot-container" style="display: none;">
|
|
1396
|
-
<slot />
|
|
1397
|
-
</div>
|
|
1398
|
-
|
|
1399
|
-
<!-- Visible carousel container that we will manually populate -->
|
|
1400
|
-
<div ref="carouselRef" class="carousel-container">
|
|
1401
|
-
<!-- We'll populate this with cloned content programmatically -->
|
|
1402
|
-
</div>
|
|
1403
|
-
<!-- Dots navigation (Vue-managed) -->
|
|
1404
|
-
<div v-if="props.dots && totalDots > 1" class="carousel-dots">
|
|
1405
|
-
<button
|
|
1406
|
-
v-for="i in totalDots" :key="i" type="button" class="carousel-dot"
|
|
1407
|
-
:class="[{ active: (i - 1) === currentSlide }]" :aria-label="`Go to slide ${i}`" @click="goTo(i - 1)"
|
|
1408
|
-
/>
|
|
1409
|
-
</div>
|
|
1410
|
-
</div>
|
|
1411
|
-
</template>
|
|
1412
|
-
|
|
1413
|
-
<style scoped>
|
|
1414
|
-
.carousel-wrapper {
|
|
1415
|
-
position: relative;
|
|
1416
|
-
width: 100%;
|
|
1417
|
-
}
|
|
1418
|
-
|
|
1419
|
-
.carousel-container {
|
|
1420
|
-
margin: 0 auto;
|
|
1421
|
-
overflow: hidden;
|
|
1422
|
-
}
|
|
1423
|
-
|
|
1424
|
-
.carousel-dots {
|
|
1425
|
-
display: flex;
|
|
1426
|
-
justify-content: center;
|
|
1427
|
-
gap: 8px;
|
|
1428
|
-
margin-top: 16px;
|
|
1429
|
-
}
|
|
1430
|
-
|
|
1431
|
-
.carousel-dot {
|
|
1432
|
-
width: 12px;
|
|
1433
|
-
height: 12px;
|
|
1434
|
-
border-radius: 50px;
|
|
1435
|
-
background-color: var(--bgl-gray-light);
|
|
1436
|
-
border: none;
|
|
1437
|
-
padding: 0;
|
|
1438
|
-
cursor: pointer;
|
|
1439
|
-
transition: all 0.3s ease-in-out;
|
|
1440
|
-
}
|
|
1441
|
-
|
|
1442
|
-
.carousel-dot.active {
|
|
1443
|
-
background-color: var(--bgl-primary);
|
|
1444
|
-
width: 26px;
|
|
1445
|
-
}
|
|
1446
|
-
</style>
|