Extend reactive store

This commit is contained in:
Vadim
2021-10-06 00:25:25 +03:00
parent bce6199e13
commit a0b90283c0
5 changed files with 181 additions and 152 deletions

View File

@@ -33,25 +33,28 @@
import { carousel2 } from './carousel2' import { carousel2 } from './carousel2'
let currentPageIndex let currentPageIndex
let focused = false
let progressValue
let offset = 0
let _duration = 0
const [data, methods] = carousel2((key, value) => { const [{ data, progressManager }, methods] = carousel2((key, value) => {
// console.log('onChange', key, value) const description = {
if (key === 'currentPageIndex') { 'currentPageIndex': () => currentPageIndex = value,
currentPageIndex = value 'progressValue': () => progressValue = value,
'focused': () => focused = value,
'offset': () => offset = value,
'_duration': () => _duration = value,
} }
}) // put init data description[key] && description[key]()
})
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const autoplayDirectionFnDescription = {
[NEXT]: async () => await progressManager.start(showNextPage),
[PREV]: async () => await progressManager.start(showPrevPage)
}
const directionFnDescription = { const directionFnDescription = {
[NEXT]: showNextPage, [NEXT]: methods.showNextPage,
[PREV]: showPrevPage [PREV]: methods.showPrevPage
} }
/** /**
@@ -82,30 +85,42 @@
* Transition duration (ms) * Transition duration (ms)
*/ */
export let duration = 500 export let duration = 500
let _duration = duration $: {
data.duration = duration
}
/** /**
* Enables autoplay of pages * Enables autoplay of pages
*/ */
export let autoplay = false export let autoplay = false
$: { $: {
applyAutoplayIfNeeded(autoplay) data.autoplay = autoplay
methods.applyAutoplayIfNeeded(autoplay) // call in carousel2
} }
/** /**
* Autoplay change interval (ms) * Autoplay change interval (ms)
*/ */
export let autoplayDuration = 3000 export let autoplayDuration = 3000
$: {
data.autoplayDuration = autoplayDuration
}
/** /**
* Autoplay change direction ('next', 'prev') * Autoplay change direction ('next', 'prev')
*/ */
export let autoplayDirection = NEXT export let autoplayDirection = NEXT
$: {
data.autoplayDirection = autoplayDirection
}
/** /**
* Pause autoplay on focus * Pause autoplay on focus
*/ */
export let pauseOnFocus = false export let pauseOnFocus = false
$: {
data.pauseOnFocus = pauseOnFocus
}
/** /**
* Show autoplay duration progress indicator * Show autoplay duration progress indicator
@@ -127,8 +142,7 @@
*/ */
export let particlesToShow = 1 export let particlesToShow = 1
$: { $: {
console.log('particlesToShow changed') data.particlesToShow = particlesToShow // verify, normalize, use from data
// setParticlesToShow(particlesToShow)
} }
/** /**
@@ -136,7 +150,7 @@
*/ */
export let particlesToScroll = 1 export let particlesToScroll = 1
$: { $: {
// setParticlesToScroll(particlesToScroll) data.particlesToScroll = particlesToScroll // verify, normalize, use from data
} }
export async function goTo(pageIndex, options) { export async function goTo(pageIndex, options) {
@@ -144,7 +158,7 @@
if (typeof pageIndex !== 'number') { if (typeof pageIndex !== 'number') {
throw new Error('pageIndex should be a number') throw new Error('pageIndex should be a number')
} }
await showParticle(getParticleIndexByPageIndex({ await methods.showParticle(getParticleIndexByPageIndex({
infinite: data.infinite, infinite: data.infinite,
pageIndex, pageIndex,
clonesCountHead: data.clonesCountHead, clonesCountHead: data.clonesCountHead,
@@ -157,17 +171,17 @@
export async function goToPrev(options) { export async function goToPrev(options) {
const animated = get(options, 'animated', true) const animated = get(options, 'animated', true)
await showPrevPage({ animated }) await methods.showPrevPage({ animated })
} }
export async function goToNext(options) { export async function goToNext(options) {
const animated = get(options, 'animated', true) const animated = get(options, 'animated', true)
await showNextPage({ animated }) await methods.showNextPage({ animated })
} }
let pageWindowWidth = 0 let pageWindowWidth = 0
let particleWidth = 0 let particleWidth = 0
let offset = 0
let pageWindowElement let pageWindowElement
let particlesContainer let particlesContainer
@@ -177,35 +191,18 @@
}) => { }) => {
pageWindowWidth = width pageWindowWidth = width
particleWidth = pageWindowWidth / data.particlesToShow particleWidth = pageWindowWidth / data.particlesToShow
data.particleWidth = pageWindowWidth / data.particlesToShow
applyParticleSizes({ applyParticleSizes({
particlesContainerChildren: particlesContainer.children, particlesContainerChildren: particlesContainer.children,
particleWidth, particleWidth: data.particleWidth,
}) })
offsetPage({ methods.offsetPage({
animated: false, animated: false,
}) })
}) })
let focused = false
let progressValue
const progressManager = new ProgressManager({
autoplayDuration,
onProgressValueChange: (value) => {
progressValue = 1 - value
}
})
$: {
if (pauseOnFocus) {
if (focused) {
progressManager.pause()
} else {
progressManager.resume()
}
}
}
// used for lazy loading images, preloaded only current, adjacent and cloanable images // used for lazy loading images, preloaded only current, adjacent and cloanable images
let loaded = [] let loaded = []
@@ -234,22 +231,6 @@
}) })
} }
async function applyAutoplayIfNeeded(autoplay) {
// prevent progress change if not infinite for first and last page
if (
!data.infinite && (
(autoplayDirection === NEXT && data.currentParticleIndex === data.particlesCount - 1) ||
(autoplayDirection === PREV && data.currentParticleIndex === 0)
)
) {
progressManager.reset()
return
}
if (autoplay) {
await autoplayDirectionFnDescription[autoplayDirection]()
}
}
let cleanupFns = [] let cleanupFns = []
@@ -280,7 +261,7 @@
}) })
async function handlePageChange(pageIndex) { async function handlePageChange(pageIndex) {
await showParticle(getParticleIndexByPageIndex({ await methods.showParticle(getParticleIndexByPageIndex({
infinite: data.infinite, infinite: data.infinite,
pageIndex, pageIndex,
clonesCountHead: data.clonesCountHead, clonesCountHead: data.clonesCountHead,
@@ -291,72 +272,6 @@
})) }))
} }
function offsetPage(options) {
const animated = get(options, 'animated', true)
return new Promise((resolve) => {
// _duration is an offset animation time
_duration = animated ? duration : 0
offset = -data.currentParticleIndex * particleWidth
setTimeout(() => {
resolve()
}, _duration)
})
}
// makes delayed jump to 1st or last element
async function jumpIfNeeded() {
let jumped = false
if (data.infinite) {
if (data.currentParticleIndex === 0) {
await showParticle(data.particlesCount - data.clonesCountTotal, { animated: false })
jumped = true
} else if (data.currentParticleIndex === data.particlesCount - data.clonesCountTail) {
await showParticle(data.clonesCountHead, { animated: false })
jumped = true
}
}
return jumped
}
// Disable page change while animation is in progress
let disabled = false
async function changePage(updateStoreFn, options) {
progressManager.reset()
if (disabled) return
disabled = true
updateStoreFn()
await offsetPage({ animated: get(options, 'animated', true) })
disabled = false
const jumped = await jumpIfNeeded()
!jumped && applyAutoplayIfNeeded(autoplay) // no need to wait it finishes
}
async function showParticle(particleIndex, options) {
console.log('showParticle => particleIndex', particleIndex)
await changePage(
() => methods.moveToParticle(particleIndex),
options
)
}
async function showPrevPage(options) {
if (disabled) return
await changePage(
methods.prev,
options,
)
}
async function showNextPage(options) {
if (disabled) return
await changePage(
methods.next,
options,
)
}
// gestures // gestures
function handleSwipeStart() { function handleSwipeStart() {
if (!swiping) return if (!swiping) return
@@ -372,30 +287,30 @@
} }
function handleSwipeEnd() { function handleSwipeEnd() {
if (!swiping) return if (!swiping) return
showParticle(data.currentParticleIndex) methods.showParticle(data.currentParticleIndex)
} }
async function handleSwipeFailed() { async function handleSwipeFailed() {
if (!swiping) return if (!swiping) return
await offsetPage({ animated: true }) await methods.offsetPage({ animated: true })
} }
function handleHovered(event) { function handleHovered(event) {
focused = event.detail.value data.focused = event.detail.value
} }
function handleTapped(event) { function handleTapped(event) {
focused = !focused methods.toggleFocused()
} }
</script> </script>
<div class="sc-carousel__carousel-container"> <div class="sc-carousel__carousel-container">
<div class="sc-carousel__content-container"> <div class="sc-carousel__content-container">
{#if arrows} {#if arrows}
<slot name="prev" {showPrevPage}> <slot name="prev" showPrevPage={methods.showPrevPage}>
<div class="sc-carousel__arrow-container"> <div class="sc-carousel__arrow-container">
<Arrow <Arrow
direction="prev" direction="prev"
disabled={!data.infinite && currentPageIndex === 0} disabled={!data.infinite && currentPageIndex === 0}
on:click={showPrevPage} on:click={methods.showPrevPage}
/> />
</div> </div>
</slot> </slot>
@@ -434,12 +349,12 @@
{/if} {/if}
</div> </div>
{#if arrows} {#if arrows}
<slot name="next" {showNextPage}> <slot name="next" showNextPage={methods.showNextPage}>
<div class="sc-carousel__arrow-container"> <div class="sc-carousel__arrow-container">
<Arrow <Arrow
direction="next" direction="next"
disabled={!data.infinite && currentPageIndex === data.pagesCount - 1} disabled={!data.infinite && currentPageIndex === data.pagesCount - 1}
on:click={showNextPage} on:click={methods.showNextPage}
/> />
</div> </div>
</slot> </slot>

View File

@@ -41,10 +41,17 @@ export const carousel2 = (onChange) => {
pagesCount: 1, pagesCount: 1,
pauseOnFocus: false, pauseOnFocus: false,
focused: false, focused: false,
autoplay: false,
autoplayDirection: 'next',
disabled: false, // Disable page change while animation is in progress
duration: 1000,
_duration: 1000,
offset: 0,
particleWidth: 0,
}, },
{ {
setCurrentPageIndex: (data) => { setCurrentPageIndex: (data) => {
const ind = getCurrentPageIndexByCurrentParticleIndex({ data.currentPageIndex = getCurrentPageIndexByCurrentParticleIndex({
currentParticleIndex: data.currentParticleIndex, currentParticleIndex: data.currentParticleIndex,
particlesCount: data.particlesCount, particlesCount: data.particlesCount,
clonesCountHead: data.clonesCountHead, clonesCountHead: data.clonesCountHead,
@@ -52,8 +59,6 @@ export const carousel2 = (onChange) => {
infinite: data.initialPageIndex, infinite: data.initialPageIndex,
particlesToScroll: data.particlesToScroll, particlesToScroll: data.particlesToScroll,
}) })
data.currentPageIndex = ind
console.log('===> data.currentPageIndex', ind)
}, },
setPartialPageSize: (data) => { setPartialPageSize: (data) => {
data.partialPageSize = getPartialPageSize({ data.partialPageSize = getPartialPageSize({
@@ -81,21 +86,23 @@ export const carousel2 = (onChange) => {
}) })
}, },
setProgressManagerAutoplayDuration: (data) => { setProgressManagerAutoplayDuration: (data) => {
// progressManager.setAutoplayDuration(data.autoplayDuration) progressManager.setAutoplayDuration(data.autoplayDuration)
// progressManager.restart()
}, },
toggleProgressManager: (data) => { toggleProgressManager: ({ pauseOnFocus, focused }) => {
if (data.pauseOnFocus) { if (pauseOnFocus) {
if (data.focused) { if (focused) {
progressManager.pause() progressManager.pause()
} else { } else {
progressManager.resume() progressManager.resume()
} }
} }
}, },
initDuration: (data) => {
data._duration = data.duration
},
}, },
{ {
prev: (data) => { _prev: (data) => {
const newCurrentParticleIndex = getParticleIndexByPageIndex({ const newCurrentParticleIndex = getParticleIndexByPageIndex({
infinite: data.infinite, infinite: data.infinite,
pageIndex: data.currentPageIndex - 1, pageIndex: data.currentPageIndex - 1,
@@ -107,7 +114,7 @@ export const carousel2 = (onChange) => {
}) })
data.currentParticleIndex = newCurrentParticleIndex data.currentParticleIndex = newCurrentParticleIndex
}, },
next: (data) => { _next: (data) => {
const newCurrentParticleIndex = getParticleIndexByPageIndex({ const newCurrentParticleIndex = getParticleIndexByPageIndex({
infinite: data.infinite, infinite: data.infinite,
pageIndex: data.currentPageIndex + 1, pageIndex: data.currentPageIndex + 1,
@@ -119,7 +126,7 @@ export const carousel2 = (onChange) => {
}) })
data.currentParticleIndex = newCurrentParticleIndex data.currentParticleIndex = newCurrentParticleIndex
}, },
moveToParticle: (data, particleIndex) => { _moveToParticle: (data, _, particleIndex) => {
const newCurrentParticleIndex = getValueInRange( const newCurrentParticleIndex = getValueInRange(
0, 0,
particleIndex, particleIndex,
@@ -130,9 +137,116 @@ export const carousel2 = (onChange) => {
toggleFocused: (data) => { toggleFocused: (data) => {
data.focused = !data.focused data.focused = !data.focused
}, },
applyAutoplayIfNeeded: async (
{
infinite,
autoplayDirection,
currentParticleIndex,
particlesCount,
autoplay,
},
{ showNextPage, showPrevPage }
) => {
// prevent progress change if not infinite for first and last page
if (
!infinite &&
((autoplayDirection === NEXT &&
currentParticleIndex === particlesCount - 1) ||
(autoplayDirection === PREV && currentParticleIndex === 0))
) {
progressManager.reset()
return
}
if (autoplay) {
const autoplayDirectionFnDescription = {
[NEXT]: async () => await progressManager.start(showNextPage),
[PREV]: async () => await progressManager.start(showPrevPage),
}
await autoplayDirectionFnDescription[autoplayDirection]()
}
},
// makes delayed jump to 1st or last element
_jumpIfNeeded: async (
{
infinite,
currentParticleIndex,
particlesCount,
clonesCountTotal,
clonesCountTail,
clonesCountHead,
},
{ showParticle }
) => {
let jumped = false
if (infinite) {
if (currentParticleIndex === 0) {
await showParticle(particlesCount - clonesCountTotal, {
animated: false,
})
jumped = true
} else if (
currentParticleIndex ===
particlesCount - clonesCountTail
) {
await showParticle(clonesCountHead, {
animated: false,
})
jumped = true
}
}
return jumped
},
changePage: async (
data,
{ offsetPage, applyAutoplayIfNeeded, _jumpIfNeeded },
updateStoreFn,
options
) => {
progressManager.reset()
if (data.disabled) return
data.disabled = true
updateStoreFn()
await offsetPage({ animated: get(options, 'animated', true) })
data.disabled = false
const jumped = await _jumpIfNeeded()
!jumped && applyAutoplayIfNeeded() // no need to wait it finishes
},
showNextPage: async ({ disabled }, { changePage, _next }, options) => {
if (disabled) return
await changePage(_next, options)
},
showPrevPage: async ({ disabled }, { changePage, _prev }, options) => {
if (disabled) return
await changePage(_prev, options)
},
showParticle: async (
_,
{ changePage, _moveToParticle },
particleIndex,
options
) => {
await changePage(() => _moveToParticle(particleIndex), options)
},
offsetPage: (data, _, options) => {
const animated = get(options, 'animated', true)
return new Promise((resolve) => {
// _duration is an offset animation time
data._duration = animated ? data.duration : 0
data.offset = -data.currentParticleIndex * data.particleWidth
setTimeout(() => {
resolve()
}, data._duration)
})
},
}, },
onChange onChange
) )
return [{ ...data, progressManager }, methods] return [{ data, progressManager }, methods]
// return [{ ...data, progressManager }, methods]
} }

View File

@@ -104,7 +104,7 @@ export const reactive = (data, watchers, methods, onChange) => {
const _methods = {} const _methods = {}
Object.entries(methods).forEach(([methodName, method]) => { Object.entries(methods).forEach(([methodName, method]) => {
_methods[methodName] = (args) => method(_data, args) _methods[methodName] = (...args) => method(_data, _methods, ...args)
}) })
return [_data, _methods] return [_data, _methods]

View File

@@ -121,7 +121,7 @@
{/each} {/each}
</Carousel> </Carousel>
<Carousel <!-- <Carousel
{timingFunction} {timingFunction}
{arrows} {arrows}
{infinite} {infinite}
@@ -145,7 +145,7 @@
<p>{text}</p> <p>{text}</p>
</div> </div>
{/each} {/each}
</Carousel> </Carousel> -->
</div> </div>
<style> <style>

View File

@@ -10,14 +10,14 @@ export class ProgressManager {
#interval #interval
#paused = false #paused = false
constructor({ constructor({ onProgressValueChange }) {
autoplayDuration,
onProgressValueChange,
}) {
this.#autoplayDuration = autoplayDuration
this.#onProgressValueChange = onProgressValueChange this.#onProgressValueChange = onProgressValueChange
} }
setAutoplayDuration(autoplayDuration) {
this.#autoplayDuration = autoplayDuration
}
start(onFinish) { start(onFinish) {
return new Promise((resolve) => { return new Promise((resolve) => {
this.reset() this.reset()