@brandocms/jupiter 5.0.0-beta.7 → 5.0.0-beta.8
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/package.json +2 -1
- package/src/modules/Looper/index.js +124 -151
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brandocms/jupiter",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.8",
|
|
4
4
|
"description": "Frontend helpers.",
|
|
5
5
|
"author": "Univers/Twined",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"playwright:dataloader": "playwright test e2e/dataloader.spec.js --project=chromium --reporter line",
|
|
37
37
|
"playwright:dataloader-url-sync": "playwright test e2e/dataloader-url-sync.spec.js --project=chromium --reporter line",
|
|
38
38
|
"playwright:parallax": "playwright test e2e/parallax.spec.js --project=chromium --reporter line",
|
|
39
|
+
"playwright:looper": "playwright test e2e/looper.spec.js --project=chromium --reporter line",
|
|
39
40
|
"vite": "vite",
|
|
40
41
|
"vite:build": "vite build",
|
|
41
42
|
"vite:preview": "vite preview"
|
|
@@ -77,9 +77,7 @@ function horizontalLoop(app, items, config) {
|
|
|
77
77
|
let originalItemsWidth = 0 // Width of ONLY original items (for wrapping)
|
|
78
78
|
let pixelsPerSecond = (config.speed || 1) * 100
|
|
79
79
|
let animation = null
|
|
80
|
-
let position = motionValue(0) // Source of truth for position
|
|
81
|
-
let boundedPos = motionValue(0) // Bounded position (0 to originalItemsWidth)
|
|
82
|
-
let lastBoundedValue = 0 // Track last value to detect wraps
|
|
80
|
+
let position = motionValue(0) // Source of truth for position (raw, unbounded)
|
|
83
81
|
let originalItemCount = 0 // Track count of ORIGINAL items (before clones)
|
|
84
82
|
let maxScrollPosition = 0 // For non-looping: max scroll where last item is at right edge
|
|
85
83
|
|
|
@@ -174,9 +172,9 @@ function horizontalLoop(app, items, config) {
|
|
|
174
172
|
let count = 0
|
|
175
173
|
let previousTotalWidth = totalWidth
|
|
176
174
|
|
|
177
|
-
// Always create at least
|
|
175
|
+
// Always create at least TWO sets of clones - needed for starting at first clone position
|
|
178
176
|
// Then continue until we have enough width for seamless looping
|
|
179
|
-
while ((count
|
|
177
|
+
while ((count < 2 || totalWidth < minRequiredWidth) && count < maxReplications) {
|
|
180
178
|
// Clone ONLY original items
|
|
181
179
|
for (let i = 0; i < originalItemCount; i++) {
|
|
182
180
|
const clone = items[i].cloneNode(true)
|
|
@@ -358,84 +356,48 @@ function horizontalLoop(app, items, config) {
|
|
|
358
356
|
|
|
359
357
|
/**
|
|
360
358
|
* Check item positions and wrap when needed
|
|
361
|
-
* Container
|
|
362
|
-
*
|
|
363
|
-
* @param {number} pos - Current position value (can grow infinitely)
|
|
359
|
+
* Container uses RAW position - items wrap individually when far off-screen
|
|
360
|
+
* @param {number} rawPos - Current raw position value (unbounded)
|
|
364
361
|
*/
|
|
365
|
-
function updateItemPositions(
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
if (!shouldLoop) {
|
|
369
|
-
// Non-looping: we'll handle this with direct animation
|
|
370
|
-
return
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
// Calculate bounded position for checking item wrap points
|
|
374
|
-
// Use same wrapping formula as frame.render to handle negative positions (reversed mode)
|
|
375
|
-
const boundedPos = ((pos % originalItemsWidth) + originalItemsWidth) % originalItemsWidth
|
|
362
|
+
function updateItemPositions(rawPos) {
|
|
363
|
+
if (!shouldLoop) return
|
|
376
364
|
|
|
377
365
|
// Initialize wrap offsets cache if needed
|
|
378
366
|
if (itemWrapOffsets.length === 0) {
|
|
379
367
|
itemWrapOffsets = new Array(items.length).fill(0)
|
|
380
368
|
}
|
|
381
369
|
|
|
382
|
-
//
|
|
383
|
-
// This
|
|
384
|
-
|
|
385
|
-
// Skip clones - they stay in natural flow! (use cached value for performance)
|
|
386
|
-
if (isCloneCache[i]) {
|
|
387
|
-
return
|
|
388
|
-
}
|
|
370
|
+
// Items wrap by the full cycle distance (totalWidth) to maintain relative positions
|
|
371
|
+
// This keeps all items within viewing distance as position grows/shrinks
|
|
372
|
+
const cycleDistance = totalWidth
|
|
389
373
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
//
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
//
|
|
398
|
-
//
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
if (nearForwardWrap && itemWrapOffsets[i] === wrapOffset) {
|
|
410
|
-
// Container about to wrap (forward), reset items at END back to START
|
|
411
|
-
newOffset = 0
|
|
412
|
-
} else if (nearReverseWrap && itemWrapOffsets[i] === -wrapOffset) {
|
|
413
|
-
// Container about to wrap (reverse), reset items at START back to END
|
|
414
|
-
newOffset = 0
|
|
415
|
-
} else if (nearForwardWrap || nearReverseWrap) {
|
|
416
|
-
// In reset zone but item doesn't need reset → keep current offset
|
|
417
|
-
newOffset = itemWrapOffsets[i]
|
|
418
|
-
} else if (itemLeft < -(widths[i] + containerWidth * 0.5)) {
|
|
419
|
-
// Item exited LEFT edge - only wrap during forward scroll
|
|
420
|
-
// During reverse scroll (scrollDirection < 0), items off-screen left
|
|
421
|
-
// will naturally scroll back into view - don't wrap them
|
|
422
|
-
const isForwardScroll = scrollDirection >= 0
|
|
423
|
-
newOffset = (isForwardScroll && boundedPos < originalItemsWidth / 2) ? wrapOffset : 0
|
|
424
|
-
} else if (itemLeft > containerWidth + containerWidth * 0.5) {
|
|
425
|
-
// Item exited RIGHT edge
|
|
426
|
-
// This shouldn't happen much, but handle it
|
|
427
|
-
newOffset = 0
|
|
428
|
-
} else {
|
|
429
|
-
// Keep current offset
|
|
430
|
-
newOffset = itemWrapOffsets[i]
|
|
374
|
+
// Wrap threshold: when an item is more than half a cycle from view, wrap it
|
|
375
|
+
const wrapThreshold = cycleDistance / 2
|
|
376
|
+
|
|
377
|
+
for (let i = 0; i < items.length; i++) {
|
|
378
|
+
// Calculate this item's effective position (DOM position + wrap offset)
|
|
379
|
+
const effectivePos = offsetLefts[i] + itemWrapOffsets[i]
|
|
380
|
+
|
|
381
|
+
// Distance from current view position
|
|
382
|
+
// Positive = item is ahead (to the right), Negative = item is behind (to the left)
|
|
383
|
+
const distanceFromView = effectivePos - rawPos
|
|
384
|
+
|
|
385
|
+
let newOffset = itemWrapOffsets[i]
|
|
386
|
+
|
|
387
|
+
if (distanceFromView < -wrapThreshold) {
|
|
388
|
+
// Item is too far left (behind), wrap it forward (to the right)
|
|
389
|
+
newOffset = itemWrapOffsets[i] + cycleDistance
|
|
390
|
+
} else if (distanceFromView > wrapThreshold + containerWidth) {
|
|
391
|
+
// Item is too far right (ahead), wrap it backward (to the left)
|
|
392
|
+
newOffset = itemWrapOffsets[i] - cycleDistance
|
|
431
393
|
}
|
|
432
394
|
|
|
433
|
-
//
|
|
395
|
+
// Only update DOM if offset changed
|
|
434
396
|
if (newOffset !== itemWrapOffsets[i]) {
|
|
435
|
-
|
|
397
|
+
items[i].style.transform = newOffset !== 0 ? `translateX(${newOffset}px)` : 'none'
|
|
436
398
|
itemWrapOffsets[i] = newOffset
|
|
437
399
|
}
|
|
438
|
-
}
|
|
400
|
+
}
|
|
439
401
|
}
|
|
440
402
|
|
|
441
403
|
/**
|
|
@@ -443,32 +405,58 @@ function horizontalLoop(app, items, config) {
|
|
|
443
405
|
* @param {boolean} deep - Whether to rebuild animation (on resize)
|
|
444
406
|
*/
|
|
445
407
|
function refresh(deep = false) {
|
|
446
|
-
// Save progress to preserve position
|
|
447
|
-
const progress = animation ? animation.time / animation.duration : 0
|
|
448
|
-
const currentPos = position.get()
|
|
449
|
-
|
|
450
408
|
// Pause animation if running
|
|
451
409
|
const wasPlaying = animation && animation.speed !== 0
|
|
452
410
|
if (animation) {
|
|
453
411
|
animation.pause()
|
|
454
412
|
}
|
|
455
413
|
|
|
414
|
+
if (deep && shouldLoop) {
|
|
415
|
+
// DEEP REFRESH: Reset everything for new dimensions
|
|
416
|
+
// Clear all item wrap offsets and transforms
|
|
417
|
+
for (let i = 0; i < items.length; i++) {
|
|
418
|
+
items[i].style.transform = 'none'
|
|
419
|
+
if (itemWrapOffsets[i] !== undefined) {
|
|
420
|
+
itemWrapOffsets[i] = 0
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
itemWrapOffsets = []
|
|
424
|
+
}
|
|
425
|
+
|
|
456
426
|
// Remeasure everything
|
|
457
427
|
populateWidths()
|
|
458
428
|
|
|
459
429
|
if (deep) {
|
|
460
430
|
// Check if we need to replicate more items
|
|
461
|
-
const
|
|
431
|
+
const currentContainerWidth = container.offsetWidth
|
|
462
432
|
const currentTotalWidth = getTotalWidthOfItems()
|
|
463
433
|
|
|
464
434
|
// Use same 2.5x buffer as replication logic
|
|
465
|
-
if (shouldLoop && currentTotalWidth <
|
|
435
|
+
if (shouldLoop && currentTotalWidth < currentContainerWidth * 2.5) {
|
|
466
436
|
replicateItemsIfNeeded()
|
|
437
|
+
// Re-cache clone status for any new items
|
|
438
|
+
isCloneCache = items.map((item, i) => i >= originalItemCount)
|
|
467
439
|
populateWidths()
|
|
468
440
|
}
|
|
469
441
|
|
|
470
442
|
populateSnapTimes()
|
|
471
443
|
|
|
444
|
+
// Reset position to start at first clone (like initial state)
|
|
445
|
+
if (shouldLoop && !config.centerSlide) {
|
|
446
|
+
position.set(originalItemsWidth)
|
|
447
|
+
lastPositionForDirection = originalItemsWidth
|
|
448
|
+
items[0].parentElement.style.transform = `translateX(${-originalItemsWidth}px)`
|
|
449
|
+
} else if (shouldLoop && config.centerSlide) {
|
|
450
|
+
// For center mode, go to middle slide
|
|
451
|
+
const middleIndex = Math.floor(originalItemCount / 2)
|
|
452
|
+
const targetTime = times[middleIndex]
|
|
453
|
+
const initialPos = targetTime * pixelsPerSecond
|
|
454
|
+
position.set(initialPos)
|
|
455
|
+
lastPositionForDirection = initialPos
|
|
456
|
+
items[0].parentElement.style.transform = `translateX(${-initialPos}px)`
|
|
457
|
+
curIndex = middleIndex
|
|
458
|
+
}
|
|
459
|
+
|
|
472
460
|
// Recreate animation with new measurements
|
|
473
461
|
if (shouldLoop && config.crawl) {
|
|
474
462
|
// Stop old animation
|
|
@@ -476,7 +464,7 @@ function horizontalLoop(app, items, config) {
|
|
|
476
464
|
animation.stop()
|
|
477
465
|
}
|
|
478
466
|
|
|
479
|
-
//
|
|
467
|
+
// Recreate loop animation with new measurements
|
|
480
468
|
animation = startLoopAnimation()
|
|
481
469
|
|
|
482
470
|
// Restore playback state
|
|
@@ -499,19 +487,20 @@ function horizontalLoop(app, items, config) {
|
|
|
499
487
|
})
|
|
500
488
|
|
|
501
489
|
if (wasPlaying) {
|
|
502
|
-
animation.time = progress * animation.duration
|
|
503
490
|
animation.play()
|
|
504
491
|
} else {
|
|
505
492
|
animation.pause()
|
|
506
493
|
}
|
|
507
494
|
}
|
|
495
|
+
|
|
496
|
+
// Update index display
|
|
497
|
+
updateIndexDisplay()
|
|
508
498
|
} else {
|
|
509
499
|
// Light refresh - just update measurements
|
|
510
500
|
populateSnapTimes()
|
|
501
|
+
// Update positions based on current scroll
|
|
502
|
+
updateItemPositions(position.get())
|
|
511
503
|
}
|
|
512
|
-
|
|
513
|
-
// Update positions based on current scroll
|
|
514
|
-
updateItemPositions(currentPos)
|
|
515
504
|
}
|
|
516
505
|
|
|
517
506
|
/**
|
|
@@ -541,7 +530,7 @@ function horizontalLoop(app, items, config) {
|
|
|
541
530
|
: currentPos + originalItemsWidth
|
|
542
531
|
|
|
543
532
|
// Animate the position motionValue
|
|
544
|
-
// frame.render loop will apply
|
|
533
|
+
// frame.render loop will apply raw position to container and wrap items
|
|
545
534
|
animation = animate(position, target, {
|
|
546
535
|
duration,
|
|
547
536
|
repeat: Infinity,
|
|
@@ -551,6 +540,21 @@ function horizontalLoop(app, items, config) {
|
|
|
551
540
|
return animation
|
|
552
541
|
}
|
|
553
542
|
|
|
543
|
+
/**
|
|
544
|
+
* Stop the frame.render loop and cleanup listeners
|
|
545
|
+
* Called from init() and destroy()
|
|
546
|
+
*/
|
|
547
|
+
function stopRenderLoop() {
|
|
548
|
+
if (renderUnsubscribe) {
|
|
549
|
+
// Unsubscribe from motionValue listener
|
|
550
|
+
if (renderUnsubscribe.positionUnsubscribe) {
|
|
551
|
+
renderUnsubscribe.positionUnsubscribe()
|
|
552
|
+
}
|
|
553
|
+
cancelFrame(renderUnsubscribe)
|
|
554
|
+
renderUnsubscribe = null
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
554
558
|
/**
|
|
555
559
|
* Initialize the loop animation
|
|
556
560
|
*/
|
|
@@ -571,82 +575,45 @@ function horizontalLoop(app, items, config) {
|
|
|
571
575
|
// Set initial container position
|
|
572
576
|
const containerElement = items[0].parentElement
|
|
573
577
|
containerElement.style.willChange = 'transform'
|
|
574
|
-
containerElement.style.transform = 'translateX(0px)'
|
|
575
578
|
|
|
576
|
-
//
|
|
577
|
-
//
|
|
579
|
+
// For looping (non-center mode): start viewing first CLONE, not originals
|
|
580
|
+
// This positions originals OFF-SCREEN LEFT so backward scroll reveals them smoothly
|
|
581
|
+
if (shouldLoop && !config.centerSlide) {
|
|
582
|
+
position.set(originalItemsWidth)
|
|
583
|
+
lastPositionForDirection = originalItemsWidth
|
|
584
|
+
containerElement.style.transform = `translateX(${-originalItemsWidth}px)`
|
|
585
|
+
} else {
|
|
586
|
+
containerElement.style.transform = 'translateX(0px)'
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// Set up RAF loop to update container position and wrap items
|
|
590
|
+
// Uses RAW position (no modulo) for container transform
|
|
578
591
|
// This is Motion's optimized render loop - prevents layout thrashing
|
|
579
592
|
function startRenderLoop() {
|
|
580
593
|
if (renderUnsubscribe) return // Already running
|
|
581
594
|
|
|
582
595
|
const containerElement = items[0].parentElement
|
|
583
596
|
|
|
584
|
-
//
|
|
585
|
-
// This calculates the bounded position (0 to originalItemsWidth)
|
|
597
|
+
// Track scroll direction for wrap logic
|
|
586
598
|
const positionUnsubscribe = position.on('change', latest => {
|
|
587
|
-
// Track scroll direction for wrap logic
|
|
588
599
|
const delta = latest - lastPositionForDirection
|
|
589
600
|
if (Math.abs(delta) > 1) {
|
|
590
601
|
scrollDirection = delta > 0 ? 1 : -1
|
|
591
602
|
}
|
|
592
603
|
lastPositionForDirection = latest
|
|
593
|
-
|
|
594
|
-
const bounded = ((latest % originalItemsWidth) + originalItemsWidth) % originalItemsWidth
|
|
595
|
-
boundedPos.set(bounded)
|
|
596
|
-
})
|
|
597
|
-
|
|
598
|
-
// Detect when boundedPos wraps (makes large jump) and reset all items
|
|
599
|
-
// This prevents stuck items during fast drags in either direction
|
|
600
|
-
const boundedPosUnsubscribe = boundedPos.on('change', latest => {
|
|
601
|
-
const delta = Math.abs(latest - lastBoundedValue)
|
|
602
|
-
|
|
603
|
-
// If boundedPos jumped by more than 40% of the width, it wrapped
|
|
604
|
-
// Using 40% instead of 50% to catch edge cases
|
|
605
|
-
const didWrap = delta > originalItemsWidth * 0.4
|
|
606
|
-
|
|
607
|
-
if (didWrap) {
|
|
608
|
-
// NOTE: We intentionally do NOT reset item transforms here anymore.
|
|
609
|
-
// Resetting here caused flash because it happens between render frames.
|
|
610
|
-
// The updateItemPositions() in frame.render handles wrapping correctly
|
|
611
|
-
// when called with the new boundedPos.
|
|
612
|
-
|
|
613
|
-
// Sync position to bounded value (only when not dragging/animating)
|
|
614
|
-
if (!snapAnimation && !navAnimation && !isDragging) {
|
|
615
|
-
position.set(latest)
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
lastBoundedValue = latest
|
|
620
604
|
})
|
|
621
605
|
|
|
622
606
|
renderUnsubscribe = frame.render(() => {
|
|
623
|
-
//
|
|
624
|
-
const
|
|
625
|
-
|
|
626
|
-
// Apply bounded transform to container
|
|
627
|
-
containerElement.style.transform = `translateX(${-currentBoundedPos}px)`
|
|
607
|
+
// Use RAW position (no bounded) for container
|
|
608
|
+
const currentPos = position.get()
|
|
609
|
+
containerElement.style.transform = `translateX(${-currentPos}px)`
|
|
628
610
|
|
|
629
|
-
// Wrap items based on
|
|
630
|
-
updateItemPositions(
|
|
611
|
+
// Wrap items based on raw position
|
|
612
|
+
updateItemPositions(currentPos)
|
|
631
613
|
}, true) // true = keep alive
|
|
632
614
|
|
|
633
|
-
// Store unsubscribe
|
|
615
|
+
// Store unsubscribe function for cleanup
|
|
634
616
|
renderUnsubscribe.positionUnsubscribe = positionUnsubscribe
|
|
635
|
-
renderUnsubscribe.boundedPosUnsubscribe = boundedPosUnsubscribe
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
function stopRenderLoop() {
|
|
639
|
-
if (renderUnsubscribe) {
|
|
640
|
-
// Unsubscribe from motionValue listeners
|
|
641
|
-
if (renderUnsubscribe.positionUnsubscribe) {
|
|
642
|
-
renderUnsubscribe.positionUnsubscribe()
|
|
643
|
-
}
|
|
644
|
-
if (renderUnsubscribe.boundedPosUnsubscribe) {
|
|
645
|
-
renderUnsubscribe.boundedPosUnsubscribe()
|
|
646
|
-
}
|
|
647
|
-
cancelFrame(renderUnsubscribe)
|
|
648
|
-
renderUnsubscribe = null
|
|
649
|
-
}
|
|
650
617
|
}
|
|
651
618
|
|
|
652
619
|
// Start the frame.render loop
|
|
@@ -734,7 +701,7 @@ function horizontalLoop(app, items, config) {
|
|
|
734
701
|
// Update display in real-time as position changes
|
|
735
702
|
let lastDisplayedIndex = -1
|
|
736
703
|
const updateIndexOnChange = () => {
|
|
737
|
-
// Find closest slide to current
|
|
704
|
+
// Find closest slide to current position
|
|
738
705
|
const closest = closestIndex(false)
|
|
739
706
|
|
|
740
707
|
// Only update DOM if index changed (avoid thrashing)
|
|
@@ -747,12 +714,8 @@ function horizontalLoop(app, items, config) {
|
|
|
747
714
|
}
|
|
748
715
|
}
|
|
749
716
|
|
|
750
|
-
//
|
|
751
|
-
|
|
752
|
-
boundedPos.on('change', updateIndexOnChange)
|
|
753
|
-
} else {
|
|
754
|
-
position.on('change', updateIndexOnChange)
|
|
755
|
-
}
|
|
717
|
+
// Update index display whenever position changes (closestIndex normalizes internally)
|
|
718
|
+
position.on('change', updateIndexOnChange)
|
|
756
719
|
}
|
|
757
720
|
}
|
|
758
721
|
|
|
@@ -922,9 +885,9 @@ function horizontalLoop(app, items, config) {
|
|
|
922
885
|
const newPosition = startPosition + deltaX
|
|
923
886
|
|
|
924
887
|
// Update position motionValue
|
|
925
|
-
// frame.render loop
|
|
888
|
+
// frame.render loop applies raw position to container
|
|
926
889
|
if (shouldLoop) {
|
|
927
|
-
// For looping,
|
|
890
|
+
// For looping, position grows freely - items wrap as groups
|
|
928
891
|
position.set(newPosition)
|
|
929
892
|
} else {
|
|
930
893
|
// For non-looping, clamp position to maxScrollPosition (last item at right edge)
|
|
@@ -1513,13 +1476,18 @@ function horizontalLoop(app, items, config) {
|
|
|
1513
1476
|
closestIndex(true)
|
|
1514
1477
|
let nextIndex = curIndex + 1
|
|
1515
1478
|
|
|
1516
|
-
// Non-looping:
|
|
1479
|
+
// Non-looping: clamp at boundaries
|
|
1517
1480
|
if (!shouldLoop) {
|
|
1518
1481
|
const currentPos = position.get()
|
|
1519
1482
|
const atEnd = currentPos >= maxScrollPosition - 1
|
|
1520
1483
|
|
|
1521
1484
|
if (nextIndex >= originalItemCount || atEnd) {
|
|
1522
|
-
|
|
1485
|
+
// Autoplay resets to start, user navigation clamps
|
|
1486
|
+
if (options.autoplay) {
|
|
1487
|
+
nextIndex = 0
|
|
1488
|
+
} else {
|
|
1489
|
+
return // Clamp - do nothing at boundary
|
|
1490
|
+
}
|
|
1523
1491
|
}
|
|
1524
1492
|
}
|
|
1525
1493
|
|
|
@@ -1535,9 +1503,14 @@ function horizontalLoop(app, items, config) {
|
|
|
1535
1503
|
closestIndex(true)
|
|
1536
1504
|
let prevIndex = curIndex - 1
|
|
1537
1505
|
|
|
1538
|
-
// Non-looping:
|
|
1506
|
+
// Non-looping: clamp at boundaries
|
|
1539
1507
|
if (!shouldLoop && prevIndex < 0) {
|
|
1540
|
-
|
|
1508
|
+
// Autoplay resets to end, user navigation clamps
|
|
1509
|
+
if (options.autoplay) {
|
|
1510
|
+
prevIndex = originalItemCount - 1
|
|
1511
|
+
} else {
|
|
1512
|
+
return // Clamp - do nothing at boundary
|
|
1513
|
+
}
|
|
1541
1514
|
}
|
|
1542
1515
|
|
|
1543
1516
|
return toIndex(prevIndex, vars)
|