Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
react-fluckity / fluckity / drag.js
Size: Mime:
// 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