text: import {closest, distance} from 'shared/utils';
  import Sensor from '../Sensor';
  import {DragStartSensorEvent, DragMoveSensorEvent, DragStopSensorEvent} from '../SensorEvent';
  
  const onTouchStart = Symbol('onTouchStart');
  const onTouchEnd = Symbol('onTouchEnd');
  const onTouchMove = Symbol('onTouchMove');
  const startDrag = Symbol('startDrag');
  const onDistanceChange = Symbol('onDistanceChange');
  
  /**
   * Prevents scrolling when set to true
   * @var {Boolean} preventScrolling
   */
  let preventScrolling = false;
  
  // WebKit requires cancelable `touchmove` events to be added as early as possible
  window.addEventListener(
    'touchmove',
    (event) => {
      if (!preventScrolling) {
        return;
      }
  
      // Prevent scrolling
      event.preventDefault();
    },
    {passive: false},
  );
  
  /**
   * This sensor picks up native browser touch events and dictates drag operations
   * @class TouchSensor
   * @module TouchSensor
   * @extends Sensor
   */
  export default class TouchSensor extends Sensor {
    /**
     * TouchSensor constructor.
     * @constructs TouchSensor
     * @param {HTMLElement[]|NodeList|HTMLElement} containers - Containers
     * @param {Object} options - Options
     */
    constructor(containers = [], options = {}) {
      super(containers, options);
  
      /**
       * Closest scrollable container so accidental scroll can cancel long touch
       * @property currentScrollableParent
       * @type {HTMLElement}
       */
      this.currentScrollableParent = null;
  
      /**
       * TimeoutID for long touch
       * @property tapTimeout
       * @type {Number}
       */
      this.tapTimeout = null;
  
      /**
       * touchMoved indicates if touch has moved during tapTimeout
       * @property touchMoved
       * @type {Boolean}
       */
      this.touchMoved = false;
  
      this[onTouchStart] = this[onTouchStart].bind(this);
      this[onTouchEnd] = this[onTouchEnd].bind(this);
      this[onTouchMove] = this[onTouchMove].bind(this);
      this[startDrag] = this[startDrag].bind(this);
      this[onDistanceChange] = this[onDistanceChange].bind(this);
    }
  
    /**
     * Attaches sensors event listeners to the DOM
     */
    attach() {
      document.addEventListener('touchstart', this[onTouchStart]);
    }
  
    /**
     * Detaches sensors event listeners to the DOM
     */
    detach() {
      document.removeEventListener('touchstart', this[onTouchStart]);
    }
  
    /**
     * Touch start handler
     * @private
     * @param {Event} event - Touch start event
     */
    [onTouchStart](event) {
      const container = closest(event.target, this.containers);
  
      if (!container) {
        return;
      }
  
      this.startEvent = event;
  
      document.addEventListener('touchmove', this[onTouchMove]);
      document.addEventListener('touchend', this[onTouchEnd]);
      document.addEventListener('touchcancel', this[onTouchEnd]);
      document.addEventListener('touchmove', this[onDistanceChange]);
      container.addEventListener('contextmenu', onContextMenu);
  
      if (this.options.distance) {
        preventScrolling = true;
      }
  
      this.currentContainer = container;
      this.tapTimeout = setTimeout(() => {
        this.delayOver = true;
        if (this.touchMoved || this.distance < this.options.distance) {
          return;
        }
        this[startDrag]();
      }, this.options.delay);
    }
  
    /**
     * Start the drag
     * @private
     */
    [startDrag]() {
      const startEvent = this.startEvent;
      const container = this.currentContainer;
      const touch = startEvent.touches[0] || startEvent.changedTouches[0];
  
      const dragStartEvent = new DragStartSensorEvent({
        clientX: touch.pageX,
        clientY: touch.pageY,
        target: startEvent.target,
        container,
        originalEvent: startEvent,
      });
  
      this.trigger(this.currentContainer, dragStartEvent);
  
      this.dragging = !dragStartEvent.canceled();
      preventScrolling = this.dragging;
    }
  
    /**
     * Detect change in distance
     * @private
     * @param {Event} event - Touch move event
     */
    [onDistanceChange](event) {
      if (this.dragging || !this.options.distance) {
        return;
      }
  
      const tap = this.startEvent.touches[0] || this.startEvent.changedTouches[0];
      const touch = event.touches[0] || event.changedTouches[0];
  
      this.distance = distance(tap.pageX, tap.pageY, touch.pageX, touch.pageY);
  
      if (this.delayOver && this.distance >= this.options.distance) {
        this[startDrag]();
      }
    }
  
    /**
     * Touch move handler
     * @private
     * @param {Event} event - Touch move event
     */
    [onTouchMove](event) {
      this.touchMoved = true;
  
      if (!this.dragging) {
        return;
      }
  
      const touch = event.touches[0] || event.changedTouches[0];
      const target = document.elementFromPoint(touch.pageX - window.scrollX, touch.pageY - window.scrollY);
  
      const dragMoveEvent = new DragMoveSensorEvent({
        clientX: touch.pageX,
        clientY: touch.pageY,
        target,
        container: this.currentContainer,
        originalEvent: event,
      });
  
      this.trigger(this.currentContainer, dragMoveEvent);
    }
  
    /**
     * Touch end handler
     * @private
     * @param {Event} event - Touch end event
     */
    [onTouchEnd](event) {
      this.touchMoved = false;
      preventScrolling = false;
  
      document.removeEventListener('touchend', this[onTouchEnd]);
      document.removeEventListener('touchcancel', this[onTouchEnd]);
      document.removeEventListener('touchmove', this[onTouchMove]);
      document.removeEventListener('touchmove', this[onDistanceChange]);
  
      if (this.currentContainer) {
        this.currentContainer.removeEventListener('contextmenu', onContextMenu);
      }
  
      clearTimeout(this.tapTimeout);
  
      if (!this.dragging) {
        return;
      }
  
      const touch = event.touches[0] || event.changedTouches[0];
      const target = document.elementFromPoint(touch.pageX - window.scrollX, touch.pageY - window.scrollY);
  
      event.preventDefault();
  
      const dragStopEvent = new DragStopSensorEvent({
        clientX: touch.pageX,
        clientY: touch.pageY,
        target,
        container: this.currentContainer,
        originalEvent: event,
      });
  
      this.trigger(this.currentContainer, dragStopEvent);
  
      this.currentContainer = null;
      this.dragging = false;
      this.distance = 0;
      this.delayOver = false;
      this.startEvent = null;
    }
  }
  
  function onContextMenu(event) {
    event.preventDefault();
    event.stopPropagation();
  }
  