Files
svelte-carousel/src/components/Carousel/createCarousel.js
2021-10-10 12:28:44 +03:00

276 lines
9.5 KiB
JavaScript

import simplyReactive from '../../../../simply-reactive/main'
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 [data, methods, service] = simplyReactive(
{
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,
})
},
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,
}
)
return [{ data, progressManager }, methods, service]
}
export default createCarousel