Add reactivity

This commit is contained in:
Vadim
2021-10-05 19:33:07 +03:00
parent e80ea005d1
commit 04fc80ecec
3 changed files with 249 additions and 143 deletions

View File

@@ -31,22 +31,18 @@
import { wait } from '../../utils/interval' import { wait } from '../../utils/interval'
import { carouselEngine } from './carousel' import { carouselEngine } from './carousel'
import { carousel2 } from './carousel2'
import { reactive } from './reactive' import { reactive } from './reactive'
const data = reactive( let currentPageIndex
{
message: 'hello',
someValue: 'some-value'
},
{
renderFunction: (data) => {
console.log('renderFunction watcher called', data.message)
},
}
)
console.log('console', data)
data.message = 'Hello!' const [data, methods] = carousel2((key, value) => {
// console.log('onChange', key, value)
if (key === 'currentPageIndex') {
currentPageIndex = value
}
}) // put init data
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
@@ -76,7 +72,7 @@
*/ */
export let infinite export let infinite
$: { $: {
setInfinite(infinite) data.infinite = infinite
} }
/** /**
@@ -151,13 +147,13 @@
throw new Error('pageIndex should be a number') throw new Error('pageIndex should be a number')
} }
await showParticle(getParticleIndexByPageIndex({ await showParticle(getParticleIndexByPageIndex({
infinite, infinite: data.infinite,
pageIndex, pageIndex,
clonesCountHead: clonesCount.head, clonesCountHead: data.clonesCountHead,
clonesCountTail: clonesCount.tail, clonesCountTail: data.clonesCountTail,
particlesToScroll, particlesToScroll: data.particlesToScroll,
particlesCount, particlesCount: data.particlesCount,
particlesToShow, particlesToShow: data.particlesToShow,
}), { animated }) }), { animated })
} }
@@ -171,44 +167,6 @@
await showNextPage({ animated }) await showNextPage({ animated })
} }
///// ==========================
let currentPageIndex
let pagesCount
let particlesCountWithoutClones
let clonesCount
let currentParticleIndex = 0
let particlesCount = 0
let _particlesToShow
let _particlesToScroll
const {
setParticlesToShow,
setParticlesToScroll,
setParticlesCountWithoutClones,
setPartialPageSize,
setParticlesCount,
setCurrentParticleIndex,
setInitialPageIndex,
setInfinite,
prev,
next,
moveToParticle,
} = carouselEngine((values, computed) => {
// console.log('hello')
currentPageIndex = computed.currentPageIndex
pagesCount = computed.pagesCount
particlesCountWithoutClones = values.particlesCountWithoutClones
clonesCount = computed.clonesCount
currentParticleIndex = values.currentParticleIndex
particlesCount = values.particlesCount
_particlesToShow = values.particlesToShow
_particlesToScroll = values.particlesToScroll
})
///// ==========================
let pageWindowWidth = 0 let pageWindowWidth = 0
let particleWidth = 0 let particleWidth = 0
let offset = 0 let offset = 0
@@ -220,7 +178,7 @@
width, width,
}) => { }) => {
pageWindowWidth = width pageWindowWidth = width
particleWidth = pageWindowWidth / _particlesToShow particleWidth = pageWindowWidth / data.particlesToShow
applyParticleSizes({ applyParticleSizes({
particlesContainerChildren: particlesContainer.children, particlesContainerChildren: particlesContainer.children,
@@ -267,8 +225,8 @@
clonesToAppend, clonesToAppend,
clonesToPrepend, clonesToPrepend,
} = getClones({ } = getClones({
clonesCountHead: clonesCount.head, clonesCountHead: data.clonesCountHead,
clonesCountTail: clonesCount.tail, clonesCountTail: data.clonesCountTail,
particlesContainerChildren: particlesContainer.children, particlesContainerChildren: particlesContainer.children,
}) })
applyClones({ applyClones({
@@ -281,9 +239,9 @@
async function applyAutoplayIfNeeded(autoplay) { async function applyAutoplayIfNeeded(autoplay) {
// prevent progress change if not infinite for first and last page // prevent progress change if not infinite for first and last page
if ( if (
!infinite && ( !data.infinite && (
(autoplayDirection === NEXT && currentParticleIndex === particlesCount - 1) || (autoplayDirection === NEXT && data.currentParticleIndex === data.particlesCount - 1) ||
(autoplayDirection === PREV && currentParticleIndex === 0) (autoplayDirection === PREV && data.currentParticleIndex === 0)
) )
) { ) {
progressManager.reset() progressManager.reset()
@@ -302,16 +260,16 @@
await tick() await tick()
cleanupFns.push(() => progressManager.reset()) cleanupFns.push(() => progressManager.reset())
if (particlesContainer && pageWindowElement) { if (particlesContainer && pageWindowElement) {
setParticlesCountWithoutClones(particlesContainer.children.length) data.particlesCountWithoutClones = particlesContainer.children.length
setParticlesToShow(particlesToShow) data.particlesToShow = particlesToShow
setParticlesToScroll(particlesToScroll) data.particlesToScroll = particlesToScroll
await tick() await tick()
infinite && addClones() data.infinite && addClones()
// call after adding clones // call after adding clones
setParticlesCount(particlesContainer.children.length) data.particlesCount = particlesContainer.children.length
setInitialPageIndex(initialPageIndex) data.initialPageIndex = initialPageIndex
pageWindowElementResizeObserver.observe(pageWindowElement); pageWindowElementResizeObserver.observe(pageWindowElement);
} }
@@ -325,13 +283,13 @@
async function handlePageChange(pageIndex) { async function handlePageChange(pageIndex) {
await showParticle(getParticleIndexByPageIndex({ await showParticle(getParticleIndexByPageIndex({
infinite, infinite: data.infinite,
pageIndex, pageIndex,
clonesCountHead: clonesCount.head, clonesCountHead: data.clonesCountHead,
clonesCountTail: clonesCount.tail, clonesCountTail: data.clonesCountTail,
particlesToScroll, particlesToScroll: data.particlesToScroll,
particlesCount, particlesCount: data.particlesCount,
particlesToShow: _particlesToShow, particlesToShow: data.particlesToShow,
})) }))
} }
@@ -340,7 +298,7 @@
return new Promise((resolve) => { return new Promise((resolve) => {
// _duration is an offset animation time // _duration is an offset animation time
_duration = animated ? duration : 0 _duration = animated ? duration : 0
offset = -currentParticleIndex * particleWidth offset = -data.currentParticleIndex * particleWidth
setTimeout(() => { setTimeout(() => {
resolve() resolve()
}, _duration) }, _duration)
@@ -350,12 +308,12 @@
// makes delayed jump to 1st or last element // makes delayed jump to 1st or last element
async function jumpIfNeeded() { async function jumpIfNeeded() {
let jumped = false let jumped = false
if (infinite) { if (data.infinite) {
if (currentParticleIndex === 0) { if (data.currentParticleIndex === 0) {
await showParticle(particlesCount - clonesCount.total, { animated: false }) await showParticle(data.particlesCount - data.clonesCountTotal, { animated: false })
jumped = true jumped = true
} else if (currentParticleIndex === particlesCount - clonesCount.tail) { } else if (data.currentParticleIndex === data.particlesCount - data.clonesCountTail) {
await showParticle(clonesCount.head, { animated: false }) await showParticle(data.clonesCountHead, { animated: false })
jumped = true jumped = true
} }
} }
@@ -378,8 +336,9 @@
} }
async function showParticle(particleIndex, options) { async function showParticle(particleIndex, options) {
console.log('showParticle => particleIndex', particleIndex)
await changePage( await changePage(
() => moveToParticle(particleIndex), () => methods.moveToParticle(particleIndex),
options options
) )
} }
@@ -387,7 +346,7 @@
if (disabled) return if (disabled) return
await changePage( await changePage(
prev, methods.prev,
options, options,
) )
} }
@@ -395,7 +354,7 @@
if (disabled) return if (disabled) return
await changePage( await changePage(
next, methods.next,
options, options,
) )
} }
@@ -415,7 +374,7 @@
} }
function handleSwipeEnd() { function handleSwipeEnd() {
if (!swiping) return if (!swiping) return
showParticle(currentParticleIndex) showParticle(data.currentParticleIndex)
} }
async function handleSwipeFailed() { async function handleSwipeFailed() {
if (!swiping) return if (!swiping) return
@@ -437,7 +396,7 @@
<div class="sc-carousel__arrow-container"> <div class="sc-carousel__arrow-container">
<Arrow <Arrow
direction="prev" direction="prev"
disabled={!infinite && currentPageIndex === 0} disabled={!data.infinite && currentPageIndex === 0}
on:click={showPrevPage} on:click={showPrevPage}
/> />
</div> </div>
@@ -481,7 +440,7 @@
<div class="sc-carousel__arrow-container"> <div class="sc-carousel__arrow-container">
<Arrow <Arrow
direction="next" direction="next"
disabled={!infinite && currentPageIndex === pagesCount - 1} disabled={!data.infinite && currentPageIndex === data.pagesCount - 1}
on:click={showNextPage} on:click={showNextPage}
/> />
</div> </div>
@@ -492,11 +451,11 @@
<slot <slot
name="dots" name="dots"
currentPageIndex={currentPageIndex} currentPageIndex={currentPageIndex}
pagesCount={pagesCount} pagesCount={data.pagesCount}
showPage={handlePageChange} showPage={handlePageChange}
> >
<Dots <Dots
pagesCount={pagesCount} pagesCount={data.pagesCount}
currentPageIndex={currentPageIndex} currentPageIndex={currentPageIndex}
on:pageChange={event => handlePageChange(event.detail)} on:pageChange={event => handlePageChange(event.detail)}
></Dots> ></Dots>

View File

@@ -0,0 +1,111 @@
import { NEXT, PREV } from '../../direction'
import {
applyParticleSizes,
getCurrentPageIndexByCurrentParticleIndex,
getPartialPageSize,
getPagesCountByParticlesCount,
getParticleIndexByPageIndex,
createResizeObserver,
} from '../../utils/page'
import { getClones, applyClones, getClonesCount } from '../../utils/clones'
import { getAdjacentIndexes } from '../../utils/lazy'
import { getValueInRange } from '../../utils/math'
import { get } from '../../utils/object'
import { ProgressManager } from '../../utils/ProgressManager'
import { wait } from '../../utils/interval'
import { reactive } from './reactive'
// return only getters
export const carousel2 = (onChange) => {
return reactive(
{
particlesCountWithoutClones: 0,
particlesToShow: 1,
particlesToScroll: 1,
initialPageIndex: 1,
particlesCount: 1,
currentParticleIndex: 1,
infinite: false,
clonesCountHead: 0,
clonesCountTail: 0,
clonesCountTotal: 0,
partialPageSize: 1,
currentPageIndex: 1,
pagesCount: 1,
},
{
setCurrentPageIndex: (data) => {
const ind = getCurrentPageIndexByCurrentParticleIndex({
currentParticleIndex: data.currentParticleIndex,
particlesCount: data.particlesCount,
clonesCountHead: data.clonesCountHead,
clonesCountTotal: data.clonesCountTotal,
infinite: data.initialPageIndex,
particlesToScroll: data.particlesToScroll,
})
data.currentPageIndex = ind
console.log('===> data.currentPageIndex', ind)
},
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
},
setPagesCount: (data) => {
data.pagesCount = getPagesCountByParticlesCount({
infinite: data.infinite,
particlesCountWithoutClones: data.particlesCountWithoutClones,
particlesToScroll: data.particlesToScroll,
})
},
},
{
prev: (data) => {
const newCurrentParticleIndex = getParticleIndexByPageIndex({
infinite: data.infinite,
pageIndex: data.currentPageIndex - 1,
clonesCountHead: data.clonesCountHead,
clonesCountTail: data.clonesCountTail,
particlesToScroll: data.particlesToScroll,
particlesCount: data.particlesCount,
particlesToShow: data.particlesToShow,
})
data.currentParticleIndex = newCurrentParticleIndex
},
next: (data) => {
const newCurrentParticleIndex = getParticleIndexByPageIndex({
infinite: data.infinite,
pageIndex: data.currentPageIndex + 1,
clonesCountHead: data.clonesCountHead,
clonesCountTail: data.clonesCountTail,
particlesToScroll: data.particlesToScroll,
particlesCount: data.particlesCount,
particlesToShow: data.particlesToShow,
})
data.currentParticleIndex = newCurrentParticleIndex
},
moveToParticle: (data, particleIndex) => {
const newCurrentParticleIndex = getValueInRange(
0,
particleIndex,
data.particlesCount - 1
)
data.currentParticleIndex = newCurrentParticleIndex
},
},
onChange
)
}

View File

@@ -1,80 +1,116 @@
// let data = {
// message: '',
// }
// let methods = {
// changeMessage: function () {
// data.message = document.getElementById('messageInput').value
// },
// }
// Code that has to run when a // Code that has to run when a
// reactive property changes it's value. // reactive property changes it's value.
let target = null
class Dep { const objectsAreSame = (x, y) => {
constructor() { // return false
this.subscribers = [] let _objectsAreSame = true
} for (let propertyName in x) {
depend() { if (Number.isNaN(x[propertyName]) || Number.isNaN(y[propertyName])) {
// Saves target function into subscribers array continue
if (target && !this.subscribers.includes(target)) { }
this.subscribers.push(target) if (x[propertyName] !== y[propertyName]) {
_objectsAreSame = false
break
} }
} }
notify() {
// Replays target functions saved in the subscribers array return _objectsAreSame
this.subscribers.forEach((sub) => sub()) }
const getObject = (oldData, newData) => {
const newDeps = {}
Object.entries(oldData).forEach(([key, value]) => {
// console.log('oldData', key, value)
newDeps[key] = newData[key]
})
// console.log('isDiff', oldData, newDeps)
return newDeps
}
const useSubscription = () => {
const subscribers = {}
const memoDependency = (target, dep) => {
const { watcherName, fn } = target
const { key, value } = dep
if (!subscribers[watcherName]) {
subscribers[watcherName] = {
deps: {},
fn,
}
}
subscribers[watcherName].deps[key] = value
}
return {
subscribe: (target, dep) => {
if (target) {
memoDependency(target, dep)
}
},
notify: (data) => {
Object.entries(subscribers).forEach(([watcherName, { deps }]) => {
const newDeps = getObject(deps, data)
if (!objectsAreSame(deps, newDeps)) {
subscribers[watcherName].deps = newDeps
subscribers[watcherName].fn()
}
})
},
} }
} }
let watch = function (func) { const useWatcher = () => {
// Here, a watcher is a function that encapsulates the code let target = null
// that needs to recorded/watched.
// PS: It just runs once, because after that, the target code is stored return {
// in the subscriber's list of the Dep() instance. watch: (watcherName, fn) => {
target = func // Then it assigns the function to target target = {
target() // Run the target function watcherName,
target = null // Reset target to null fn,
}
target.fn()
target = null
},
getTarget: () => {
return target
},
}
} }
// reactive( export const reactive = (data, watchers, methods, onChange) => {
// { const { subscribe, notify } = useSubscription()
// message: '', const { watch, getTarget } = useWatcher()
// },
// {
// renderFunction: (data) => {
// console.log(data.message)
// },
// }
// )
export const reactive = (data, watchers, methods) => {
const _data = {} const _data = {}
Object.keys(data).forEach((key) => { Object.keys(data).forEach((key) => {
let internalValue = data[key] let currentValue = data[key]
// Each property gets a dependency instance
const dep = new Dep()
Object.defineProperty(_data, key, { Object.defineProperty(_data, key, {
get() { get() {
console.log(`Getting value, ${internalValue}`) subscribe(getTarget(), { key, value: currentValue })
dep.depend() // Saves the target function into the subscribers array return currentValue
return internalValue
}, },
set(newVal) { set(newVal) {
console.log(`Setting the internalValue to ${newVal}`) currentValue = newVal
internalValue = newVal onChange && onChange(key, newVal)
dep.notify() // Reruns saved target functions in the subscribers array notify(_data)
}, },
}) })
}) })
Object.entries(watchers).forEach(([watcherName, watcher]) => { Object.entries(watchers).forEach(([watcherName, watcher]) => {
watch(() => watcher(_data)) watch(watcherName, () => watcher(_data))
}) })
return _data const _methods = {}
Object.entries(methods).forEach(([methodName, method]) => {
_methods[methodName] = (args) => method(_data, args)
})
return [_data, _methods]
} }