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 { carouselEngine } from './carousel'
import { carousel2 } from './carousel2'
import { reactive } from './reactive'
const data = reactive(
{
message: 'hello',
someValue: 'some-value'
},
{
renderFunction: (data) => {
console.log('renderFunction watcher called', data.message)
},
}
)
console.log('console', data)
let currentPageIndex
const [data, methods] = carousel2((key, value) => {
// console.log('onChange', key, value)
if (key === 'currentPageIndex') {
currentPageIndex = value
}
}) // put init data
data.message = 'Hello!'
const dispatch = createEventDispatcher()
@@ -76,7 +72,7 @@
*/
export let infinite
$: {
setInfinite(infinite)
data.infinite = infinite
}
/**
@@ -151,13 +147,13 @@
throw new Error('pageIndex should be a number')
}
await showParticle(getParticleIndexByPageIndex({
infinite,
infinite: data.infinite,
pageIndex,
clonesCountHead: clonesCount.head,
clonesCountTail: clonesCount.tail,
particlesToScroll,
particlesCount,
particlesToShow,
clonesCountHead: data.clonesCountHead,
clonesCountTail: data.clonesCountTail,
particlesToScroll: data.particlesToScroll,
particlesCount: data.particlesCount,
particlesToShow: data.particlesToShow,
}), { animated })
}
@@ -171,44 +167,6 @@
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 particleWidth = 0
let offset = 0
@@ -220,7 +178,7 @@
width,
}) => {
pageWindowWidth = width
particleWidth = pageWindowWidth / _particlesToShow
particleWidth = pageWindowWidth / data.particlesToShow
applyParticleSizes({
particlesContainerChildren: particlesContainer.children,
@@ -267,8 +225,8 @@
clonesToAppend,
clonesToPrepend,
} = getClones({
clonesCountHead: clonesCount.head,
clonesCountTail: clonesCount.tail,
clonesCountHead: data.clonesCountHead,
clonesCountTail: data.clonesCountTail,
particlesContainerChildren: particlesContainer.children,
})
applyClones({
@@ -281,9 +239,9 @@
async function applyAutoplayIfNeeded(autoplay) {
// prevent progress change if not infinite for first and last page
if (
!infinite && (
(autoplayDirection === NEXT && currentParticleIndex === particlesCount - 1) ||
(autoplayDirection === PREV && currentParticleIndex === 0)
!data.infinite && (
(autoplayDirection === NEXT && data.currentParticleIndex === data.particlesCount - 1) ||
(autoplayDirection === PREV && data.currentParticleIndex === 0)
)
) {
progressManager.reset()
@@ -302,16 +260,16 @@
await tick()
cleanupFns.push(() => progressManager.reset())
if (particlesContainer && pageWindowElement) {
setParticlesCountWithoutClones(particlesContainer.children.length)
setParticlesToShow(particlesToShow)
setParticlesToScroll(particlesToScroll)
data.particlesCountWithoutClones = particlesContainer.children.length
data.particlesToShow = particlesToShow
data.particlesToScroll = particlesToScroll
await tick()
infinite && addClones()
data.infinite && addClones()
// call after adding clones
setParticlesCount(particlesContainer.children.length)
setInitialPageIndex(initialPageIndex)
data.particlesCount = particlesContainer.children.length
data.initialPageIndex = initialPageIndex
pageWindowElementResizeObserver.observe(pageWindowElement);
}
@@ -325,13 +283,13 @@
async function handlePageChange(pageIndex) {
await showParticle(getParticleIndexByPageIndex({
infinite,
infinite: data.infinite,
pageIndex,
clonesCountHead: clonesCount.head,
clonesCountTail: clonesCount.tail,
particlesToScroll,
particlesCount,
particlesToShow: _particlesToShow,
clonesCountHead: data.clonesCountHead,
clonesCountTail: data.clonesCountTail,
particlesToScroll: data.particlesToScroll,
particlesCount: data.particlesCount,
particlesToShow: data.particlesToShow,
}))
}
@@ -340,7 +298,7 @@
return new Promise((resolve) => {
// _duration is an offset animation time
_duration = animated ? duration : 0
offset = -currentParticleIndex * particleWidth
offset = -data.currentParticleIndex * particleWidth
setTimeout(() => {
resolve()
}, _duration)
@@ -350,12 +308,12 @@
// makes delayed jump to 1st or last element
async function jumpIfNeeded() {
let jumped = false
if (infinite) {
if (currentParticleIndex === 0) {
await showParticle(particlesCount - clonesCount.total, { animated: false })
if (data.infinite) {
if (data.currentParticleIndex === 0) {
await showParticle(data.particlesCount - data.clonesCountTotal, { animated: false })
jumped = true
} else if (currentParticleIndex === particlesCount - clonesCount.tail) {
await showParticle(clonesCount.head, { animated: false })
} else if (data.currentParticleIndex === data.particlesCount - data.clonesCountTail) {
await showParticle(data.clonesCountHead, { animated: false })
jumped = true
}
}
@@ -378,8 +336,9 @@
}
async function showParticle(particleIndex, options) {
console.log('showParticle => particleIndex', particleIndex)
await changePage(
() => moveToParticle(particleIndex),
() => methods.moveToParticle(particleIndex),
options
)
}
@@ -387,7 +346,7 @@
if (disabled) return
await changePage(
prev,
methods.prev,
options,
)
}
@@ -395,7 +354,7 @@
if (disabled) return
await changePage(
next,
methods.next,
options,
)
}
@@ -415,7 +374,7 @@
}
function handleSwipeEnd() {
if (!swiping) return
showParticle(currentParticleIndex)
showParticle(data.currentParticleIndex)
}
async function handleSwipeFailed() {
if (!swiping) return
@@ -437,7 +396,7 @@
<div class="sc-carousel__arrow-container">
<Arrow
direction="prev"
disabled={!infinite && currentPageIndex === 0}
disabled={!data.infinite && currentPageIndex === 0}
on:click={showPrevPage}
/>
</div>
@@ -481,7 +440,7 @@
<div class="sc-carousel__arrow-container">
<Arrow
direction="next"
disabled={!infinite && currentPageIndex === pagesCount - 1}
disabled={!data.infinite && currentPageIndex === data.pagesCount - 1}
on:click={showNextPage}
/>
</div>
@@ -492,11 +451,11 @@
<slot
name="dots"
currentPageIndex={currentPageIndex}
pagesCount={pagesCount}
pagesCount={data.pagesCount}
showPage={handlePageChange}
>
<Dots
pagesCount={pagesCount}
pagesCount={data.pagesCount}
currentPageIndex={currentPageIndex}
on:pageChange={event => handlePageChange(event.detail)}
></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
// reactive property changes it's value.
let target = null
class Dep {
constructor() {
this.subscribers = []
}
depend() {
// Saves target function into subscribers array
if (target && !this.subscribers.includes(target)) {
this.subscribers.push(target)
const objectsAreSame = (x, y) => {
// return false
let _objectsAreSame = true
for (let propertyName in x) {
if (Number.isNaN(x[propertyName]) || Number.isNaN(y[propertyName])) {
continue
}
if (x[propertyName] !== y[propertyName]) {
_objectsAreSame = false
break
}
}
notify() {
// Replays target functions saved in the subscribers array
this.subscribers.forEach((sub) => sub())
return _objectsAreSame
}
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) {
// Here, a watcher is a function that encapsulates the code
// that needs to recorded/watched.
// PS: It just runs once, because after that, the target code is stored
// in the subscriber's list of the Dep() instance.
target = func // Then it assigns the function to target
target() // Run the target function
target = null // Reset target to null
const useWatcher = () => {
let target = null
return {
watch: (watcherName, fn) => {
target = {
watcherName,
fn,
}
target.fn()
target = null
},
getTarget: () => {
return target
},
}
}
// reactive(
// {
// message: '',
// },
// {
// renderFunction: (data) => {
// console.log(data.message)
// },
// }
// )
export const reactive = (data, watchers, methods, onChange) => {
const { subscribe, notify } = useSubscription()
const { watch, getTarget } = useWatcher()
export const reactive = (data, watchers, methods) => {
const _data = {}
Object.keys(data).forEach((key) => {
let internalValue = data[key]
// Each property gets a dependency instance
const dep = new Dep()
let currentValue = data[key]
Object.defineProperty(_data, key, {
get() {
console.log(`Getting value, ${internalValue}`)
dep.depend() // Saves the target function into the subscribers array
return internalValue
subscribe(getTarget(), { key, value: currentValue })
return currentValue
},
set(newVal) {
console.log(`Setting the internalValue to ${newVal}`)
internalValue = newVal
dep.notify() // Reruns saved target functions in the subscribers array
currentValue = newVal
onChange && onChange(key, newVal)
notify(_data)
},
})
})
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]
}