278 lines
9.6 KiB
JavaScript
278 lines
9.6 KiB
JavaScript
import easyReactive from 'easy-reactive'
|
|
|
|
import { NEXT, PREV } from '../../direction'
|
|
import {
|
|
getCurrentPageIndexByCurrentParticleIndex,
|
|
getPartialPageSize,
|
|
getPagesCountByParticlesCount,
|
|
getParticleIndexByPageIndex,
|
|
} from '../../utils/page'
|
|
import { getClonesCount } from '../../utils/clones'
|
|
import { getAdjacentIndexes } from '../../utils/lazy'
|
|
import { getValueInRange } from '../../utils/math'
|
|
import { get, switcher } from '../../utils/object'
|
|
import { ProgressManager } from '../../utils/ProgressManager'
|
|
|
|
function createCarousel(onChange) {
|
|
const progressManager = new ProgressManager({
|
|
onProgressValueChange: (value) => {
|
|
onChange('progressValue', 1 - value)
|
|
},
|
|
})
|
|
|
|
const reactive = easyReactive(
|
|
{
|
|
data: {
|
|
particlesCountWithoutClones: 0,
|
|
particlesToShow: 1, // normalized
|
|
particlesToShowInit: 1, // initial value
|
|
particlesToScroll: 1, // normalized
|
|
particlesToScrollInit: 1, // initial value
|
|
particlesCount: 1,
|
|
currentParticleIndex: 1,
|
|
infinite: false,
|
|
autoplayDuration: 1000,
|
|
clonesCountHead: 0,
|
|
clonesCountTail: 0,
|
|
clonesCountTotal: 0,
|
|
partialPageSize: 1,
|
|
currentPageIndex: 1,
|
|
pagesCount: 1,
|
|
pauseOnFocus: false,
|
|
focused: false,
|
|
autoplay: false,
|
|
autoplayDirection: 'next',
|
|
disabled: false, // disable page change while animation is in progress
|
|
durationMsInit: 1000,
|
|
durationMs: 1000,
|
|
offset: 0,
|
|
particleWidth: 0,
|
|
loaded: [],
|
|
},
|
|
watch: {
|
|
setLoaded({ data }) {
|
|
data.loaded = getAdjacentIndexes({
|
|
infinite: data.infinite,
|
|
pageIndex: data.currentPageIndex,
|
|
pagesCount: data.pagesCount,
|
|
particlesCount: data.particlesCountWithoutClones,
|
|
particlesToShow: data.particlesToShow,
|
|
particlesToScroll: data.particlesToScroll,
|
|
}).particleIndexes
|
|
},
|
|
setCurrentPageIndex({ data }) {
|
|
data.currentPageIndex = getCurrentPageIndexByCurrentParticleIndex({
|
|
currentParticleIndex: data.currentParticleIndex,
|
|
particlesCount: data.particlesCount,
|
|
clonesCountHead: data.clonesCountHead,
|
|
clonesCountTotal: data.clonesCountTotal,
|
|
infinite: data.infinite,
|
|
particlesToScroll: data.particlesToScroll,
|
|
})
|
|
},
|
|
setPartialPageSize({ data }) {
|
|
data.partialPageSize = getPartialPageSize({
|
|
particlesToScroll: data.particlesToScroll,
|
|
particlesToShow: data.particlesToShow,
|
|
particlesCountWithoutClones: data.particlesCountWithoutClones,
|
|
})
|
|
},
|
|
setClonesCount({ data }) {
|
|
const { head, tail } = getClonesCount({
|
|
infinite: data.infinite,
|
|
particlesToShow: data.particlesToShow,
|
|
partialPageSize: data.partialPageSize,
|
|
})
|
|
data.clonesCountHead = head
|
|
data.clonesCountTail = tail
|
|
data.clonesCountTotal = head + tail
|
|
},
|
|
setProgressManagerAutoplayDuration({ data }) {
|
|
progressManager.setAutoplayDuration(data.autoplayDuration)
|
|
},
|
|
toggleProgressManager({ data: { pauseOnFocus, focused } }) {
|
|
// as focused is in if block, it will not be put to deps, read them in data: {}
|
|
if (pauseOnFocus) {
|
|
if (focused) {
|
|
progressManager.pause()
|
|
} else {
|
|
progressManager.resume()
|
|
}
|
|
}
|
|
},
|
|
initDuration({ data }) {
|
|
data.durationMs = data.durationMsInit
|
|
},
|
|
applyAutoplay({ data, methods: { _applyAutoplayIfNeeded } }) {
|
|
// prevent _applyAutoplayIfNeeded to be called with watcher
|
|
// to prevent its data added to deps
|
|
data.autoplay && _applyAutoplayIfNeeded(data.autoplay)
|
|
},
|
|
setPagesCount({ data }) {
|
|
data.pagesCount = getPagesCountByParticlesCount({
|
|
infinite: data.infinite,
|
|
particlesCountWithoutClones: data.particlesCountWithoutClones,
|
|
particlesToScroll: data.particlesToScroll,
|
|
particlesToShow: data.particlesToShow,
|
|
})
|
|
},
|
|
setParticlesToShow({ data }) {
|
|
data.particlesToShow = getValueInRange(
|
|
1,
|
|
data.particlesToShowInit,
|
|
data.particlesCountWithoutClones
|
|
)
|
|
},
|
|
setParticlesToScroll({ data }) {
|
|
data.particlesToScroll = getValueInRange(
|
|
1,
|
|
data.particlesToScrollInit,
|
|
data.particlesCountWithoutClones
|
|
)
|
|
},
|
|
},
|
|
methods: {
|
|
_prev({ data }) {
|
|
data.currentParticleIndex = getParticleIndexByPageIndex({
|
|
infinite: data.infinite,
|
|
pageIndex: data.currentPageIndex - 1,
|
|
clonesCountHead: data.clonesCountHead,
|
|
clonesCountTail: data.clonesCountTail,
|
|
particlesToScroll: data.particlesToScroll,
|
|
particlesCount: data.particlesCount,
|
|
particlesToShow: data.particlesToShow,
|
|
})
|
|
},
|
|
_next({ data }) {
|
|
data.currentParticleIndex = getParticleIndexByPageIndex({
|
|
infinite: data.infinite,
|
|
pageIndex: data.currentPageIndex + 1,
|
|
clonesCountHead: data.clonesCountHead,
|
|
clonesCountTail: data.clonesCountTail,
|
|
particlesToScroll: data.particlesToScroll,
|
|
particlesCount: data.particlesCount,
|
|
particlesToShow: data.particlesToShow,
|
|
})
|
|
},
|
|
_moveToParticle({ data }, particleIndex) {
|
|
data.currentParticleIndex = getValueInRange(
|
|
0,
|
|
particleIndex,
|
|
data.particlesCount - 1
|
|
)
|
|
},
|
|
toggleFocused({ data }) {
|
|
data.focused = !data.focused
|
|
},
|
|
async _applyAutoplayIfNeeded({ data, methods }) {
|
|
// prevent progress change if not infinite for first and last page
|
|
if (
|
|
!data.infinite &&
|
|
((data.autoplayDirection === NEXT &&
|
|
data.currentParticleIndex === data.particlesCount - 1) ||
|
|
(data.autoplayDirection === PREV &&
|
|
data.currentParticleIndex === 0))
|
|
) {
|
|
progressManager.reset()
|
|
return
|
|
}
|
|
|
|
if (data.autoplay) {
|
|
const onFinish = () =>
|
|
switcher({
|
|
[NEXT]: async () => methods.showNextPage(),
|
|
[PREV]: async () => methods.showPrevPage(),
|
|
})(data.autoplayDirection)
|
|
|
|
await progressManager.start(onFinish)
|
|
}
|
|
},
|
|
// makes delayed jump to 1st or last element
|
|
async _jumpIfNeeded({ data, methods }) {
|
|
let jumped = false
|
|
if (data.infinite) {
|
|
if (data.currentParticleIndex === 0) {
|
|
await methods.showParticle(
|
|
data.particlesCount - data.clonesCountTotal,
|
|
{
|
|
animated: false,
|
|
}
|
|
)
|
|
jumped = true
|
|
} else if (
|
|
data.currentParticleIndex ===
|
|
data.particlesCount - data.clonesCountTail
|
|
) {
|
|
await methods.showParticle(data.clonesCountHead, {
|
|
animated: false,
|
|
})
|
|
jumped = true
|
|
}
|
|
}
|
|
return jumped
|
|
},
|
|
async changePage({ data, methods }, updateStoreFn, options) {
|
|
progressManager.reset()
|
|
if (data.disabled) return
|
|
data.disabled = true
|
|
|
|
updateStoreFn()
|
|
await methods.offsetPage({ animated: get(options, 'animated', true) })
|
|
data.disabled = false
|
|
|
|
const jumped = await methods._jumpIfNeeded()
|
|
!jumped && methods._applyAutoplayIfNeeded() // no need to wait it finishes
|
|
},
|
|
async showNextPage({ data, methods }, options) {
|
|
if (data.disabled) return
|
|
await methods.changePage(methods._next, options)
|
|
},
|
|
async showPrevPage({ data, methods }, options) {
|
|
if (data.disabled) return
|
|
await methods.changePage(methods._prev, options)
|
|
},
|
|
async showParticle({ methods }, particleIndex, options) {
|
|
await methods.changePage(
|
|
() => methods._moveToParticle(particleIndex),
|
|
options
|
|
)
|
|
},
|
|
_getParticleIndexByPageIndex({ data }, pageIndex) {
|
|
return getParticleIndexByPageIndex({
|
|
infinite: data.infinite,
|
|
pageIndex,
|
|
clonesCountHead: data.clonesCountHead,
|
|
clonesCountTail: data.clonesCountTail,
|
|
particlesToScroll: data.particlesToScroll,
|
|
particlesCount: data.particlesCount,
|
|
particlesToShow: data.particlesToShow,
|
|
})
|
|
},
|
|
async showPage({ methods }, pageIndex, options) {
|
|
const particleIndex = methods._getParticleIndexByPageIndex(pageIndex)
|
|
await methods.showParticle(particleIndex, options)
|
|
},
|
|
offsetPage({ data }, options) {
|
|
const animated = get(options, 'animated', true)
|
|
return new Promise((resolve) => {
|
|
// durationMs is an offset animation time
|
|
data.durationMs = animated ? data.durationMsInit : 0
|
|
data.offset = -data.currentParticleIndex * data.particleWidth
|
|
setTimeout(() => {
|
|
resolve()
|
|
}, data.durationMs)
|
|
})
|
|
},
|
|
},
|
|
},
|
|
{
|
|
onChange,
|
|
}
|
|
)
|
|
const [data, methods] = reactive
|
|
|
|
return [{ data, progressManager }, methods, reactive._internal]
|
|
}
|
|
|
|
export default createCarousel
|