@bagelink/vue 1.4.12 → 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.
Files changed (30) hide show
  1. package/dist/components/AddressSearch.vue.d.ts.map +1 -1
  2. package/dist/components/Carousel2.vue.d.ts +89 -0
  3. package/dist/components/Carousel2.vue.d.ts.map +1 -0
  4. package/dist/components/Dropdown.vue.d.ts.map +1 -1
  5. package/dist/components/ImportData.vue.d.ts +2 -1
  6. package/dist/components/ImportData.vue.d.ts.map +1 -1
  7. package/dist/components/Slider.vue.d.ts +6 -0
  8. package/dist/components/Slider.vue.d.ts.map +1 -1
  9. package/dist/components/calendar/CalendarPopover.vue.d.ts.map +1 -1
  10. package/dist/components/calendar/views/CalendarPopover.vue.d.ts +2 -2
  11. package/dist/components/calendar/views/CalendarPopover.vue.d.ts.map +1 -1
  12. package/dist/components/form/inputs/SelectInput.vue.d.ts.map +1 -1
  13. package/dist/components/form/inputs/SignaturePad.vue.d.ts.map +1 -1
  14. package/dist/index.cjs +467 -243
  15. package/dist/index.mjs +468 -244
  16. package/dist/style.css +99 -99
  17. package/package.json +1 -1
  18. package/src/components/ImportData.vue +20 -247
  19. package/src/components/Slider.vue +508 -85
  20. package/src/components/form/inputs/SignaturePad.vue +8 -14
  21. package/dist/components/Icon.vue.d.ts +0 -16
  22. package/dist/components/Icon.vue.d.ts.map +0 -1
  23. package/dist/components/form/BglFieldSet.vue.d.ts +0 -25
  24. package/dist/components/form/BglFieldSet.vue.d.ts.map +0 -1
  25. package/dist/iconify-0J3vK-m1.cjs +0 -1693
  26. package/dist/iconify-Bc1B42Ak.cjs +0 -1771
  27. package/dist/iconify-BiLGk5km.js +0 -1693
  28. package/dist/iconify-DVnNdzog.js +0 -1771
  29. package/dist/types/timeago.d.ts +0 -23
  30. 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(() => init())
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(carouselRef.value.children)
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
- await nextTick()
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
- // Ensure we have elements to build the slider
276
- if (!innerElements.value.length) {
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
- const widthItem = selectorWidth.value / perPage.value
282
- const itemsToBuild = config.value.loop
283
- ? innerElements.value.length + (2 * perPage.value)
284
- : innerElements.value.length
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
- // Create frame
287
- if (!sliderFrame.value) {
288
- sliderFrame.value = document.createElement('div')
289
- }
352
+ // Reset counter since we have elements
353
+ emptyRebuildAttempts.value = 0
290
354
 
291
- sliderFrame.value.style.width = `${widthItem * itemsToBuild}px`
292
- enableTransition()
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
- if (config.value.draggable && carouselRef.value) {
295
- carouselRef.value.style.cursor = '-webkit-grab'
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
- // Create document fragment for slides
299
- const docFragment = document.createDocumentFragment()
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
- // Add clones before original elements if loop is true
302
- if (config.value.loop && innerElements.value.length > 0) {
303
- for (let i = innerElements.value.length - perPage.value; i < innerElements.value.length; i++) {
304
- // Ensure the index is valid
305
- if (i >= 0 && i < innerElements.value.length) {
306
- const element = buildSliderFrameItem(innerElements.value[i].cloneNode(true) as Element)
307
- docFragment.appendChild(element)
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
- // Add original elements
313
- for (let i = 0; i < innerElements.value.length; i++) {
314
- const element = buildSliderFrameItem(innerElements.value[i])
315
- docFragment.appendChild(element)
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
- // Add clones after original elements if loop is true
319
- if (config.value.loop && innerElements.value.length > 0) {
320
- for (let i = 0; i < perPage.value; i++) {
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
- // Clear selector and append the frame
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
- if (carouselRef.value) {
335
- carouselRef.value.innerHTML = ''
336
- carouselRef.value.appendChild(sliderFrame.value)
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
- // Go to current slide
341
- slideToCurrent()
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 * 2))
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
- const currentSlideValue = config.value.loop
361
- ? currentSlide.value + perPage.value
362
- : currentSlide.value
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
- const offset = (config.value.rtl ? 1 : -1) * currentSlideValue * (selectorWidth.value / perPage.value)
529
+ const offset = (config.value.rtl ? 1 : -1) * currentSlideValue * (selectorWidth.value / perPage.value)
365
530
 
366
- if (enableTransitionFlag && sliderFrame.value) {
367
- // Use requestAnimationFrame for smooth transitions
368
- requestAnimationFrame(() => {
531
+ if (enableTransitionFlag && sliderFrame.value) {
532
+ // Use requestAnimationFrame for smooth transitions
369
533
  requestAnimationFrame(() => {
370
- enableTransition()
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
- } else if (sliderFrame.value) {
377
- sliderFrame.value.style[transformProperty.value as any] = `translate3d(${offset}px, 0, 0)`
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 (!isHovering.value) {
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
- // Reset the linear offset
865
- linearOffset.value = 0
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 * linearOffset.value}px, 0, 0)`
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
- linearAnimationFrame.value = requestAnimationFrame(animate)
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
- linearAnimationFrame.value = requestAnimationFrame(animate)
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
- <div ref="carouselRef" class="carousel-container">
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"