time-format-js
Version:
Localized time formatting library.
533 lines (476 loc) • 13.5 kB
JavaScript
import {
hasPerspective,
hasTransition,
hasTransform,
hasTouch,
style,
offset,
addEvent,
removeEvent,
getRect,
preventDefaultException
} from '../util/dom'
import { extend, isUndef } from '../util/lang'
const DEFAULT_OPTIONS = {
startX: 0,
startY: 0,
scrollX: false,
scrollY: true,
freeScroll: false,
directionLockThreshold: 5,
eventPassthrough: '',
click: false,
tap: false,
/**
* support any side
* bounce: {
* top: true,
* bottom: true,
* left: true,
* right: true
* }
*/
bounce: true,
bounceTime: 800,
momentum: true,
momentumLimitTime: 300,
momentumLimitDistance: 15,
swipeTime: 2500,
swipeBounceTime: 500,
deceleration: 0.0015,
flickLimitTime: 200,
flickLimitDistance: 100,
resizePolling: 60,
probeType: 0,
preventDefault: true,
preventDefaultException: {
tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT)$/
},
HWCompositing: true,
useTransition: true,
useTransform: true,
bindToWrapper: false,
disableMouse: hasTouch,
disableTouch: !hasTouch,
observeDOM: true,
autoBlur: true,
/**
* for picker
* wheel: {
* selectedIndex: 0,
* rotate: 25,
* adjustTime: 400
* wheelWrapperClass: 'wheel-scroll',
* wheelItemClass: 'wheel-item'
* }
*/
wheel: false,
/**
* for slide
* snap: {
* loop: false,
* el: domEl,
* threshold: 0.1,
* stepX: 100,
* stepY: 100,
* speed: 400,
* easing: {
* style: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
* fn: function (t) {
* return t * (2 - t)
* }
* }
* listenFlick: true
* }
*/
snap: false,
/**
* for scrollbar
* scrollbar: {
* fade: true,
* interactive: false
* }
*/
scrollbar: false,
/**
* for pull down and refresh
* pullDownRefresh: {
* threshold: 50,
* stop: 20
* }
*/
pullDownRefresh: false,
/**
* for pull up and load
* pullUpLoad: {
* threshold: 50
* }
*/
pullUpLoad: false,
/**
* for mouse wheel
* mouseWheel: {
* speed: 20,
* invert: false,
* easeTime: 300
* }
*/
mouseWheel: false,
stopPropagation: false,
/**
* for zoom
* zoom: {
* start: 1,
* min: 1,
* max: 4
* }
*/
zoom: false,
/**
* for infinity
* infinity: {
* render(item, div) {
* },
* createTombstone() {
* },
* fetch(count) {
* }
* }
*/
infinity: false,
/**
* for double click
* dblclick: {
* delay: 300
* }
*/
dblclick: false
}
export function initMixin(BScroll) {
BScroll.prototype._init = function (el, options) {
this._handleOptions(options)
// init private custom events
this._events = {}
this.x = 0
this.y = 0
this.directionX = 0
this.directionY = 0
this.setScale(1)
this._addDOMEvents()
this._initExtFeatures()
this._watchTransition()
if (this.options.observeDOM) {
this._initDOMObserver()
}
if (this.options.autoBlur) {
this._handleAutoBlur()
}
this.refresh()
if (!this.options.snap) {
this.scrollTo(this.options.startX, this.options.startY)
}
this.enable()
}
BScroll.prototype.setScale = function (scale) {
this.lastScale = isUndef(this.scale) ? scale : this.scale
this.scale = scale
}
BScroll.prototype._handleOptions = function (options) {
this.options = extend({}, DEFAULT_OPTIONS, options)
this.translateZ = this.options.HWCompositing && hasPerspective ? ' translateZ(0)' : ''
this.options.useTransition = this.options.useTransition && hasTransition
this.options.useTransform = this.options.useTransform && hasTransform
this.options.preventDefault = !this.options.eventPassthrough && this.options.preventDefault
// If you want eventPassthrough I have to lock one of the axes
this.options.scrollX = this.options.eventPassthrough === 'horizontal' ? false : this.options.scrollX
this.options.scrollY = this.options.eventPassthrough === 'vertical' ? false : this.options.scrollY
// With eventPassthrough we also need lockDirection mechanism
this.options.freeScroll = this.options.freeScroll && !this.options.eventPassthrough
this.options.directionLockThreshold = this.options.eventPassthrough ? 0 : this.options.directionLockThreshold
if (this.options.tap === true) {
this.options.tap = 'tap'
}
}
BScroll.prototype._addDOMEvents = function () {
let eventOperation = addEvent
this._handleDOMEvents(eventOperation)
}
BScroll.prototype._removeDOMEvents = function () {
let eventOperation = removeEvent
this._handleDOMEvents(eventOperation)
}
BScroll.prototype._handleDOMEvents = function (eventOperation) {
let target = this.options.bindToWrapper ? this.wrapper : window
eventOperation(window, 'orientationchange', this)
eventOperation(window, 'resize', this)
if (this.options.click) {
eventOperation(this.wrapper, 'click', this, true)
}
if (!this.options.disableMouse) {
eventOperation(this.wrapper, 'mousedown', this)
eventOperation(target, 'mousemove', this)
eventOperation(target, 'mousecancel', this)
eventOperation(target, 'mouseup', this)
}
if (hasTouch && !this.options.disableTouch) {
eventOperation(this.wrapper, 'touchstart', this)
eventOperation(target, 'touchmove', this)
eventOperation(target, 'touchcancel', this)
eventOperation(target, 'touchend', this)
}
eventOperation(this.scroller, style.transitionEnd, this)
}
BScroll.prototype._initExtFeatures = function () {
if (this.options.snap) {
this._initSnap()
}
if (this.options.scrollbar) {
this._initScrollbar()
}
if (this.options.pullUpLoad) {
this._initPullUp()
}
if (this.options.pullDownRefresh) {
this._initPullDown()
}
if (this.options.wheel) {
this._initWheel()
}
if (this.options.mouseWheel) {
this._initMouseWheel()
}
if (this.options.zoom) {
this._initZoom()
}
if (this.options.infinity) {
this._initInfinite()
}
}
BScroll.prototype._watchTransition = function () {
if (typeof Object.defineProperty !== 'function') {
return
}
let me = this
let isInTransition = false
let key = this.useTransition ? 'isInTransition' : 'isAnimating'
Object.defineProperty(this, key, {
get() {
return isInTransition
},
set(newVal) {
isInTransition = newVal
// fix issue #359
let el = me.scroller.children.length ? me.scroller.children : [me.scroller]
let pointerEvents = (isInTransition && !me.pulling) ? 'none' : 'auto'
for (let i = 0; i < el.length; i++) {
el[i].style.pointerEvents = pointerEvents
}
}
})
}
BScroll.prototype._handleAutoBlur = function () {
this.on('scrollStart', () => {
let activeElement = document.activeElement
if (activeElement && (activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA')) {
activeElement.blur()
}
})
}
BScroll.prototype._initDOMObserver = function () {
if (typeof MutationObserver !== 'undefined') {
let timer
let observer = new MutationObserver((mutations) => {
// don't do any refresh during the transition, or outside of the boundaries
if (this._shouldNotRefresh()) {
return
}
let immediateRefresh = false
let deferredRefresh = false
for (let i = 0; i < mutations.length; i++) {
const mutation = mutations[i]
if (mutation.type !== 'attributes') {
immediateRefresh = true
break
} else {
if (mutation.target !== this.scroller) {
deferredRefresh = true
break
}
}
}
if (immediateRefresh) {
this.refresh()
} else if (deferredRefresh) {
// attributes changes too often
clearTimeout(timer)
timer = setTimeout(() => {
if (!this._shouldNotRefresh()) {
this.refresh()
}
}, 60)
}
})
const config = {
attributes: true,
childList: true,
subtree: true
}
observer.observe(this.scroller, config)
this.on('destroy', () => {
observer.disconnect()
})
} else {
this._checkDOMUpdate()
}
}
BScroll.prototype._shouldNotRefresh = function () {
let outsideBoundaries = this.x > this.minScrollX || this.x < this.maxScrollX || this.y > this.minScrollY || this.y < this.maxScrollY
return this.isInTransition || this.stopFromTransition || outsideBoundaries
}
BScroll.prototype._checkDOMUpdate = function () {
let scrollerRect = getRect(this.scroller)
let oldWidth = scrollerRect.width
let oldHeight = scrollerRect.height
function check() {
if (this.destroyed) {
return
}
scrollerRect = getRect(this.scroller)
let newWidth = scrollerRect.width
let newHeight = scrollerRect.height
if (oldWidth !== newWidth || oldHeight !== newHeight) {
this.refresh()
}
oldWidth = newWidth
oldHeight = newHeight
next.call(this)
}
function next() {
setTimeout(() => {
check.call(this)
}, 1000)
}
next.call(this)
}
BScroll.prototype.handleEvent = function (e) {
switch (e.type) {
case 'touchstart':
case 'mousedown':
this._start(e)
if (this.options.zoom && e.touches && e.touches.length > 1) {
this._zoomStart(e)
}
break
case 'touchmove':
case 'mousemove':
if (this.options.zoom && e.touches && e.touches.length > 1) {
this._zoom(e)
} else {
this._move(e)
}
break
case 'touchend':
case 'mouseup':
case 'touchcancel':
case 'mousecancel':
if (this.scaled) {
this._zoomEnd(e)
} else {
this._end(e)
}
break
case 'orientationchange':
case 'resize':
this._resize()
break
case 'transitionend':
case 'webkitTransitionEnd':
case 'oTransitionEnd':
case 'MSTransitionEnd':
this._transitionEnd(e)
break
case 'click':
if (this.enabled && !e._constructed) {
if (!preventDefaultException(e.target, this.options.preventDefaultException)) {
e.preventDefault()
e.stopPropagation()
}
}
break
case 'wheel':
case 'DOMMouseScroll':
case 'mousewheel':
this._onMouseWheel(e)
break
}
}
BScroll.prototype.refresh = function () {
const isWrapperStatic = window.getComputedStyle(this.wrapper, null).position === 'static'
let wrapperRect = getRect(this.wrapper)
this.wrapperWidth = wrapperRect.width
this.wrapperHeight = wrapperRect.height
let scrollerRect = getRect(this.scroller)
this.scrollerWidth = Math.round(scrollerRect.width * this.scale)
this.scrollerHeight = Math.round(scrollerRect.height * this.scale)
this.relativeX = scrollerRect.left
this.relativeY = scrollerRect.top
if (isWrapperStatic) {
this.relativeX -= wrapperRect.left
this.relativeY -= wrapperRect.top
}
this.minScrollX = 0
this.minScrollY = 0
const wheel = this.options.wheel
if (wheel) {
this.items = this.scroller.children
this.options.itemHeight = this.itemHeight = this.items.length ? this.scrollerHeight / this.items.length : 0
if (this.selectedIndex === undefined) {
this.selectedIndex = wheel.selectedIndex || 0
}
this.options.startY = -this.selectedIndex * this.itemHeight
this.maxScrollX = 0
this.maxScrollY = -this.itemHeight * (this.items.length - 1)
} else {
this.maxScrollX = this.wrapperWidth - this.scrollerWidth
if (!this.options.infinity) {
this.maxScrollY = this.wrapperHeight - this.scrollerHeight
}
if (this.maxScrollX < 0) {
this.maxScrollX -= this.relativeX
this.minScrollX = -this.relativeX
} else if (this.scale > 1) {
this.maxScrollX = (this.maxScrollX / 2 - this.relativeX)
this.minScrollX = this.maxScrollX
}
if (this.maxScrollY < 0) {
this.maxScrollY -= this.relativeY
this.minScrollY = -this.relativeY
} else if (this.scale > 1) {
this.maxScrollY = (this.maxScrollY / 2 - this.relativeY)
this.minScrollY = this.maxScrollY
}
}
this.hasHorizontalScroll = this.options.scrollX && this.maxScrollX < this.minScrollX
this.hasVerticalScroll = this.options.scrollY && this.maxScrollY < this.minScrollY
if (!this.hasHorizontalScroll) {
this.maxScrollX = this.minScrollX
this.scrollerWidth = this.wrapperWidth
}
if (!this.hasVerticalScroll) {
this.maxScrollY = this.minScrollY
this.scrollerHeight = this.wrapperHeight
}
this.endTime = 0
this.directionX = 0
this.directionY = 0
this.wrapperOffset = offset(this.wrapper)
this.trigger('refresh')
!this.scaled && this.resetPosition()
}
BScroll.prototype.enable = function () {
this.enabled = true
}
BScroll.prototype.disable = function () {
this.enabled = false
}
}