Repository URL to install this package:
|
Version:
1.0.4 ▾
|
// const Flickity = require('./flickity')
const utils = require('./utils')
const Unidragger = require('./Unidragger')
// ----- defaults ----- //
function fluckDrag(Flickity) {
utils.extend(Flickity.defaults, {
draggable: true,
dragThreshold: 3,
})
// ----- create ----- //
Flickity.createMethods.push('_createDrag')
// -------------------------- drag prototype -------------------------- //
const IS_BROWSER = typeof window === 'object'
var proto = Flickity.prototype
utils.extend(proto, Unidragger.prototype)
proto._touchActionValue = 'pan-y'
// -------------------------- -------------------------- //
var isTouch = IS_BROWSER && 'createTouch' in document
var isTouchmoveScrollCanceled = false
proto._createDrag = function() {
this.on('activate', this.bindDrag)
this.on('uiChange', this._uiChangeDrag)
this.on('childUIPointerDown', this._childUIPointerDownDrag)
this.on('deactivate', this.unbindDrag)
// HACK - add seemingly innocuous handler to fix iOS 10 scroll behavior
// #457, RubaXa/Sortable#973
if (isTouch && !isTouchmoveScrollCanceled) {
window.addEventListener('touchmove', function() {})
isTouchmoveScrollCanceled = true
}
}
proto.bindDrag = function() {
if (!this.options.draggable || this.isDragBound) {
return
}
this.element.classList.add('is-draggable')
this.handles = [this.viewport]
this.bindHandles()
this.isDragBound = true
}
proto.unbindDrag = function() {
if (!this.isDragBound) {
return
}
this.element.classList.remove('is-draggable')
this.unbindHandles()
delete this.isDragBound
}
proto._uiChangeDrag = function() {
delete this.isFreeScrolling
}
proto._childUIPointerDownDrag = function(event) {
event.preventDefault()
this.pointerDownFocus(event)
}
// -------------------------- pointer events -------------------------- //
// nodes that have text fields
var cursorNodes = {
TEXTAREA: true,
INPUT: true,
OPTION: true
}
// input types that do not have text fields
var clickTypes = {
radio: true,
checkbox: true,
button: true,
submit: true,
image: true,
file: true
}
proto.pointerDown = function(event, pointer) {
// dismiss inputs with text fields. #403, #404
var isCursorInput =
cursorNodes[event.target.nodeName] && !clickTypes[event.target.type]
if (isCursorInput) {
// reset pointerDown logic
this.isPointerDown = false
delete this.pointerIdentifier
return
}
this._dragPointerDown(event, pointer)
// kludge to blur focused inputs in dragger
var focused = document.activeElement
if (
focused &&
focused.blur &&
focused != this.element &&
// do not blur body for IE9 & 10, #117
focused != document.body
) {
focused.blur()
}
this.pointerDownFocus(event)
// stop if it was moving
this.dragX = this.x
this.viewport.classList.add('is-pointer-down')
// bind move and end events
this._bindPostStartEvents(event)
// track scrolling
this.pointerDownScroll = getScrollPosition()
window.addEventListener('scroll', this)
this.dispatchEvent('pointerDown', event, [pointer])
}
proto.pointerDownFocus = function(event) {
// focus element, if not touch, and its not an input or select
var canPointerDown = getCanPointerDown(event)
if (!this.options.accessibility || canPointerDown) {
return
}
var prevScrollY = window.pageYOffset
this.element.focus()
// hack to fix scroll jump after focus, #76
if (window.pageYOffset != prevScrollY) {
window.scrollTo(window.pageXOffset, prevScrollY)
}
}
var focusNodes = {
INPUT: true,
SELECT: true
}
function getCanPointerDown(event) {
var isTouchStart = event.type == 'touchstart'
var isTouchPointer = event.pointerType == 'touch'
var isFocusNode = focusNodes[event.target.nodeName]
return isTouchStart || isTouchPointer || isFocusNode
}
proto.canPreventDefaultOnPointerDown = function(event) {
// prevent default, unless touchstart or input
var canPointerDown = getCanPointerDown(event)
return !canPointerDown
}
// ----- move ----- //
proto.hasDragStarted = function(moveVector) {
return Math.abs(moveVector.x) > this.options.dragThreshold
}
// ----- up ----- //
proto.pointerUp = function(event, pointer) {
delete this.isTouchScrolling
this.viewport.classList.remove('is-pointer-down')
this.dispatchEvent('pointerUp', event, [pointer])
this._dragPointerUp(event, pointer)
}
proto.pointerDone = function() {
window.removeEventListener('scroll', this)
delete this.pointerDownScroll
}
// -------------------------- dragging -------------------------- //
proto.dragStart = function(event, pointer) {
this.dragStartPosition = this.x
this.startAnimation()
window.removeEventListener('scroll', this)
this.dispatchEvent('dragStart', event, [pointer])
}
proto.pointerMove = function(event, pointer) {
var moveVector = this._dragPointerMove(event, pointer)
this.dispatchEvent('pointerMove', event, [pointer, moveVector])
this._dragMove(event, pointer, moveVector)
}
proto.dragMove = function(event, pointer, moveVector) {
event.preventDefault()
this.previousDragX = this.dragX
// reverse if right-to-left
var direction = this.options.rightToLeft ? -1 : 1
var dragX = this.dragStartPosition + moveVector.x * direction
if (!this.options.wrapAround && this.slides.length) {
// slow drag
var originBound = Math.max(-this.slides[0].target, this.dragStartPosition)
dragX = dragX > originBound ? (dragX + originBound) * 0.5 : dragX
var endBound = Math.min(-this.getLastSlide().target, this.dragStartPosition)
dragX = dragX < endBound ? (dragX + endBound) * 0.5 : dragX
}
this.dragX = dragX
this.dragMoveTime = new Date()
this.dispatchEvent('dragMove', event, [pointer, moveVector])
}
proto.dragEnd = function(event, pointer) {
if (this.options.freeScroll) {
this.isFreeScrolling = true
}
// set selectedIndex based on where flick will end up
var index = this.dragEndRestingSelect()
if (this.options.freeScroll && !this.options.wrapAround) {
// if free-scroll & not wrap around
// do not free-scroll if going outside of bounding slides
// so bounding slides can attract slider, and keep it in bounds
var restingX = this.getRestingPosition()
this.isFreeScrolling =
-restingX > this.slides[0].target &&
-restingX < this.getLastSlide().target
} else if (!this.options.freeScroll && index == this.selectedIndex) {
// boost selection if selected index has not changed
index += this.dragEndBoostSelect()
}
delete this.previousDragX
// apply selection
// TODO refactor this, selecting here feels weird
// HACK, set flag so dragging stays in correct direction
this.isDragSelect = this.options.wrapAround
this.select(index)
delete this.isDragSelect
this.dispatchEvent('dragEnd', event, [pointer])
}
proto.dragEndRestingSelect = function() {
var restingX = this.getRestingPosition()
// how far away from selected slide
var distance = Math.abs(this.getSlideDistance(-restingX, this.selectedIndex))
// get closet resting going up and going down
var positiveResting = this._getClosestResting(restingX, distance, 1)
var negativeResting = this._getClosestResting(restingX, distance, -1)
// use closer resting for wrap-around
var index =
positiveResting.distance < negativeResting.distance
? positiveResting.index
: negativeResting.index
return index
}
/**
* given resting X and distance to selected cell
* get the distance and index of the closest cell
* @param {Number} restingX - estimated post-flick resting position
* @param {Number} distance - distance to selected cell
* @param {Integer} increment - +1 or -1, going up or down
* @returns {Object} - { distance: {Number}, index: {Integer} }
*/
proto._getClosestResting = function(restingX, distance, increment) {
var index = this.selectedIndex
var minDistance = Infinity
var condition =
this.options.contain && !this.options.wrapAround
? // if contain, keep going if distance is equal to minDistance
function(d, md) {
return d <= md
}
: function(d, md) {
return d < md
}
while (condition(distance, minDistance)) {
// measure distance to next cell
index += increment
minDistance = distance
distance = this.getSlideDistance(-restingX, index)
if (distance === null) {
break
}
distance = Math.abs(distance)
}
return {
distance: minDistance,
// selected was previous index
index: index - increment
}
}
/**
* measure distance between x and a slide target
* @param {Number} x
* @param {Integer} index - slide index
*/
proto.getSlideDistance = function(x, index) {
var len = this.slides.length
// wrap around if at least 2 slides
var isWrapAround = this.options.wrapAround && len > 1
var slideIndex = isWrapAround ? utils.modulo(index, len) : index
var slide = this.slides[slideIndex]
if (!slide) {
return null
}
// add distance for wrap-around slides
var wrap = isWrapAround ? this.slideableWidth * Math.floor(index / len) : 0
return x - (slide.target + wrap)
}
proto.dragEndBoostSelect = function() {
// do not boost if no previousDragX or dragMoveTime
if (
this.previousDragX === undefined ||
!this.dragMoveTime ||
// or if drag was held for 100 ms
new Date() - this.dragMoveTime > 100
) {
return 0
}
var distance = this.getSlideDistance(-this.dragX, this.selectedIndex)
var delta = this.previousDragX - this.dragX
if (distance > 0 && delta > 0) {
// boost to next if moving towards the right, and positive velocity
return 1
} else if (distance < 0 && delta < 0) {
// boost to previous if moving towards the left, and negative velocity
return -1
}
return 0
}
// ----- staticClick ----- //
proto.staticClick = function(event, pointer) {
// get clickedCell, if cell was clicked
var clickedCell = this.getParentCell(event.target)
var cellElem = clickedCell && clickedCell.element
var cellIndex = clickedCell && this.cells.indexOf(clickedCell)
this.dispatchEvent('staticClick', event, [pointer, cellElem, cellIndex])
}
// ----- scroll ----- //
proto.onscroll = function() {
var scroll = getScrollPosition()
var scrollMoveX = this.pointerDownScroll.x - scroll.x
var scrollMoveY = this.pointerDownScroll.y - scroll.y
// cancel click/tap if scroll is too much
if (Math.abs(scrollMoveX) > 3 || Math.abs(scrollMoveY) > 3) {
this._pointerDone()
}
}
}
// ----- utils ----- //
function getScrollPosition() {
return {
x: window.pageXOffset,
y: window.pageYOffset
}
}
fluckDrag.flickTheBean = Flickity => {
// func reassign madness
// fluckDrag = fluckDrag(Flickity)
return fluckDrag(Flickity)
}
module.exports = fluckDrag