|
@@ -1,21 +1,7 @@
|
|
|
import { default as EmitterMixin } from '../common/EmitterMixin'
|
|
import { default as EmitterMixin } from '../common/EmitterMixin'
|
|
|
import { default as PointerDragListener, PointerDragEvent } from './PointerDragListener'
|
|
import { default as PointerDragListener, PointerDragEvent } from './PointerDragListener'
|
|
|
import { preventSelection, allowSelection, preventContextMenu, allowContextMenu } from '../util/misc'
|
|
import { preventSelection, allowSelection, preventContextMenu, allowContextMenu } from '../util/misc'
|
|
|
-
|
|
|
|
|
-export interface IntentfulDragOptions {
|
|
|
|
|
- containerEl: HTMLElement
|
|
|
|
|
- selector?: string
|
|
|
|
|
- ignoreMove?: any // set to false if you don't need to track moves, for performance reasons
|
|
|
|
|
- touchMinDistance?: number
|
|
|
|
|
- mouseMinDistance?: number
|
|
|
|
|
- touchDelay?: number | ((ev: PointerDragEvent) => number)
|
|
|
|
|
- mouseDelay?: number | ((ev: PointerDragEvent) => number)
|
|
|
|
|
-
|
|
|
|
|
- // if set to false, if there's a touch scroll after pointdown but before the drag begins,
|
|
|
|
|
- // it won't be considered a drag and dragstart/dragmove/dragend won't be fired.
|
|
|
|
|
- // if the drag initiates and this value is set to false, touch dragging will be prevented.
|
|
|
|
|
- touchScrollAllowed?: boolean
|
|
|
|
|
-}
|
|
|
|
|
|
|
+import DragMirror from './DragMirror'
|
|
|
|
|
|
|
|
/*
|
|
/*
|
|
|
fires:
|
|
fires:
|
|
@@ -23,35 +9,37 @@ fires:
|
|
|
- dragstart
|
|
- dragstart
|
|
|
- dragmove
|
|
- dragmove
|
|
|
- pointermove
|
|
- pointermove
|
|
|
-- dragend
|
|
|
|
|
-- pointerup
|
|
|
|
|
|
|
+- pointerup (always happens before dragend!)
|
|
|
|
|
+- dragend (happens after any revert animation)
|
|
|
*/
|
|
*/
|
|
|
export default class IntentfulDragListener {
|
|
export default class IntentfulDragListener {
|
|
|
|
|
|
|
|
pointerListener: PointerDragListener
|
|
pointerListener: PointerDragListener
|
|
|
emitter: EmitterMixin
|
|
emitter: EmitterMixin
|
|
|
|
|
+ dragMirror: DragMirror // TODO: move out of here?
|
|
|
|
|
|
|
|
- options: IntentfulDragOptions
|
|
|
|
|
|
|
+ // options
|
|
|
|
|
+ delay: number
|
|
|
|
|
+ minDistance: number = 0
|
|
|
|
|
+ touchScrollAllowed: boolean = true
|
|
|
|
|
|
|
|
- isDragging: boolean = false // is it INTENTFULLY dragging?
|
|
|
|
|
|
|
+ isWatchingPointer: boolean = false
|
|
|
|
|
+ isDragging: boolean = false // is it INTENTFULLY dragging? lasts until after revert animation // TODO: exclude revert anim?
|
|
|
isDelayEnded: boolean = false
|
|
isDelayEnded: boolean = false
|
|
|
isDistanceSurpassed: boolean = false
|
|
isDistanceSurpassed: boolean = false
|
|
|
|
|
|
|
|
- delay: number
|
|
|
|
|
delayTimeoutId: number
|
|
delayTimeoutId: number
|
|
|
-
|
|
|
|
|
- minDistance: number
|
|
|
|
|
origX: number
|
|
origX: number
|
|
|
origY: number
|
|
origY: number
|
|
|
|
|
|
|
|
- constructor(options: IntentfulDragOptions) {
|
|
|
|
|
- this.options = options
|
|
|
|
|
- this.pointerListener = new PointerDragListener(options.containerEl, options.selector, options.ignoreMove)
|
|
|
|
|
|
|
+ constructor(containerEl: HTMLElement) {
|
|
|
this.emitter = new EmitterMixin()
|
|
this.emitter = new EmitterMixin()
|
|
|
|
|
+ this.dragMirror = new DragMirror(this)
|
|
|
|
|
|
|
|
- this.pointerListener.on('pointerdown', this.onPointerDown)
|
|
|
|
|
- this.pointerListener.on('pointermove', this.onPointerMove)
|
|
|
|
|
- this.pointerListener.on('pointerup', this.onPointerUp)
|
|
|
|
|
|
|
+ let pointerListener = this.pointerListener = new PointerDragListener(containerEl)
|
|
|
|
|
+ pointerListener.on('pointerdown', this.onPointerDown)
|
|
|
|
|
+ pointerListener.on('pointermove', this.onPointerMove)
|
|
|
|
|
+ pointerListener.on('pointerup', this.onPointerUp)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
destroy() {
|
|
destroy() {
|
|
@@ -63,64 +51,72 @@ export default class IntentfulDragListener {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
onPointerDown = (ev: PointerDragEvent) => {
|
|
onPointerDown = (ev: PointerDragEvent) => {
|
|
|
- this.emitter.trigger('pointerdown', ev)
|
|
|
|
|
-
|
|
|
|
|
- preventSelection(document.body)
|
|
|
|
|
- preventContextMenu(document.body)
|
|
|
|
|
|
|
+ if (!this.isDragging) { // mainly so new drag doesn't happen while revert animation is going
|
|
|
|
|
+ this.isWatchingPointer = true
|
|
|
|
|
+ this.isDelayEnded = false
|
|
|
|
|
+ this.isDistanceSurpassed = false
|
|
|
|
|
|
|
|
- let minDistance = this.options[ev.isTouch ? 'touchMinDistance' : 'mouseMinDistance']
|
|
|
|
|
- let delay = this.options[ev.isTouch ? 'touchDelay' : 'mouseDelay']
|
|
|
|
|
|
|
+ preventSelection(document.body)
|
|
|
|
|
+ preventContextMenu(document.body)
|
|
|
|
|
|
|
|
- this.minDistance = minDistance || 0
|
|
|
|
|
- this.delay = typeof delay === 'function' ? (delay as any)(ev) : delay
|
|
|
|
|
|
|
+ this.origX = ev.pageX
|
|
|
|
|
+ this.origY = ev.pageY
|
|
|
|
|
|
|
|
- this.origX = ev.pageX
|
|
|
|
|
- this.origY = ev.pageY
|
|
|
|
|
|
|
+ this.emitter.trigger('pointerdown', ev)
|
|
|
|
|
|
|
|
- this.isDelayEnded = false
|
|
|
|
|
- this.isDistanceSurpassed = false
|
|
|
|
|
|
|
+ // if moving is being ignored, don't fire any initial drag events
|
|
|
|
|
+ if (!this.pointerListener.ignoreMove) {
|
|
|
|
|
+ // actions that could fire dragstart...
|
|
|
|
|
|
|
|
- this.startDelay(ev)
|
|
|
|
|
|
|
+ this.startDelay(ev)
|
|
|
|
|
|
|
|
- if (!this.minDistance) {
|
|
|
|
|
- this.handleDistanceSurpassed(ev)
|
|
|
|
|
|
|
+ if (!this.minDistance) {
|
|
|
|
|
+ this.handleDistanceSurpassed(ev)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
onPointerMove = (ev: PointerDragEvent) => {
|
|
onPointerMove = (ev: PointerDragEvent) => {
|
|
|
- this.emitter.trigger('pointermove', ev)
|
|
|
|
|
-
|
|
|
|
|
- if (!this.isDistanceSurpassed) {
|
|
|
|
|
- let dx = ev.pageX - this.origX
|
|
|
|
|
- let dy = ev.pageY - this.origY
|
|
|
|
|
- let minDistance = this.minDistance
|
|
|
|
|
- let distanceSq // current distance from the origin, squared
|
|
|
|
|
-
|
|
|
|
|
- distanceSq = dx * dx + dy * dy
|
|
|
|
|
- if (distanceSq >= minDistance * minDistance) { // use pythagorean theorem
|
|
|
|
|
- this.handleDistanceSurpassed(ev)
|
|
|
|
|
|
|
+ if (this.isWatchingPointer) { // if false, still waiting for previous drag's revert
|
|
|
|
|
+ this.emitter.trigger('pointermove', ev)
|
|
|
|
|
+
|
|
|
|
|
+ if (!this.isDistanceSurpassed) {
|
|
|
|
|
+ let dx = ev.pageX - this.origX
|
|
|
|
|
+ let dy = ev.pageY - this.origY
|
|
|
|
|
+ let minDistance = this.minDistance
|
|
|
|
|
+ let distanceSq // current distance from the origin, squared
|
|
|
|
|
+
|
|
|
|
|
+ distanceSq = dx * dx + dy * dy
|
|
|
|
|
+ if (distanceSq >= minDistance * minDistance) { // use pythagorean theorem
|
|
|
|
|
+ this.handleDistanceSurpassed(ev)
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
- if (this.isDragging) {
|
|
|
|
|
- this.emitter.trigger('dragmove', ev)
|
|
|
|
|
|
|
+ if (this.isDragging) {
|
|
|
|
|
+ this.emitter.trigger('dragmove', ev)
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
onPointerUp = (ev: PointerDragEvent) => {
|
|
onPointerUp = (ev: PointerDragEvent) => {
|
|
|
- if (this.isDragging) {
|
|
|
|
|
- this.stopDrag(ev)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (this.isWatchingPointer) { // if false, still waiting for previous drag's revert
|
|
|
|
|
+ this.isWatchingPointer = false
|
|
|
|
|
|
|
|
- allowSelection(document.body)
|
|
|
|
|
- allowContextMenu(document.body)
|
|
|
|
|
|
|
+ this.emitter.trigger('pointerup', ev) // can potentially set needsRevert
|
|
|
|
|
|
|
|
- if (this.delayTimeoutId) {
|
|
|
|
|
- clearTimeout(this.delayTimeoutId)
|
|
|
|
|
- this.delayTimeoutId = null
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (this.isDragging) {
|
|
|
|
|
+ this.tryStopDrag(ev)
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- this.emitter.trigger('pointerup', ev)
|
|
|
|
|
|
|
+ allowSelection(document.body)
|
|
|
|
|
+ allowContextMenu(document.body)
|
|
|
|
|
+
|
|
|
|
|
+ if (this.delayTimeoutId) {
|
|
|
|
|
+ clearTimeout(this.delayTimeoutId)
|
|
|
|
|
+ this.delayTimeoutId = null
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
startDelay(ev: PointerDragEvent) {
|
|
startDelay(ev: PointerDragEvent) {
|
|
@@ -136,29 +132,40 @@ export default class IntentfulDragListener {
|
|
|
|
|
|
|
|
handleDelayEnd(ev: PointerDragEvent) {
|
|
handleDelayEnd(ev: PointerDragEvent) {
|
|
|
this.isDelayEnded = true
|
|
this.isDelayEnded = true
|
|
|
- this.startDrag(ev)
|
|
|
|
|
|
|
+ this.tryStartDrag(ev)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
handleDistanceSurpassed(ev: PointerDragEvent) {
|
|
handleDistanceSurpassed(ev: PointerDragEvent) {
|
|
|
this.isDistanceSurpassed = true
|
|
this.isDistanceSurpassed = true
|
|
|
- this.startDrag(ev)
|
|
|
|
|
|
|
+ this.tryStartDrag(ev)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- startDrag(ev: PointerDragEvent) { // will only start if appropriate
|
|
|
|
|
|
|
+ tryStartDrag(ev: PointerDragEvent) {
|
|
|
if (this.isDelayEnded && this.isDistanceSurpassed) {
|
|
if (this.isDelayEnded && this.isDistanceSurpassed) {
|
|
|
- let touchScrollAllowed = this.options.touchScrollAllowed
|
|
|
|
|
-
|
|
|
|
|
- if (!this.pointerListener.isTouchScroll || touchScrollAllowed) {
|
|
|
|
|
- this.emitter.trigger('dragstart', ev)
|
|
|
|
|
|
|
+ if (!this.pointerListener.isTouchScroll || this.touchScrollAllowed) {
|
|
|
this.isDragging = true
|
|
this.isDragging = true
|
|
|
|
|
+ this.emitter.trigger('dragstart', ev)
|
|
|
|
|
|
|
|
- if (touchScrollAllowed === false) {
|
|
|
|
|
|
|
+ if (this.touchScrollAllowed === false) {
|
|
|
this.pointerListener.cancelTouchScroll()
|
|
this.pointerListener.cancelTouchScroll()
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ tryStopDrag(ev) {
|
|
|
|
|
+ let stopDrag = this.stopDrag.bind(this, ev) // bound with args
|
|
|
|
|
+
|
|
|
|
|
+ if (this.dragMirror.isReverting) {
|
|
|
|
|
+ this.dragMirror.revertDoneCallback = stopDrag // will clear itself
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // HACK - we want to make sure dragend fires after all pointerup events.
|
|
|
|
|
+ // Without doing this hack, pointer-up event propogation might reach an ancestor
|
|
|
|
|
+ // node after dragend
|
|
|
|
|
+ setTimeout(stopDrag, 0)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
stopDrag(ev) {
|
|
stopDrag(ev) {
|
|
|
this.emitter.trigger('dragend', ev)
|
|
this.emitter.trigger('dragend', ev)
|
|
|
this.isDragging = false
|
|
this.isDragging = false
|