Merge pull request #46 from vadimkorr/feature/45-promisify-functions
feature/45 Promisify functions
This commit is contained in:
@@ -13,30 +13,19 @@
|
|||||||
} from '../../utils/event'
|
} from '../../utils/event'
|
||||||
import { getAdjacentIndexes } from '../../utils/page'
|
import { getAdjacentIndexes } from '../../utils/page'
|
||||||
import { get } from '../../utils/object'
|
import { get } from '../../utils/object'
|
||||||
import { ProgressManager } from '../../utils/ProgressManager.js'
|
import { ProgressManager } from '../../utils/ProgressManager'
|
||||||
|
import { wait } from '../../utils/interval'
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
const autoplayDirectionFnDescription = {
|
const autoplayDirectionFnDescription = {
|
||||||
[NEXT]: () => {
|
[NEXT]: async () => await progressManager.start(showNextPage),
|
||||||
progressManager.start(() => {
|
[PREV]: async () => await progressManager.start(showPrevPage)
|
||||||
showNextPage()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
[PREV]: () => {
|
|
||||||
progressManager.start(() => {
|
|
||||||
showPrevPage()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const directionFnDescription = {
|
const directionFnDescription = {
|
||||||
[NEXT]: () => {
|
[NEXT]: showNextPage,
|
||||||
showNextPage()
|
[PREV]: showPrevPage
|
||||||
},
|
|
||||||
[PREV]: () => {
|
|
||||||
showPrevPage()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -70,6 +59,9 @@
|
|||||||
* Enables autoplay of pages
|
* Enables autoplay of pages
|
||||||
*/
|
*/
|
||||||
export let autoplay = false
|
export let autoplay = false
|
||||||
|
$: {
|
||||||
|
applyAutoplayIfNeeded(autoplay)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Autoplay change interval (ms)
|
* Autoplay change interval (ms)
|
||||||
@@ -96,31 +88,42 @@
|
|||||||
*/
|
*/
|
||||||
export let dots = true
|
export let dots = true
|
||||||
|
|
||||||
export function goTo(pageIndex, options) {
|
export async function goTo(pageIndex, options) {
|
||||||
const animated = get(options, 'animated', true)
|
const animated = get(options, 'animated', true)
|
||||||
if (typeof pageIndex !== 'number') {
|
if (typeof pageIndex !== 'number') {
|
||||||
throw new Error('pageIndex should be a number')
|
throw new Error('pageIndex should be a number')
|
||||||
}
|
}
|
||||||
showPage(pageIndex + Number(infinite), { animated })
|
await showPage(pageIndex + Number(infinite), { animated })
|
||||||
}
|
}
|
||||||
|
|
||||||
export function goToPrev(options) {
|
export async function goToPrev(options) {
|
||||||
const animated = get(options, 'animated', true)
|
const animated = get(options, 'animated', true)
|
||||||
showPrevPage({ animated })
|
await showPrevPage({ animated })
|
||||||
}
|
}
|
||||||
|
|
||||||
export function goToNext(options) {
|
export async function goToNext(options) {
|
||||||
const animated = get(options, 'animated', true)
|
const animated = get(options, 'animated', true)
|
||||||
showNextPage({ animated })
|
await showNextPage({ animated })
|
||||||
}
|
}
|
||||||
|
|
||||||
let store = createStore()
|
let store = createStore()
|
||||||
let currentPageIndex = 0
|
let currentPageIndex = 0
|
||||||
$: originalCurrentPageIndex = currentPageIndex - Number(infinite);
|
$: originalCurrentPageIndex = getOriginalCurrentPageIndex(currentPageIndex, pagesCount, infinite) // index without cloenes
|
||||||
$: dispatch('pageChange', originalCurrentPageIndex)
|
$: dispatch('pageChange', originalCurrentPageIndex)
|
||||||
|
|
||||||
let pagesCount = 0
|
let pagesCount = 0
|
||||||
$: originalPagesCount = Math.max(pagesCount - (infinite ? 2 : 0), 1) // without clones
|
$: originalPagesCount = Math.max(pagesCount - (infinite ? 2 : 0), 1) // without clones
|
||||||
|
|
||||||
|
function getOriginalCurrentPageIndex(currentPageIndex, pagesCount, infinite) {
|
||||||
|
if (infinite) {
|
||||||
|
const CLONES_COUNT = 2
|
||||||
|
if (currentPageIndex === pagesCount - 1) return 0
|
||||||
|
if (currentPageIndex === 0) return (pagesCount - CLONES_COUNT) - 1
|
||||||
|
return currentPageIndex - 1
|
||||||
|
}
|
||||||
|
return currentPageIndex
|
||||||
|
}
|
||||||
|
|
||||||
let pageWidth = 0
|
let pageWidth = 0
|
||||||
let offset = 0
|
let offset = 0
|
||||||
let pageWindowElement
|
let pageWindowElement
|
||||||
@@ -169,7 +172,7 @@
|
|||||||
pagesElement.append(first.cloneNode(true))
|
pagesElement.append(first.cloneNode(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyAutoplayIfNeeded(options) {
|
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 && (
|
!infinite && (
|
||||||
@@ -180,16 +183,8 @@
|
|||||||
progressManager.reset()
|
progressManager.reset()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (autoplay) {
|
|
||||||
const delayMs = get(options, 'delayMs', 0)
|
autoplay && await autoplayDirectionFnDescription[autoplayDirection]()
|
||||||
if (delayMs) {
|
|
||||||
setTimeout(() => {
|
|
||||||
autoplayDirectionFnDescription[autoplayDirection]()
|
|
||||||
}, delayMs)
|
|
||||||
} else {
|
|
||||||
autoplayDirectionFnDescription[autoplayDirection]()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let cleanupFns = []
|
let cleanupFns = []
|
||||||
@@ -211,8 +206,6 @@
|
|||||||
applyPageSizes()
|
applyPageSizes()
|
||||||
}
|
}
|
||||||
|
|
||||||
applyAutoplayIfNeeded()
|
|
||||||
|
|
||||||
addResizeEventListener(applyPageSizes)
|
addResizeEventListener(applyPageSizes)
|
||||||
})()
|
})()
|
||||||
})
|
})
|
||||||
@@ -222,26 +215,30 @@
|
|||||||
cleanupFns.filter(fn => fn && typeof fn === 'function').forEach(fn => fn())
|
cleanupFns.filter(fn => fn && typeof fn === 'function').forEach(fn => fn())
|
||||||
})
|
})
|
||||||
|
|
||||||
function handlePageChange(pageIndex) {
|
async function handlePageChange(pageIndex) {
|
||||||
showPage(pageIndex + Number(infinite))
|
await showPage(pageIndex + Number(infinite))
|
||||||
}
|
}
|
||||||
|
|
||||||
function offsetPage(animated) {
|
function offsetPage(animated) {
|
||||||
// _duration is an offset animation time
|
return new Promise((resolve) => {
|
||||||
_duration = animated ? duration : 0
|
// _duration is an offset animation time
|
||||||
offset = -currentPageIndex * pageWidth
|
_duration = animated ? duration : 0
|
||||||
|
offset = -currentPageIndex * pageWidth
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve()
|
||||||
|
}, _duration)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// makes delayed jump to 1st or last element
|
// makes delayed jump to 1st or last element
|
||||||
function jumpIfNeeded() {
|
async function jumpIfNeeded() {
|
||||||
let jumped = false
|
let jumped = false
|
||||||
if (infinite) {
|
if (infinite) {
|
||||||
if (currentPageIndex === 0) {
|
if (currentPageIndex === 0) {
|
||||||
// offsetDelayMs should depend on _duration, as it wait when offset finishes
|
await showPage(pagesCount - 2, { animated: false })
|
||||||
showPage(pagesCount - 2, { offsetDelayMs: _duration, animated: false })
|
|
||||||
jumped = true
|
jumped = true
|
||||||
} else if (currentPageIndex === pagesCount - 1) {
|
} else if (currentPageIndex === pagesCount - 1) {
|
||||||
showPage(1, { offsetDelayMs: _duration, animated: false })
|
await showPage(1, { animated: false })
|
||||||
jumped = true
|
jumped = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -250,54 +247,43 @@
|
|||||||
|
|
||||||
// Disable page change while animation is in progress
|
// Disable page change while animation is in progress
|
||||||
let disabled = false
|
let disabled = false
|
||||||
function safeChangePage(cb, options) {
|
async function changePage(updateStoreFn, options) {
|
||||||
const animated = get(options, 'animated', true)
|
|
||||||
if (disabled) return
|
if (disabled) return
|
||||||
cb()
|
|
||||||
disabled = true
|
disabled = true
|
||||||
setTimeout(() => {
|
|
||||||
disabled = false
|
updateStoreFn()
|
||||||
}, animated ? duration : 0)
|
await offsetPage(get(options, 'animated', true))
|
||||||
|
disabled = false
|
||||||
|
|
||||||
|
const jumped = await jumpIfNeeded()
|
||||||
|
!jumped && applyAutoplayIfNeeded(autoplay) // no need to wait it finishes
|
||||||
}
|
}
|
||||||
|
|
||||||
function showPage(pageIndex, options) {
|
async function showPage(pageIndex, options) {
|
||||||
const animated = get(options, 'animated', true)
|
await changePage(
|
||||||
const offsetDelayMs = get(options, 'offsetDelayMs', 0)
|
() => store.moveToPage({ pageIndex, pagesCount }),
|
||||||
safeChangePage(() => {
|
options
|
||||||
store.moveToPage({ pageIndex, pagesCount })
|
)
|
||||||
// delayed page transition, used for infinite autoplay to jump to real page
|
|
||||||
setTimeout(() => {
|
|
||||||
offsetPage(animated)
|
|
||||||
const jumped = jumpIfNeeded()
|
|
||||||
!jumped && applyAutoplayIfNeeded({ delayMs: _duration }) // while offset animation is in progress (delayMs = _duration ms) wait for it
|
|
||||||
}, offsetDelayMs)
|
|
||||||
}, { animated })
|
|
||||||
}
|
}
|
||||||
function showPrevPage(options) {
|
async function showPrevPage(options) {
|
||||||
const animated = get(options, 'animated', true)
|
await changePage(
|
||||||
safeChangePage(() => {
|
() => store.prev({ infinite, pagesCount }),
|
||||||
store.prev({ infinite, pagesCount })
|
options
|
||||||
offsetPage(animated)
|
)
|
||||||
const jumped = jumpIfNeeded()
|
|
||||||
!jumped && applyAutoplayIfNeeded({ delayMs: _duration })
|
|
||||||
}, { animated })
|
|
||||||
}
|
}
|
||||||
function showNextPage(options) {
|
async function showNextPage(options) {
|
||||||
const animated = get(options, 'animated', true)
|
await changePage(
|
||||||
safeChangePage(() => {
|
() => store.next({ infinite, pagesCount }),
|
||||||
store.next({ infinite, pagesCount })
|
options
|
||||||
offsetPage(animated)
|
)
|
||||||
const jumped = jumpIfNeeded()
|
|
||||||
!jumped && applyAutoplayIfNeeded({ delayMs: _duration })
|
|
||||||
}, { animated })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// gestures
|
// gestures
|
||||||
function handleSwipeStart() {
|
function handleSwipeStart() {
|
||||||
_duration = 0
|
_duration = 0
|
||||||
}
|
}
|
||||||
function handleThreshold(event) {
|
async function handleThreshold(event) {
|
||||||
directionFnDescription[event.detail.direction]()
|
await directionFnDescription[event.detail.direction]()
|
||||||
}
|
}
|
||||||
function handleSwipeMove(event) {
|
function handleSwipeMove(event) {
|
||||||
offset += event.detail.dx
|
offset += event.detail.dx
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { setIntervalImmediate } from './interval'
|
import { setIntervalImmediate } from './interval'
|
||||||
|
|
||||||
const STEP_MS = 35
|
const STEP_MS = 35
|
||||||
|
const MAX_VALUE = 1
|
||||||
|
|
||||||
export class ProgressManager {
|
export class ProgressManager {
|
||||||
#autoplayDuration
|
#autoplayDuration
|
||||||
#onProgressValueChange
|
#onProgressValueChange
|
||||||
@@ -17,25 +19,28 @@ export class ProgressManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
start(onFinish) {
|
start(onFinish) {
|
||||||
this.reset()
|
return new Promise((resolve) => {
|
||||||
|
this.reset()
|
||||||
|
|
||||||
const stepMs = Math.min(STEP_MS, this.#autoplayDuration)
|
const stepMs = Math.min(STEP_MS, this.#autoplayDuration)
|
||||||
let progress = -stepMs
|
let progress = -stepMs
|
||||||
|
|
||||||
this.#interval = setIntervalImmediate(() => {
|
this.#interval = setIntervalImmediate(async () => {
|
||||||
if (this.#paused) {
|
if (this.#paused) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
progress += stepMs
|
progress += stepMs
|
||||||
|
|
||||||
const value = progress / this.#autoplayDuration
|
const value = progress / this.#autoplayDuration
|
||||||
this.#onProgressValueChange(value)
|
this.#onProgressValueChange(value)
|
||||||
|
|
||||||
if (value > 1) {
|
if (value > MAX_VALUE) {
|
||||||
this.reset()
|
this.reset()
|
||||||
onFinish()
|
await onFinish()
|
||||||
}
|
resolve()
|
||||||
}, stepMs)
|
}
|
||||||
|
}, stepMs)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pause() {
|
pause() {
|
||||||
@@ -48,5 +53,6 @@ export class ProgressManager {
|
|||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
clearInterval(this.#interval)
|
clearInterval(this.#interval)
|
||||||
|
this.#onProgressValueChange(MAX_VALUE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,3 +2,11 @@ export const setIntervalImmediate = (fn, ms) => {
|
|||||||
fn();
|
fn();
|
||||||
return setInterval(fn, ms);
|
return setInterval(fn, ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const wait = (ms) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve()
|
||||||
|
}, ms)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
setIntervalImmediate,
|
setIntervalImmediate,
|
||||||
|
wait
|
||||||
} from './interval.js'
|
} from './interval.js'
|
||||||
|
|
||||||
describe('setIntervalImmediate', () => {
|
describe('setIntervalImmediate', () => {
|
||||||
@@ -28,3 +29,18 @@ describe('setIntervalImmediate', () => {
|
|||||||
expect(clearInterval).toHaveBeenCalledWith(interval)
|
expect(clearInterval).toHaveBeenCalledWith(interval)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('wait', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
})
|
||||||
|
|
||||||
|
it('wait n ms', () => {
|
||||||
|
const ms = 1000
|
||||||
|
|
||||||
|
wait(ms)
|
||||||
|
jest.runAllTimers()
|
||||||
|
|
||||||
|
expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), ms)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user