@bagelink/vue 1.4.14 → 1.4.16
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/AddressSearch.vue.d.ts.map +1 -1
- package/dist/components/Carousel2.vue.d.ts +89 -0
- package/dist/components/Carousel2.vue.d.ts.map +1 -0
- package/dist/components/Dropdown.vue.d.ts.map +1 -1
- package/dist/components/ImportData.vue.d.ts +2 -1
- package/dist/components/ImportData.vue.d.ts.map +1 -1
- package/dist/components/Slider.vue.d.ts +6 -0
- package/dist/components/Slider.vue.d.ts.map +1 -1
- package/dist/components/calendar/CalendarPopover.vue.d.ts.map +1 -1
- package/dist/components/calendar/views/CalendarPopover.vue.d.ts +2 -2
- package/dist/components/calendar/views/CalendarPopover.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/SelectInput.vue.d.ts.map +1 -1
- package/dist/index.cjs +462 -228
- package/dist/index.mjs +463 -229
- package/dist/style.css +99 -99
- package/package.json +1 -1
- package/src/components/ImportData.vue +20 -247
- package/src/components/Slider.vue +508 -85
- package/dist/components/Icon.vue.d.ts +0 -16
- package/dist/components/Icon.vue.d.ts.map +0 -1
- package/dist/components/form/BglFieldSet.vue.d.ts +0 -25
- package/dist/components/form/BglFieldSet.vue.d.ts.map +0 -1
- package/dist/iconify-0J3vK-m1.cjs +0 -1693
- package/dist/iconify-Bc1B42Ak.cjs +0 -1771
- package/dist/iconify-BiLGk5km.js +0 -1693
- package/dist/iconify-DVnNdzog.js +0 -1771
- package/dist/types/timeago.d.ts +0 -23
- package/dist/types/timeago.d.ts.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { sleep, useDevice } from '@bagelink/vue'
|
|
3
|
-
import { ref, onMounted, onBeforeUnmount, watch, computed, nextTick } from 'vue'
|
|
3
|
+
import { ref, onMounted, onBeforeUnmount, watch, computed, nextTick, onUpdated } from 'vue'
|
|
4
4
|
|
|
5
5
|
type Easing = 'ease-in-out' | 'ease-in' | 'ease-out' | 'linear'
|
|
6
6
|
type AutoplayMode = 'disabled' | 'standard' | 'linear'
|
|
@@ -67,6 +67,9 @@ const { innerWidth } = useDevice()
|
|
|
67
67
|
const transformProperty = ref('transform')
|
|
68
68
|
const pointerDown = ref(false)
|
|
69
69
|
|
|
70
|
+
// Add a flag to track if we're in the middle of initializing or rebuilding
|
|
71
|
+
const isBuilding = ref(false)
|
|
72
|
+
|
|
70
73
|
// Drag-related refs
|
|
71
74
|
const drag = ref<DragState>({
|
|
72
75
|
startX: 0,
|
|
@@ -86,6 +89,10 @@ const linearAnimationFrame = ref<number | null>(null)
|
|
|
86
89
|
const isHovering = ref(false)
|
|
87
90
|
const linearOffset = ref(0)
|
|
88
91
|
|
|
92
|
+
// Add a counter to track rebuild attempts with no items
|
|
93
|
+
const emptyRebuildAttempts = ref(0)
|
|
94
|
+
const MAX_EMPTY_REBUILD_ATTEMPTS = 3
|
|
95
|
+
|
|
89
96
|
// Modified to work with optional parameters and ensure compatibility with Required type
|
|
90
97
|
type ComputedConfig = Omit<Required<CarouselOptions>, 'autoplay'> & { autoplay: AutoplayMode }
|
|
91
98
|
|
|
@@ -146,12 +153,67 @@ const totalDots = computed(() => {
|
|
|
146
153
|
return config.value.loop ? innerElements.value.length : Math.ceil(innerElements.value.length - perPage.value + 1)
|
|
147
154
|
})
|
|
148
155
|
|
|
156
|
+
// Add a flag to track slot updates
|
|
157
|
+
const slotHasChanged = ref(false)
|
|
158
|
+
|
|
159
|
+
// Add MutationObserver ref
|
|
160
|
+
const slotObserver = ref<MutationObserver | null>(null)
|
|
161
|
+
|
|
162
|
+
// Add a flag to track component mounted state
|
|
163
|
+
const isMounted = ref(false)
|
|
164
|
+
|
|
165
|
+
// Add a reference to the slot container
|
|
166
|
+
const slotContainer = ref<HTMLDivElement | null>(null)
|
|
167
|
+
|
|
149
168
|
// Lifecycle hooks
|
|
150
|
-
onMounted(() =>
|
|
169
|
+
onMounted(() => {
|
|
170
|
+
// Set the mounted flag first
|
|
171
|
+
isMounted.value = true
|
|
172
|
+
|
|
173
|
+
// Wait a tick to ensure slot content is rendered
|
|
174
|
+
nextTick(() => {
|
|
175
|
+
init()
|
|
176
|
+
})
|
|
177
|
+
})
|
|
151
178
|
|
|
152
179
|
onBeforeUnmount(() => {
|
|
180
|
+
// Clean up
|
|
181
|
+
isMounted.value = false
|
|
153
182
|
stopAutoplay()
|
|
154
183
|
detachEvents()
|
|
184
|
+
|
|
185
|
+
// Remove the observer
|
|
186
|
+
if (slotObserver.value) {
|
|
187
|
+
slotObserver.value.disconnect()
|
|
188
|
+
slotObserver.value = null
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Clear DOM content
|
|
192
|
+
if (carouselRef.value) {
|
|
193
|
+
carouselRef.value.innerHTML = ''
|
|
194
|
+
}
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
// Add an onUpdated hook that checks for slot content changes
|
|
198
|
+
onUpdated(() => {
|
|
199
|
+
// Only check if we're already initialized
|
|
200
|
+
if (isMounted.value && !isBuilding.value && emptyRebuildAttempts.value < MAX_EMPTY_REBUILD_ATTEMPTS) {
|
|
201
|
+
// Check if slot content changed
|
|
202
|
+
if (slotContainer.value) {
|
|
203
|
+
const slotElements = Array.from(slotContainer.value.children)
|
|
204
|
+
|
|
205
|
+
// Only update if count changed or first initialization
|
|
206
|
+
if (slotElements.length !== innerElements.value.length || innerElements.value.length === 0) {
|
|
207
|
+
// Clone the elements from slot
|
|
208
|
+
innerElements.value = slotElements.map(el => el.cloneNode(true) as Element)
|
|
209
|
+
|
|
210
|
+
// Schedule rebuild
|
|
211
|
+
nextTick(() => {
|
|
212
|
+
updateSlider()
|
|
213
|
+
})
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
155
217
|
})
|
|
156
218
|
|
|
157
219
|
// Watch for changes to props
|
|
@@ -224,15 +286,17 @@ function detachEvents(): void {
|
|
|
224
286
|
|
|
225
287
|
// Core functionality
|
|
226
288
|
async function init(): Promise<void> {
|
|
227
|
-
if (!carouselRef.value) return
|
|
289
|
+
if (!carouselRef.value || !slotContainer.value || !isMounted.value) return
|
|
228
290
|
|
|
229
291
|
transformProperty.value = webkitOrNot()
|
|
230
292
|
|
|
231
293
|
// Add a small delay to ensure DOM is ready
|
|
232
294
|
await sleep(10)
|
|
233
295
|
|
|
234
|
-
// Create references
|
|
235
|
-
innerElements.value = Array.from(
|
|
296
|
+
// Create references by cloning slot content
|
|
297
|
+
innerElements.value = Array.from(slotContainer.value.children).map(
|
|
298
|
+
el => el.cloneNode(true) as Element
|
|
299
|
+
)
|
|
236
300
|
|
|
237
301
|
if (!innerElements.value.length) {
|
|
238
302
|
console.warn('No carousel items found during initialization')
|
|
@@ -255,7 +319,8 @@ async function init(): Promise<void> {
|
|
|
255
319
|
// Attach events
|
|
256
320
|
attachEvents()
|
|
257
321
|
|
|
258
|
-
|
|
322
|
+
// Set up the MutationObserver to watch for slot changes
|
|
323
|
+
setupSlotObserver()
|
|
259
324
|
|
|
260
325
|
// Build slider frame
|
|
261
326
|
buildSliderFrame()
|
|
@@ -270,75 +335,171 @@ async function init(): Promise<void> {
|
|
|
270
335
|
}
|
|
271
336
|
|
|
272
337
|
function buildSliderFrame(): void {
|
|
273
|
-
if (!carouselRef.value) return
|
|
338
|
+
if (!carouselRef.value || !isMounted.value) return
|
|
274
339
|
|
|
275
|
-
//
|
|
276
|
-
|
|
277
|
-
console.warn('No carousel items found')
|
|
278
|
-
return
|
|
279
|
-
}
|
|
340
|
+
// Set building flag to prevent interactions during rebuild
|
|
341
|
+
isBuilding.value = true
|
|
280
342
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
343
|
+
try {
|
|
344
|
+
// Ensure we have elements to build the slider
|
|
345
|
+
if (!innerElements.value.length) {
|
|
346
|
+
console.warn('No carousel items found')
|
|
347
|
+
emptyRebuildAttempts.value++
|
|
348
|
+
isBuilding.value = false
|
|
349
|
+
return
|
|
350
|
+
}
|
|
285
351
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
sliderFrame.value = document.createElement('div')
|
|
289
|
-
}
|
|
352
|
+
// Reset counter since we have elements
|
|
353
|
+
emptyRebuildAttempts.value = 0
|
|
290
354
|
|
|
291
|
-
|
|
292
|
-
|
|
355
|
+
const originalCount = innerElements.value.length
|
|
356
|
+
const widthItem = selectorWidth.value / perPage.value
|
|
357
|
+
const itemsToBuild = config.value.loop
|
|
358
|
+
? originalCount + (2 * perPage.value)
|
|
359
|
+
: originalCount
|
|
293
360
|
|
|
294
|
-
|
|
295
|
-
carouselRef.value.
|
|
296
|
-
|
|
361
|
+
// Create a clean slate for the slider
|
|
362
|
+
carouselRef.value.innerHTML = ''
|
|
363
|
+
|
|
364
|
+
// Create or recreate the slider frame
|
|
365
|
+
if (!sliderFrame.value) {
|
|
366
|
+
sliderFrame.value = document.createElement('div')
|
|
367
|
+
} else {
|
|
368
|
+
// Clear existing content
|
|
369
|
+
while (sliderFrame.value.firstChild) {
|
|
370
|
+
sliderFrame.value.removeChild(sliderFrame.value.firstChild)
|
|
371
|
+
}
|
|
372
|
+
}
|
|
297
373
|
|
|
298
|
-
|
|
299
|
-
|
|
374
|
+
sliderFrame.value.style.width = `${widthItem * itemsToBuild}px`
|
|
375
|
+
enableTransition()
|
|
376
|
+
|
|
377
|
+
if (config.value.draggable) {
|
|
378
|
+
carouselRef.value.style.cursor = '-webkit-grab'
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Create a fragment to hold all slider content
|
|
382
|
+
const docFragment = document.createDocumentFragment()
|
|
300
383
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
384
|
+
// Add clones before original items (for loop mode)
|
|
385
|
+
if (config.value.loop && innerElements.value.length > 0) {
|
|
386
|
+
for (let i = originalCount - perPage.value; i < originalCount; i++) {
|
|
387
|
+
if (i >= 0 && i < originalCount) {
|
|
388
|
+
const original = innerElements.value[i]
|
|
389
|
+
const clone = original.cloneNode(true) as Element
|
|
390
|
+
|
|
391
|
+
if (clone instanceof HTMLElement) {
|
|
392
|
+
clone.setAttribute('data-clone', 'before')
|
|
393
|
+
copyAttributesAndEvents(original, clone)
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const wrapped = buildSliderFrameItem(clone)
|
|
397
|
+
docFragment.appendChild(wrapped)
|
|
398
|
+
}
|
|
308
399
|
}
|
|
309
400
|
}
|
|
310
|
-
}
|
|
311
401
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
402
|
+
// Add main items (already clones of the slot content)
|
|
403
|
+
for (let i = 0; i < originalCount; i++) {
|
|
404
|
+
const element = innerElements.value[i]
|
|
405
|
+
const wrapped = buildSliderFrameItem(element.cloneNode(true) as Element)
|
|
406
|
+
docFragment.appendChild(wrapped)
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Add clones after original items (for loop mode)
|
|
410
|
+
if (config.value.loop && innerElements.value.length > 0) {
|
|
411
|
+
for (let i = 0; i < perPage.value; i++) {
|
|
412
|
+
if (i >= 0 && i < originalCount) {
|
|
413
|
+
const original = innerElements.value[i]
|
|
414
|
+
const clone = original.cloneNode(true) as Element
|
|
415
|
+
|
|
416
|
+
if (clone instanceof HTMLElement) {
|
|
417
|
+
clone.setAttribute('data-clone', 'after')
|
|
418
|
+
copyAttributesAndEvents(original, clone)
|
|
419
|
+
}
|
|
317
420
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
// Ensure the index is valid
|
|
322
|
-
if (i >= 0 && i < innerElements.value.length) {
|
|
323
|
-
const element = buildSliderFrameItem(innerElements.value[i].cloneNode(true) as Element)
|
|
324
|
-
docFragment.appendChild(element)
|
|
421
|
+
const wrapped = buildSliderFrameItem(clone)
|
|
422
|
+
docFragment.appendChild(wrapped)
|
|
423
|
+
}
|
|
325
424
|
}
|
|
326
425
|
}
|
|
327
|
-
}
|
|
328
426
|
|
|
329
|
-
|
|
330
|
-
if (sliderFrame.value) {
|
|
331
|
-
sliderFrame.value.innerHTML = ''
|
|
427
|
+
// Append all content to the slider frame
|
|
332
428
|
sliderFrame.value.appendChild(docFragment)
|
|
333
429
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
430
|
+
// Add the slider frame to the carousel
|
|
431
|
+
carouselRef.value.appendChild(sliderFrame.value)
|
|
432
|
+
|
|
433
|
+
// Update currentSlide if needed to stay within bounds
|
|
434
|
+
if (currentSlide.value >= innerElements.value.length) {
|
|
435
|
+
currentSlide.value = Math.max(0, innerElements.value.length - 1)
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Set initial position
|
|
439
|
+
slideToCurrent()
|
|
440
|
+
} catch (error) {
|
|
441
|
+
console.error('Error building slider frame:', error)
|
|
442
|
+
} finally {
|
|
443
|
+
// Always make sure to reset the building flag
|
|
444
|
+
isBuilding.value = false
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Helper function to copy attributes and events from original to clone
|
|
449
|
+
function copyAttributesAndEvents(original: Element, clone: Element): void {
|
|
450
|
+
if (!(original instanceof HTMLElement) || !(clone instanceof HTMLElement)) {
|
|
451
|
+
return
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Copy all attributes
|
|
455
|
+
const allAttrs = original.attributes
|
|
456
|
+
for (let i = 0; i < allAttrs.length; i++) {
|
|
457
|
+
const attr = allAttrs[i]
|
|
458
|
+
if (attr.name !== 'id') { // Skip id to avoid duplicate IDs
|
|
459
|
+
clone.setAttribute(attr.name, attr.value)
|
|
337
460
|
}
|
|
338
461
|
}
|
|
339
462
|
|
|
340
|
-
//
|
|
341
|
-
|
|
463
|
+
// For Vue-specific elements, add a custom click handler that relays to the original
|
|
464
|
+
clone.addEventListener('click', (e) => {
|
|
465
|
+
// Check if original has a click handler
|
|
466
|
+
if (typeof (original as any).click === 'function') {
|
|
467
|
+
// We need to simulate the click on the original element
|
|
468
|
+
// Create and dispatch a new click event on the original element
|
|
469
|
+
const clickEvent = new MouseEvent('click', {
|
|
470
|
+
bubbles: true,
|
|
471
|
+
cancelable: true,
|
|
472
|
+
view: window
|
|
473
|
+
})
|
|
474
|
+
original.dispatchEvent(clickEvent)
|
|
475
|
+
|
|
476
|
+
// Stop propagation of the clone's event
|
|
477
|
+
e.stopPropagation()
|
|
478
|
+
}
|
|
479
|
+
})
|
|
480
|
+
|
|
481
|
+
// Special handling for links
|
|
482
|
+
if (original.tagName === 'A' && clone.tagName === 'A') {
|
|
483
|
+
const originalHref = original.getAttribute('href')
|
|
484
|
+
if (originalHref) {
|
|
485
|
+
clone.addEventListener('click', (e) => {
|
|
486
|
+
e.preventDefault()
|
|
487
|
+
|
|
488
|
+
// Get original onClick behavior
|
|
489
|
+
if (original.onclick) {
|
|
490
|
+
original.onclick(new MouseEvent('click'))
|
|
491
|
+
} else {
|
|
492
|
+
// Default behavior - navigate like original link would
|
|
493
|
+
const targetBlank = original.getAttribute('target') === '_blank'
|
|
494
|
+
if (targetBlank) {
|
|
495
|
+
window.open(originalHref, '_blank')
|
|
496
|
+
} else {
|
|
497
|
+
window.location.href = originalHref
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
})
|
|
501
|
+
}
|
|
502
|
+
}
|
|
342
503
|
}
|
|
343
504
|
|
|
344
505
|
function buildSliderFrameItem(elm: Element): HTMLElement {
|
|
@@ -346,39 +507,51 @@ function buildSliderFrameItem(elm: Element): HTMLElement {
|
|
|
346
507
|
elementContainer.style.cssFloat = config.value.rtl ? 'right' : 'left'
|
|
347
508
|
elementContainer.style.float = config.value.rtl ? 'right' : 'left'
|
|
348
509
|
elementContainer.style.padding = config.value.slideGap ? `${config.value.slideGap / 2}rem` : '0'
|
|
510
|
+
|
|
349
511
|
const percentage = config.value.loop
|
|
350
|
-
? 100 / (innerElements.value.length + (perPage.value
|
|
512
|
+
? 100 / (innerElements.value.length + (2 * perPage.value))
|
|
351
513
|
: 100 / innerElements.value.length
|
|
352
514
|
|
|
353
515
|
elementContainer.style.width = `${percentage}%`
|
|
354
516
|
elementContainer.appendChild(elm)
|
|
355
|
-
|
|
356
517
|
return elementContainer
|
|
357
518
|
}
|
|
358
519
|
|
|
359
520
|
function slideToCurrent(enableTransitionFlag?: boolean): void {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
521
|
+
try {
|
|
522
|
+
// Ensure component is still mounted
|
|
523
|
+
if (!isMounted.value || !innerElements.value.length || !sliderFrame.value) return
|
|
524
|
+
|
|
525
|
+
const currentSlideValue = config.value.loop
|
|
526
|
+
? currentSlide.value + perPage.value
|
|
527
|
+
: currentSlide.value
|
|
363
528
|
|
|
364
|
-
|
|
529
|
+
const offset = (config.value.rtl ? 1 : -1) * currentSlideValue * (selectorWidth.value / perPage.value)
|
|
365
530
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
requestAnimationFrame(() => {
|
|
531
|
+
if (enableTransitionFlag && sliderFrame.value) {
|
|
532
|
+
// Use requestAnimationFrame for smooth transitions
|
|
369
533
|
requestAnimationFrame(() => {
|
|
370
|
-
|
|
371
|
-
if (sliderFrame.value)
|
|
534
|
+
// Check again that we're still mounted and have the frame
|
|
535
|
+
if (!isMounted.value || !sliderFrame.value) return
|
|
536
|
+
|
|
537
|
+
requestAnimationFrame(() => {
|
|
538
|
+
if (!isMounted.value || !sliderFrame.value) return
|
|
539
|
+
|
|
540
|
+
enableTransition()
|
|
372
541
|
sliderFrame.value.style[transformProperty.value as any] = `translate3d(${offset}px, 0, 0)`
|
|
373
|
-
}
|
|
542
|
+
})
|
|
374
543
|
})
|
|
375
|
-
})
|
|
376
|
-
|
|
377
|
-
|
|
544
|
+
} else if (sliderFrame.value) {
|
|
545
|
+
sliderFrame.value.style[transformProperty.value as any] = `translate3d(${offset}px, 0, 0)`
|
|
546
|
+
}
|
|
547
|
+
} catch (error) {
|
|
548
|
+
console.error('Error in slideToCurrent:', error)
|
|
378
549
|
}
|
|
379
550
|
}
|
|
380
551
|
|
|
381
552
|
function prev(howManySlides: number = 1): void {
|
|
553
|
+
if (!isMounted.value || isBuilding.value || !innerElements.value.length) return
|
|
554
|
+
|
|
382
555
|
const beforeChange = currentSlide.value
|
|
383
556
|
|
|
384
557
|
if (config.value.loop) {
|
|
@@ -415,6 +588,8 @@ function prev(howManySlides: number = 1): void {
|
|
|
415
588
|
}
|
|
416
589
|
|
|
417
590
|
function next(howManySlides: number = 1): void {
|
|
591
|
+
if (!isMounted.value || isBuilding.value || !innerElements.value.length) return
|
|
592
|
+
|
|
418
593
|
const beforeChange = currentSlide.value
|
|
419
594
|
|
|
420
595
|
if (config.value.loop) {
|
|
@@ -482,6 +657,8 @@ function enableTransition(): void {
|
|
|
482
657
|
}
|
|
483
658
|
|
|
484
659
|
function updateAfterDrag(): void {
|
|
660
|
+
if (!isMounted.value) return
|
|
661
|
+
|
|
485
662
|
const movement = (config.value.rtl ? -1 : 1) * (drag.value.endX - drag.value.startX)
|
|
486
663
|
const movementDistance = Math.abs(movement)
|
|
487
664
|
const howManySliderToSlide = config.value.multipleDrag
|
|
@@ -530,6 +707,8 @@ function clearDrag(): void {
|
|
|
530
707
|
|
|
531
708
|
// Touch event handlers
|
|
532
709
|
function touchstartHandler(e: TouchEvent): void {
|
|
710
|
+
if (isBuilding.value || !isMounted.value) return
|
|
711
|
+
|
|
533
712
|
const target = e.target as HTMLElement
|
|
534
713
|
const ignoreTags = ['TEXTAREA', 'OPTION', 'INPUT', 'SELECT'].includes(target.nodeName)
|
|
535
714
|
|
|
@@ -552,9 +731,44 @@ function touchstartHandler(e: TouchEvent): void {
|
|
|
552
731
|
if (isLink || isImage) {
|
|
553
732
|
drag.value.preventClick = false
|
|
554
733
|
}
|
|
734
|
+
|
|
735
|
+
// When starting drag during linear autoplay, save the current position
|
|
736
|
+
if (config.value.autoplay === 'linear' && sliderFrame.value) {
|
|
737
|
+
// Get the current transform value and save it to apply during dragging
|
|
738
|
+
const currentTransform = getComputedStyle(sliderFrame.value)[transformProperty.value as any]
|
|
739
|
+
if (currentTransform && currentTransform !== 'none') {
|
|
740
|
+
try {
|
|
741
|
+
// Parse the transform matrix to get the current X position
|
|
742
|
+
const matrix = new DOMMatrix(currentTransform)
|
|
743
|
+
const currentX = matrix.m41
|
|
744
|
+
|
|
745
|
+
// Convert position to slides and update currentSlide
|
|
746
|
+
const itemWidth = selectorWidth.value / perPage.value
|
|
747
|
+
if (config.value.loop) {
|
|
748
|
+
// Adjust for the perPage offset in loop mode
|
|
749
|
+
const totalPosition = Math.abs(currentX)
|
|
750
|
+
const slidePosition = Math.round(totalPosition / itemWidth) - perPage.value
|
|
751
|
+
currentSlide.value = slidePosition >= 0
|
|
752
|
+
? slidePosition % innerElements.value.length
|
|
753
|
+
: (innerElements.value.length + (slidePosition % innerElements.value.length)) % innerElements.value.length
|
|
754
|
+
} else {
|
|
755
|
+
// In non-loop mode, just calculate the current slide
|
|
756
|
+
const slidePosition = Math.round(Math.abs(currentX) / itemWidth)
|
|
757
|
+
currentSlide.value = Math.min(Math.max(slidePosition, 0), innerElements.value.length - perPage.value)
|
|
758
|
+
}
|
|
759
|
+
} catch (e) {
|
|
760
|
+
console.error('Error parsing transform matrix:', e)
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// Stop the animation immediately
|
|
765
|
+
stopAutoplay()
|
|
766
|
+
}
|
|
555
767
|
}
|
|
556
768
|
|
|
557
769
|
function touchendHandler(e: TouchEvent): void {
|
|
770
|
+
if (isBuilding.value || !isMounted.value) return
|
|
771
|
+
|
|
558
772
|
e.stopPropagation()
|
|
559
773
|
|
|
560
774
|
// If we were dragging, prevent clicks by adding a short-lived capture event listener
|
|
@@ -581,12 +795,14 @@ function touchendHandler(e: TouchEvent): void {
|
|
|
581
795
|
clearDrag()
|
|
582
796
|
|
|
583
797
|
// Restart autoplay after touch interaction
|
|
584
|
-
if (config.value.autoplay !== 'disabled') {
|
|
798
|
+
if (config.value.autoplay !== 'disabled' && !isHovering.value && isMounted.value) {
|
|
585
799
|
startAutoplay()
|
|
586
800
|
}
|
|
587
801
|
}
|
|
588
802
|
|
|
589
803
|
function touchmoveHandler(e: TouchEvent): void {
|
|
804
|
+
if (isBuilding.value || !isMounted.value) return
|
|
805
|
+
|
|
590
806
|
e.stopPropagation()
|
|
591
807
|
|
|
592
808
|
if (drag.value.letItGo === null) {
|
|
@@ -628,6 +844,8 @@ function touchmoveHandler(e: TouchEvent): void {
|
|
|
628
844
|
|
|
629
845
|
// Mouse event handlers
|
|
630
846
|
function mousedownHandler(e: MouseEvent): void {
|
|
847
|
+
if (isBuilding.value || !isMounted.value) return
|
|
848
|
+
|
|
631
849
|
const target = e.target as HTMLElement
|
|
632
850
|
const ignoreTags = ['TEXTAREA', 'OPTION', 'INPUT', 'SELECT'].includes(target.nodeName)
|
|
633
851
|
|
|
@@ -648,9 +866,44 @@ function mousedownHandler(e: MouseEvent): void {
|
|
|
648
866
|
if (isLink || isImage) {
|
|
649
867
|
drag.value.preventClick = false // Start with false, will be set to true if actual dragging occurs
|
|
650
868
|
}
|
|
869
|
+
|
|
870
|
+
// When starting drag during linear autoplay, save the current position
|
|
871
|
+
if (config.value.autoplay === 'linear' && sliderFrame.value) {
|
|
872
|
+
// Get the current transform value and save it to apply during dragging
|
|
873
|
+
const currentTransform = getComputedStyle(sliderFrame.value)[transformProperty.value as any]
|
|
874
|
+
if (currentTransform && currentTransform !== 'none') {
|
|
875
|
+
try {
|
|
876
|
+
// Parse the transform matrix to get the current X position
|
|
877
|
+
const matrix = new DOMMatrix(currentTransform)
|
|
878
|
+
const currentX = matrix.m41
|
|
879
|
+
|
|
880
|
+
// Convert position to slides and update currentSlide
|
|
881
|
+
const itemWidth = selectorWidth.value / perPage.value
|
|
882
|
+
if (config.value.loop) {
|
|
883
|
+
// Adjust for the perPage offset in loop mode
|
|
884
|
+
const totalPosition = Math.abs(currentX)
|
|
885
|
+
const slidePosition = Math.round(totalPosition / itemWidth) - perPage.value
|
|
886
|
+
currentSlide.value = slidePosition >= 0
|
|
887
|
+
? slidePosition % innerElements.value.length
|
|
888
|
+
: (innerElements.value.length + (slidePosition % innerElements.value.length)) % innerElements.value.length
|
|
889
|
+
} else {
|
|
890
|
+
// In non-loop mode, just calculate the current slide
|
|
891
|
+
const slidePosition = Math.round(Math.abs(currentX) / itemWidth)
|
|
892
|
+
currentSlide.value = Math.min(Math.max(slidePosition, 0), innerElements.value.length - perPage.value)
|
|
893
|
+
}
|
|
894
|
+
} catch (e) {
|
|
895
|
+
console.error('Error parsing transform matrix:', e)
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// Stop the animation immediately
|
|
900
|
+
stopAutoplay()
|
|
901
|
+
}
|
|
651
902
|
}
|
|
652
903
|
|
|
653
904
|
function mouseupHandler(e: MouseEvent): void {
|
|
905
|
+
if (isBuilding.value || !isMounted.value) return
|
|
906
|
+
|
|
654
907
|
e.stopPropagation()
|
|
655
908
|
|
|
656
909
|
// If we were dragging, prevent clicks by adding a short-lived capture event listener
|
|
@@ -682,12 +935,14 @@ function mouseupHandler(e: MouseEvent): void {
|
|
|
682
935
|
clearDrag()
|
|
683
936
|
|
|
684
937
|
// Restart autoplay after mouse interaction if not hovering
|
|
685
|
-
if (config.value.autoplay !== 'disabled' && !isHovering.value) {
|
|
938
|
+
if (config.value.autoplay !== 'disabled' && !isHovering.value && isMounted.value) {
|
|
686
939
|
startAutoplay()
|
|
687
940
|
}
|
|
688
941
|
}
|
|
689
942
|
|
|
690
943
|
function mousemoveHandler(e: MouseEvent): void {
|
|
944
|
+
if (isBuilding.value || !isMounted.value) return
|
|
945
|
+
|
|
691
946
|
e.preventDefault()
|
|
692
947
|
|
|
693
948
|
if (pointerDown.value) {
|
|
@@ -726,6 +981,8 @@ function mousemoveHandler(e: MouseEvent): void {
|
|
|
726
981
|
}
|
|
727
982
|
|
|
728
983
|
function mouseleaveHandler(e: MouseEvent): void {
|
|
984
|
+
if (isBuilding.value || !isMounted.value) return
|
|
985
|
+
|
|
729
986
|
if (pointerDown.value) {
|
|
730
987
|
pointerDown.value = false
|
|
731
988
|
|
|
@@ -742,6 +999,8 @@ function mouseleaveHandler(e: MouseEvent): void {
|
|
|
742
999
|
}
|
|
743
1000
|
|
|
744
1001
|
function clickHandler(e: MouseEvent): void {
|
|
1002
|
+
if (isBuilding.value || !isMounted.value) return
|
|
1003
|
+
|
|
745
1004
|
if (drag.value.preventClick || isDragging.value) {
|
|
746
1005
|
e.preventDefault()
|
|
747
1006
|
e.stopPropagation()
|
|
@@ -754,6 +1013,8 @@ function clickHandler(e: MouseEvent): void {
|
|
|
754
1013
|
|
|
755
1014
|
// Add this new function to handle dragstart events
|
|
756
1015
|
function dragstartHandler(e: DragEvent): void {
|
|
1016
|
+
if (isBuilding.value || !isMounted.value) return // Prevent interaction during build
|
|
1017
|
+
|
|
757
1018
|
// Prevent default drag behavior for images and links
|
|
758
1019
|
if (config.value.draggable) {
|
|
759
1020
|
const target = e.target as HTMLElement
|
|
@@ -835,11 +1096,14 @@ function destroy(restoreMarkup: boolean = false): void {
|
|
|
835
1096
|
|
|
836
1097
|
// Add after init()
|
|
837
1098
|
async function startAutoplay(): Promise<void> {
|
|
1099
|
+
if (!isMounted.value) return // Don't start if not mounted
|
|
1100
|
+
|
|
838
1101
|
stopAutoplay() // Clear any existing timers first
|
|
839
1102
|
|
|
840
1103
|
if (config.value.autoplay === 'standard') {
|
|
841
1104
|
autoplayTimer.value = window.setInterval(() => {
|
|
842
|
-
if
|
|
1105
|
+
// Check if component is still mounted before advancing
|
|
1106
|
+
if (isMounted.value && !isHovering.value && !isBuilding.value) {
|
|
843
1107
|
next()
|
|
844
1108
|
}
|
|
845
1109
|
}, config.value.autoplayInterval)
|
|
@@ -861,12 +1125,21 @@ function stopAutoplay(): void {
|
|
|
861
1125
|
}
|
|
862
1126
|
|
|
863
1127
|
function startLinearAutoplay(): void {
|
|
864
|
-
|
|
865
|
-
|
|
1128
|
+
if (!isMounted.value || !innerElements.value.length) return
|
|
1129
|
+
|
|
1130
|
+
// Reset the linear offset to the current slide position
|
|
1131
|
+
const itemWidth = selectorWidth.value / perPage.value
|
|
1132
|
+
linearOffset.value = currentSlide.value * itemWidth
|
|
866
1133
|
let lastTimestamp = 0
|
|
867
1134
|
|
|
868
1135
|
// Animation function
|
|
869
1136
|
const animate = (timestamp: number) => {
|
|
1137
|
+
// Always verify that the component is still mounted
|
|
1138
|
+
if (!isMounted.value || !sliderFrame.value) {
|
|
1139
|
+
stopAutoplay()
|
|
1140
|
+
return
|
|
1141
|
+
}
|
|
1142
|
+
|
|
870
1143
|
if (!lastTimestamp) lastTimestamp = timestamp
|
|
871
1144
|
|
|
872
1145
|
// Calculate time delta and convert to pixels based on speed
|
|
@@ -884,7 +1157,6 @@ function startLinearAutoplay(): void {
|
|
|
884
1157
|
linearOffset.value += pixelsToMove
|
|
885
1158
|
|
|
886
1159
|
// Calculate total width of all items
|
|
887
|
-
const itemWidth = selectorWidth.value / perPage.value
|
|
888
1160
|
const totalWidth = itemWidth * innerElements.value.length
|
|
889
1161
|
|
|
890
1162
|
// If we've scrolled past the entire content, reset
|
|
@@ -895,16 +1167,22 @@ function startLinearAutoplay(): void {
|
|
|
895
1167
|
// If not looping, stop at the end
|
|
896
1168
|
linearOffset.value = totalWidth - itemWidth
|
|
897
1169
|
stopAutoplay()
|
|
898
|
-
return
|
|
899
1170
|
}
|
|
900
1171
|
}
|
|
901
1172
|
|
|
902
1173
|
// Apply the offset using transforms
|
|
903
|
-
if (sliderFrame.value) {
|
|
1174
|
+
if (sliderFrame.value && !isBuilding.value) {
|
|
904
1175
|
const direction = config.value.rtl ? 1 : -1
|
|
905
1176
|
sliderFrame.value.style.transition = 'none' // Smooth scrolling, no easing
|
|
1177
|
+
|
|
1178
|
+
// In loop mode, we need to offset by perPage
|
|
1179
|
+
let displayOffset = linearOffset.value
|
|
1180
|
+
if (config.value.loop) {
|
|
1181
|
+
displayOffset = linearOffset.value + (perPage.value * itemWidth)
|
|
1182
|
+
}
|
|
1183
|
+
|
|
906
1184
|
sliderFrame.value.style[transformProperty.value as any]
|
|
907
|
-
= `translate3d(${direction *
|
|
1185
|
+
= `translate3d(${direction * displayOffset}px, 0, 0)`
|
|
908
1186
|
}
|
|
909
1187
|
|
|
910
1188
|
// Update current slide based on the offset
|
|
@@ -914,12 +1192,16 @@ function startLinearAutoplay(): void {
|
|
|
914
1192
|
config.value.onChange()
|
|
915
1193
|
}
|
|
916
1194
|
|
|
917
|
-
// Continue animation loop
|
|
918
|
-
|
|
1195
|
+
// Continue animation loop only if mounted
|
|
1196
|
+
if (isMounted.value) {
|
|
1197
|
+
linearAnimationFrame.value = requestAnimationFrame(animate)
|
|
1198
|
+
}
|
|
919
1199
|
}
|
|
920
1200
|
|
|
921
|
-
// Start the animation
|
|
922
|
-
|
|
1201
|
+
// Start the animation only if mounted
|
|
1202
|
+
if (isMounted.value) {
|
|
1203
|
+
linearAnimationFrame.value = requestAnimationFrame(animate)
|
|
1204
|
+
}
|
|
923
1205
|
}
|
|
924
1206
|
|
|
925
1207
|
// Event handlers for autoplay pausing
|
|
@@ -942,6 +1224,137 @@ function resumeAutoplay(): void {
|
|
|
942
1224
|
}
|
|
943
1225
|
}
|
|
944
1226
|
|
|
1227
|
+
// Add function to set up the MutationObserver
|
|
1228
|
+
function setupSlotObserver(): void {
|
|
1229
|
+
if (!slotContainer.value || slotObserver.value) return
|
|
1230
|
+
|
|
1231
|
+
// Create a new MutationObserver
|
|
1232
|
+
slotObserver.value = new MutationObserver((mutations) => {
|
|
1233
|
+
// Skip if building or over rebuild limit
|
|
1234
|
+
if (isBuilding.value || !isMounted.value
|
|
1235
|
+
|| emptyRebuildAttempts.value >= MAX_EMPTY_REBUILD_ATTEMPTS) {
|
|
1236
|
+
return
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
let needsRebuild = false
|
|
1240
|
+
|
|
1241
|
+
for (const mutation of mutations) {
|
|
1242
|
+
if (mutation.type === 'childList') {
|
|
1243
|
+
needsRebuild = true
|
|
1244
|
+
break
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
if (needsRebuild) {
|
|
1249
|
+
nextTick(() => {
|
|
1250
|
+
if (isMounted.value && slotContainer.value) {
|
|
1251
|
+
// Update by cloning the new content
|
|
1252
|
+
innerElements.value = Array.from(slotContainer.value.children).map(
|
|
1253
|
+
el => el.cloneNode(true) as Element
|
|
1254
|
+
)
|
|
1255
|
+
|
|
1256
|
+
updateSlider()
|
|
1257
|
+
}
|
|
1258
|
+
})
|
|
1259
|
+
}
|
|
1260
|
+
})
|
|
1261
|
+
|
|
1262
|
+
// Start observing
|
|
1263
|
+
slotObserver.value.observe(slotContainer.value, {
|
|
1264
|
+
childList: true,
|
|
1265
|
+
subtree: false,
|
|
1266
|
+
attributes: false,
|
|
1267
|
+
characterData: false
|
|
1268
|
+
})
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
// Modify updateSlider to safely handle DOM references and always use slotContainer
|
|
1272
|
+
function updateSlider(): void {
|
|
1273
|
+
if (!carouselRef.value || !slotContainer.value || isBuilding.value || !isMounted.value) return
|
|
1274
|
+
|
|
1275
|
+
// Prevent too many empty rebuild attempts
|
|
1276
|
+
if (emptyRebuildAttempts.value >= MAX_EMPTY_REBUILD_ATTEMPTS) {
|
|
1277
|
+
console.warn('Too many rebuild attempts with no items, stopping')
|
|
1278
|
+
return
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
try {
|
|
1282
|
+
// Temporarily disconnect observer to avoid recursive calls
|
|
1283
|
+
if (slotObserver.value) {
|
|
1284
|
+
slotObserver.value.disconnect()
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
// Clone the slot content always
|
|
1288
|
+
const slotElements = Array.from(slotContainer.value.children)
|
|
1289
|
+
.map(el => el.cloneNode(true) as Element)
|
|
1290
|
+
|
|
1291
|
+
// Only proceed if we have elements to work with or if we need to clear for the first time
|
|
1292
|
+
if (slotElements.length > 0 || (innerElements.value.length > 0 && emptyRebuildAttempts.value === 0)) {
|
|
1293
|
+
// Store previous count for comparison
|
|
1294
|
+
const previousCount = innerElements.value.length
|
|
1295
|
+
|
|
1296
|
+
// Update elements with clones of slot content
|
|
1297
|
+
innerElements.value = slotElements
|
|
1298
|
+
|
|
1299
|
+
// Adjust current slide if needed
|
|
1300
|
+
if (currentSlide.value >= innerElements.value.length) {
|
|
1301
|
+
currentSlide.value = Math.max(0, innerElements.value.length - 1)
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
// If we're transitioning from 0 to some elements or vice versa,
|
|
1305
|
+
// we need special handling
|
|
1306
|
+
const isInitialPopulation = previousCount === 0 && slotElements.length > 0
|
|
1307
|
+
|
|
1308
|
+
// Stop autoplay during rebuild to prevent errors
|
|
1309
|
+
const wasAutoplay = config.value.autoplay !== 'disabled'
|
|
1310
|
+
if (wasAutoplay) {
|
|
1311
|
+
stopAutoplay()
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
// Rebuild the slider frame
|
|
1315
|
+
buildSliderFrame()
|
|
1316
|
+
|
|
1317
|
+
// Resume autoplay if it was active and not hovering
|
|
1318
|
+
if (wasAutoplay && !isHovering.value && innerElements.value.length > 0) {
|
|
1319
|
+
// Small delay for stability
|
|
1320
|
+
setTimeout(() => {
|
|
1321
|
+
startAutoplay()
|
|
1322
|
+
}, 50)
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
} catch (error) {
|
|
1326
|
+
console.error('Error updating slider:', error)
|
|
1327
|
+
} finally {
|
|
1328
|
+
// Always reconnect observer to the slot container
|
|
1329
|
+
if (slotObserver.value && slotContainer.value) {
|
|
1330
|
+
slotObserver.value.observe(slotContainer.value, {
|
|
1331
|
+
childList: true,
|
|
1332
|
+
subtree: false,
|
|
1333
|
+
attributes: false,
|
|
1334
|
+
characterData: false
|
|
1335
|
+
})
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
// Update resetSlider to use slotContainer instead of carouselRef
|
|
1341
|
+
function resetSlider(): void {
|
|
1342
|
+
// Reset the counter to allow rebuilding again
|
|
1343
|
+
emptyRebuildAttempts.value = 0
|
|
1344
|
+
|
|
1345
|
+
// Check if we have content now
|
|
1346
|
+
if (slotContainer.value) {
|
|
1347
|
+
const slotElements = Array.from(slotContainer.value.children)
|
|
1348
|
+
.map(el => el.cloneNode(true) as Element)
|
|
1349
|
+
|
|
1350
|
+
if (slotElements.length > 0) {
|
|
1351
|
+
// We have content now, rebuild
|
|
1352
|
+
innerElements.value = slotElements
|
|
1353
|
+
nextTick(() => { buildSliderFrame() })
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
|
|
945
1358
|
// Expose public methods
|
|
946
1359
|
defineExpose({
|
|
947
1360
|
prev,
|
|
@@ -955,14 +1368,24 @@ defineExpose({
|
|
|
955
1368
|
currentSlide,
|
|
956
1369
|
pauseAutoplay,
|
|
957
1370
|
resumeAutoplay,
|
|
1371
|
+
updateSlider,
|
|
1372
|
+
resetSlider,
|
|
958
1373
|
})
|
|
959
1374
|
</script>
|
|
960
1375
|
|
|
961
1376
|
<template>
|
|
962
1377
|
<div class="carousel-wrapper">
|
|
963
|
-
|
|
1378
|
+
<!-- Hidden container to hold the original slot content -->
|
|
1379
|
+
<div ref="slotContainer" class="carousel-slot-container" style="display: none;">
|
|
964
1380
|
<slot />
|
|
965
1381
|
</div>
|
|
1382
|
+
|
|
1383
|
+
<!-- Visible carousel container that we will manually populate -->
|
|
1384
|
+
<div ref="carouselRef" class="carousel-container">
|
|
1385
|
+
<!-- We'll populate this with cloned content programmatically -->
|
|
1386
|
+
</div>
|
|
1387
|
+
|
|
1388
|
+
<!-- Dots navigation (Vue-managed) -->
|
|
966
1389
|
<div v-if="props.dots && totalDots > 1" class="carousel-dots">
|
|
967
1390
|
<button
|
|
968
1391
|
v-for="i in totalDots"
|