|
@@ -5,7 +5,7 @@ import { isPrimaryMouseButton } from '../util/dom-event'
|
|
|
|
|
|
|
|
(exportHooks as any).touchMouseIgnoreWait = 500
|
|
(exportHooks as any).touchMouseIgnoreWait = 500
|
|
|
|
|
|
|
|
-export interface PointerEvent {
|
|
|
|
|
|
|
+export interface PointerDragEvent {
|
|
|
origEvent: UIEvent
|
|
origEvent: UIEvent
|
|
|
isTouch: boolean
|
|
isTouch: boolean
|
|
|
el: HTMLElement
|
|
el: HTMLElement
|
|
@@ -13,22 +13,24 @@ export interface PointerEvent {
|
|
|
pageY: number
|
|
pageY: number
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-export type PointerEventHandler = (ev: PointerEvent) => void
|
|
|
|
|
|
|
+export type PointerEventHandler = (ev: PointerDragEvent) => void
|
|
|
|
|
|
|
|
export default class PointerDragListener {
|
|
export default class PointerDragListener {
|
|
|
|
|
|
|
|
containerEl: HTMLElement
|
|
containerEl: HTMLElement
|
|
|
selector: string
|
|
selector: string
|
|
|
|
|
+ ignoreMove: any
|
|
|
subjectEl: HTMLElement
|
|
subjectEl: HTMLElement
|
|
|
emitter: EmitterMixin
|
|
emitter: EmitterMixin
|
|
|
|
|
|
|
|
isDragging: boolean = false
|
|
isDragging: boolean = false
|
|
|
isDraggingTouch: boolean = false
|
|
isDraggingTouch: boolean = false
|
|
|
- ignoreMouseDepth: number = 0
|
|
|
|
|
|
|
+ isTouchScroll: boolean = false
|
|
|
|
|
|
|
|
- constructor(containerEl: HTMLElement, selector?: string) {
|
|
|
|
|
|
|
+ constructor(containerEl: HTMLElement, selector: string, ignoreMove: any) {
|
|
|
this.containerEl = containerEl
|
|
this.containerEl = containerEl
|
|
|
this.selector = selector
|
|
this.selector = selector
|
|
|
|
|
+ this.ignoreMove = ignoreMove
|
|
|
this.emitter = new EmitterMixin()
|
|
this.emitter = new EmitterMixin()
|
|
|
containerEl.addEventListener('mousedown', this.onMouseDown)
|
|
containerEl.addEventListener('mousedown', this.onMouseDown)
|
|
|
containerEl.addEventListener('touchstart', this.onTouchStart)
|
|
containerEl.addEventListener('touchstart', this.onTouchStart)
|
|
@@ -36,6 +38,8 @@ export default class PointerDragListener {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
destroy() {
|
|
destroy() {
|
|
|
|
|
+ this.containerEl.removeEventListener('mousedown', this.onMouseDown)
|
|
|
|
|
+ this.containerEl.removeEventListener('touchstart', this.onTouchStart)
|
|
|
listenerDestroyed()
|
|
listenerDestroyed()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -43,76 +47,123 @@ export default class PointerDragListener {
|
|
|
this.emitter.on(name, handler)
|
|
this.emitter.on(name, handler)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ simulateStart(pev: PointerDragEvent) {
|
|
|
|
|
+ if (pev.isTouch) {
|
|
|
|
|
+ this.onTouchStart(pev.origEvent as TouchEvent)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.onMouseDown(pev.origEvent as MouseEvent)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ maybeStart(ev) {
|
|
|
|
|
+ if ((this.subjectEl = this.queryValidSubjectEl(ev))) {
|
|
|
|
|
+ this.isDragging = true // do this first so cancelTouchScroll will work
|
|
|
|
|
+ return true
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ cleanupDrag() { // do this last, so that pointerup has access to props
|
|
|
|
|
+ isWindowTouchMoveCancelled = false
|
|
|
|
|
+ this.isDragging = false
|
|
|
|
|
+ this.isTouchScroll = false
|
|
|
|
|
+ this.subjectEl = null
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ onTouchScroll = () => {
|
|
|
|
|
+ this.isTouchScroll = true
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ queryValidSubjectEl(ev: UIEvent): HTMLElement {
|
|
|
|
|
+ if (this.selector) {
|
|
|
|
|
+ return elementClosest(ev.target as HTMLElement, this.selector)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return this.containerEl
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
onMouseDown = (ev: MouseEvent) => {
|
|
onMouseDown = (ev: MouseEvent) => {
|
|
|
if (
|
|
if (
|
|
|
!this.shouldIgnoreMouse() &&
|
|
!this.shouldIgnoreMouse() &&
|
|
|
isPrimaryMouseButton(ev) &&
|
|
isPrimaryMouseButton(ev) &&
|
|
|
- (this.subjectEl = this.queryValidSubjectEl(ev))
|
|
|
|
|
|
|
+ this.maybeStart(ev)
|
|
|
) {
|
|
) {
|
|
|
- this.isDragging = true // do this first so cancelTouchScroll will work
|
|
|
|
|
- this.emitter.trigger('down', this.createMouseEvent(ev))
|
|
|
|
|
- document.addEventListener('mousemove', this.onMouseMove)
|
|
|
|
|
|
|
+ this.emitter.trigger('pointerdown', this.createMouseEvent(ev))
|
|
|
|
|
+
|
|
|
|
|
+ if (!this.ignoreMove) {
|
|
|
|
|
+ document.addEventListener('mousemove', this.onMouseMove)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
document.addEventListener('mouseup', this.onMouseUp)
|
|
document.addEventListener('mouseup', this.onMouseUp)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
onMouseMove = (ev: MouseEvent) => {
|
|
onMouseMove = (ev: MouseEvent) => {
|
|
|
- this.emitter.trigger('move', this.createMouseEvent(ev))
|
|
|
|
|
|
|
+ this.emitter.trigger('pointermove', this.createMouseEvent(ev))
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
onMouseUp = (ev: MouseEvent) => {
|
|
onMouseUp = (ev: MouseEvent) => {
|
|
|
- isWindowTouchMoveCancelled = false
|
|
|
|
|
- this.isDragging = false
|
|
|
|
|
document.removeEventListener('mousemove', this.onMouseMove)
|
|
document.removeEventListener('mousemove', this.onMouseMove)
|
|
|
document.removeEventListener('mouseup', this.onMouseUp)
|
|
document.removeEventListener('mouseup', this.onMouseUp)
|
|
|
- this.emitter.trigger('up', this.createMouseEvent(ev))
|
|
|
|
|
- this.subjectEl = null // clear afterwards, so handlers have access
|
|
|
|
|
|
|
+ this.emitter.trigger('pointerup', this.createMouseEvent(ev))
|
|
|
|
|
+ this.cleanupDrag()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
onTouchStart = (ev: TouchEvent) => {
|
|
onTouchStart = (ev: TouchEvent) => {
|
|
|
- if ((this.subjectEl = this.queryValidSubjectEl(ev))) {
|
|
|
|
|
- this.isDragging = true // do this first so cancelTouchScroll will work
|
|
|
|
|
|
|
+ if (this.maybeStart(ev)) {
|
|
|
this.isDraggingTouch = true
|
|
this.isDraggingTouch = true
|
|
|
- this.emitter.trigger('down', this.createTouchEvent(ev))
|
|
|
|
|
- document.addEventListener('touchmove', this.onTouchMove)
|
|
|
|
|
- document.addEventListener('touchend', this.onTouchEnd)
|
|
|
|
|
- document.addEventListener('touchcancel', this.onTouchEnd) // treat it as a touch end
|
|
|
|
|
|
|
+ this.emitter.trigger('pointerdown', this.createTouchEvent(ev))
|
|
|
|
|
+
|
|
|
|
|
+ // unlike mouse, need to attach to target, not document
|
|
|
|
|
+ // https://stackoverflow.com/a/45760014
|
|
|
|
|
+ let target = ev.target
|
|
|
|
|
+
|
|
|
|
|
+ if (!this.ignoreMove) {
|
|
|
|
|
+ target.addEventListener('touchmove', this.onTouchMove)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ target.addEventListener('touchend', this.onTouchEnd)
|
|
|
|
|
+ target.addEventListener('touchcancel', this.onTouchEnd) // treat it as a touch end
|
|
|
|
|
+
|
|
|
|
|
+ // attach a handler to get called when ANY scroll action happens on the page.
|
|
|
|
|
+ // this was impossible to do with normal on/off because 'scroll' doesn't bubble.
|
|
|
|
|
+ // http://stackoverflow.com/a/32954565/96342
|
|
|
|
|
+ window.addEventListener(
|
|
|
|
|
+ 'scroll',
|
|
|
|
|
+ this.onTouchScroll, // always bound to `this`
|
|
|
|
|
+ true // useCapture
|
|
|
|
|
+ )
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
onTouchMove = (ev: TouchEvent) => {
|
|
onTouchMove = (ev: TouchEvent) => {
|
|
|
- this.emitter.trigger('move', this.createTouchEvent(ev))
|
|
|
|
|
|
|
+ this.emitter.trigger('pointermove', this.createTouchEvent(ev))
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
onTouchEnd = (ev: TouchEvent) => {
|
|
onTouchEnd = (ev: TouchEvent) => {
|
|
|
- isWindowTouchMoveCancelled = false
|
|
|
|
|
- this.isDragging = false
|
|
|
|
|
- this.isDraggingTouch = false
|
|
|
|
|
- document.removeEventListener('touchmove', this.onTouchMove)
|
|
|
|
|
- document.removeEventListener('touchend', this.onTouchEnd)
|
|
|
|
|
- document.removeEventListener('touchcancel', this.onTouchEnd)
|
|
|
|
|
- this.emitter.trigger('up', this.createTouchEvent(ev))
|
|
|
|
|
- this.subjectEl = null // clear afterwards, so handlers have access
|
|
|
|
|
- this.startIgnoringMouse()
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- queryValidSubjectEl(ev: UIEvent): HTMLElement {
|
|
|
|
|
- if (this.selector) {
|
|
|
|
|
- return elementClosest(ev.target as HTMLElement, this.selector)
|
|
|
|
|
- } else {
|
|
|
|
|
- return this.containerEl
|
|
|
|
|
|
|
+ if (this.isDragging) { // guard against touchend followed by touchcancel
|
|
|
|
|
+ let target = ev.target
|
|
|
|
|
+ target.removeEventListener('touchmove', this.onTouchMove)
|
|
|
|
|
+ target.removeEventListener('touchend', this.onTouchEnd)
|
|
|
|
|
+ target.removeEventListener('touchcancel', this.onTouchEnd)
|
|
|
|
|
+
|
|
|
|
|
+ window.removeEventListener('scroll', this.onTouchScroll)
|
|
|
|
|
+
|
|
|
|
|
+ this.emitter.trigger('pointerup', this.createTouchEvent(ev))
|
|
|
|
|
+ this.cleanupDrag()
|
|
|
|
|
+ this.isDraggingTouch = false
|
|
|
|
|
+ this.startIgnoringMouse()
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
shouldIgnoreMouse() {
|
|
shouldIgnoreMouse() {
|
|
|
- return this.ignoreMouseDepth || this.isDraggingTouch
|
|
|
|
|
|
|
+ return ignoreMouseDepth || this.isDraggingTouch
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
startIgnoringMouse() {
|
|
startIgnoringMouse() {
|
|
|
- this.ignoreMouseDepth++
|
|
|
|
|
|
|
+ ignoreMouseDepth++
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
setTimeout(() => {
|
|
|
- this.ignoreMouseDepth--
|
|
|
|
|
|
|
+ ignoreMouseDepth--
|
|
|
}, (exportHooks as any).touchMouseIgnoreWait)
|
|
}, (exportHooks as any).touchMouseIgnoreWait)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -122,7 +173,7 @@ export default class PointerDragListener {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- createMouseEvent(ev): PointerEvent {
|
|
|
|
|
|
|
+ createMouseEvent(ev): PointerDragEvent {
|
|
|
return {
|
|
return {
|
|
|
origEvent: ev,
|
|
origEvent: ev,
|
|
|
isTouch: false,
|
|
isTouch: false,
|
|
@@ -132,13 +183,13 @@ export default class PointerDragListener {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- createTouchEvent(ev): PointerEvent {
|
|
|
|
|
- let touches = ev.touches
|
|
|
|
|
|
|
+ createTouchEvent(ev): PointerDragEvent {
|
|
|
let obj = {
|
|
let obj = {
|
|
|
origEvent: ev,
|
|
origEvent: ev,
|
|
|
isTouch: true,
|
|
isTouch: true,
|
|
|
el: this.subjectEl
|
|
el: this.subjectEl
|
|
|
- } as PointerEvent
|
|
|
|
|
|
|
+ } as PointerDragEvent
|
|
|
|
|
+ let touches = ev.touches
|
|
|
|
|
|
|
|
// if touch coords available, prefer,
|
|
// if touch coords available, prefer,
|
|
|
// because FF would give bad ev.pageX ev.pageY
|
|
// because FF would give bad ev.pageX ev.pageY
|
|
@@ -155,6 +206,8 @@ export default class PointerDragListener {
|
|
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+let ignoreMouseDepth = 0
|
|
|
|
|
+
|
|
|
// we want to attach touchmove as early as possible for safari
|
|
// we want to attach touchmove as early as possible for safari
|
|
|
|
|
|
|
|
let listenerCnt = 0
|
|
let listenerCnt = 0
|