diff --git a/src/components/app/test/gesture-collision/page1.html b/src/components/app/test/gesture-collision/page1.html index d3a3e87585..855443938c 100644 --- a/src/components/app/test/gesture-collision/page1.html +++ b/src/components/app/test/gesture-collision/page1.html @@ -58,6 +58,22 @@ + + LEFT button + + + + + + + + RIGHT button + + + + + + diff --git a/src/components/item/item-sliding-gesture.ts b/src/components/item/item-sliding-gesture.ts index 221331cd6c..f2d5d86bb1 100644 --- a/src/components/item/item-sliding-gesture.ts +++ b/src/components/item/item-sliding-gesture.ts @@ -4,35 +4,29 @@ import { List } from '../list/list'; import { closest, Coordinates, pointerCoord } from '../../util/dom'; import { PointerEvents, UIEventManager } from '../../util/ui-event-manager'; import { GestureDelegate, GestureOptions, GesturePriority } from '../../gestures/gesture-controller'; +import { PanGesture } from '../../gestures/drag-gesture'; const DRAG_THRESHOLD = 10; const MAX_ATTACK_ANGLE = 20; -export class ItemSlidingGesture { +export class ItemSlidingGesture extends PanGesture { private preSelectedContainer: ItemSliding = null; private selectedContainer: ItemSliding = null; private openContainer: ItemSliding = null; - private events: UIEventManager = new UIEventManager(false); - private panDetector: PanXRecognizer = new PanXRecognizer(DRAG_THRESHOLD, MAX_ATTACK_ANGLE); - private pointerEvents: PointerEvents; private firstCoordX: number; private firstTimestamp: number; - private gesture: GestureDelegate; constructor(public list: List) { - this.gesture = list.gestureCtrl.create('item-sliding', { - priority: GesturePriority.Interactive, - }); - - this.pointerEvents = this.events.pointerEvents({ - element: list.getNativeElement(), - pointerDown: this.pointerStart.bind(this), - pointerMove: this.pointerMove.bind(this), - pointerUp: this.pointerEnd.bind(this), + super(list.getNativeElement(), { + maxAngle: MAX_ATTACK_ANGLE, + threshold: DRAG_THRESHOLD, + gesture: list.gestureCtrl.create('item-sliding', { + priority: GesturePriority.SlidingItem, + }) }); } - private pointerStart(ev: any): boolean { + canStart(ev: any): boolean { if (this.selectedContainer) { return false; } @@ -42,85 +36,50 @@ export class ItemSlidingGesture { this.closeOpened(); return false; } - // Close open container if it is not the selected one. if (container !== this.openContainer) { this.closeOpened(); } - // Try to start gesture - if (!this.gesture.start()) { - this.gesture.release(); - return false; - } - let coord = pointerCoord(ev); this.preSelectedContainer = container; - this.panDetector.start(coord); this.firstCoordX = coord.x; this.firstTimestamp = Date.now(); return true; } - private pointerMove(ev: any) { - if (this.selectedContainer) { - this.onDragMove(ev); - return; - } + onDragStart(ev: any) { + ev.preventDefault(); + let coord = pointerCoord(ev); - if (this.panDetector.detect(coord)) { - if (this.panDetector.isPanX() && this.gesture.capture()) { - this.onDragStart(ev, coord); - return; - } - - // Detection/capturing was not successful, aborting! - this.closeOpened(); - this.pointerEvents.stop(); - } - } - - private pointerEnd(ev: any) { - this.gesture.release(); - if (this.selectedContainer) { - this.onDragEnd(ev); - } else { - this.closeOpened(); - } - } - - private onDragStart(ev: any, coord: Coordinates): boolean { - let container = getContainer(ev); - if (!container) { - console.debug('onDragStart, no itemContainerEle'); - return false; - } - ev.preventDefault(); - this.selectedContainer = this.openContainer = this.preSelectedContainer; - container.startSliding(coord.x); + this.selectedContainer.startSliding(coord.x); } - private onDragMove(ev: any) { - let coordX = pointerCoord(ev).x; + onDragMove(ev: any) { ev.preventDefault(); + + let coordX = pointerCoord(ev).x; this.selectedContainer.moveSliding(coordX); } - private onDragEnd(ev: any) { + onDragEnd(ev: any) { ev.preventDefault(); + let coordX = pointerCoord(ev).x; let deltaX = (coordX - this.firstCoordX); let deltaT = (Date.now() - this.firstTimestamp); - let openAmount = this.selectedContainer.endSliding(deltaX / deltaT); this.selectedContainer = null; this.preSelectedContainer = null; } + notCaptured(ev: any) { + this.closeOpened(); + } + closeOpened(): boolean { this.selectedContainer = null; - this.gesture.release(); if (this.openContainer) { this.openContainer.close(); @@ -131,8 +90,7 @@ export class ItemSlidingGesture { } destroy() { - this.gesture.destroy(); - this.events.unlistenAll(); + super.destroy(); this.closeOpened(); this.list = null; @@ -148,70 +106,4 @@ function getContainer(ev: any): ItemSliding { return (ele)['$ionComponent']; } return null; -} - -class AngleRecognizer { - private startCoord: Coordinates; - private sumCoord: Coordinates; - private dirty: boolean; - private _angle: any = null; - private threshold: number; - - constructor(threshold: number) { - this.threshold = threshold ** 2; - } - - start(coord: Coordinates) { - this.startCoord = coord; - this._angle = 0; - this.dirty = true; - } - - angle(): any { - return this._angle; - } - - detect(coord: Coordinates): boolean { - if (!this.dirty) { - return false; - } - let deltaX = (coord.x - this.startCoord.x); - let deltaY = (coord.y - this.startCoord.y); - let distance = deltaX * deltaX + deltaY * deltaY; - if (distance >= this.threshold) { - this._angle = Math.atan2(deltaY, deltaX); - this.dirty = false; - return true; - } - return false; - } -} - - -class PanXRecognizer extends AngleRecognizer { - private _isPanX: boolean; - private maxAngle: number; - - constructor(threshold: number, maxAngle: number) { - super(threshold); - this.maxAngle = maxAngle * (Math.PI / 180); - } - - start(coord: Coordinates) { - super.start(coord); - this._isPanX = false; - } - - isPanX(): boolean { - return this._isPanX; - } - - detect(coord: Coordinates): boolean { - if (super.detect(coord)) { - let angle = Math.abs(this.angle()); - this._isPanX = (angle < this.maxAngle || Math.abs(angle - Math.PI) < this.maxAngle); - return true; - } - return false; - } -} +} \ No newline at end of file diff --git a/src/components/list/list.ts b/src/components/list/list.ts index 18a0eb6f86..1e92ec822f 100644 --- a/src/components/list/list.ts +++ b/src/components/list/list.ts @@ -92,6 +92,7 @@ export class List extends Ion { } else if (!this._slidingGesture) { console.debug('enableSlidingItems'); this._slidingGesture = new ItemSlidingGesture(this); + this._slidingGesture.listen(); } } diff --git a/src/components/menu/menu-gestures.ts b/src/components/menu/menu-gestures.ts index 0284bf1529..dc55a4f8ee 100644 --- a/src/components/menu/menu-gestures.ts +++ b/src/components/menu/menu-gestures.ts @@ -2,69 +2,35 @@ import { Menu } from './menu'; import { SlideEdgeGesture } from '../../gestures/slide-edge-gesture'; import { SlideData } from '../../gestures/slide-gesture'; import { assign } from '../../util/util'; -import { GestureDelegate, GesturePriority } from '../../gestures/gesture-controller'; - -const DEGREES_TO_RADIANS = Math.PI / 180; -const MIN_COSINE = Math.cos(40 * DEGREES_TO_RADIANS); +import { GesturePriority } from '../../gestures/gesture-controller'; /** * Gesture attached to the content which the menu is assigned to */ export class MenuContentGesture extends SlideEdgeGesture { - gesture: GestureDelegate; constructor(public menu: Menu, contentEle: HTMLElement, options: any = {}) { super(contentEle, assign({ direction: 'x', edge: menu.side, threshold: 0, - maxEdgeStart: menu.maxEdgeStart || 75 + maxEdgeStart: menu.maxEdgeStart || 50, + maxAngle: 40, + gesture: menu.gestureCtrl.create('menu-swipe', { + priority: GesturePriority.MenuSwipe, + }) }, options)); - - this.gesture = menu.gestureCtrl.create('menu-swipe', { - priority: GesturePriority.NavigationOptional, - }); } canStart(ev: any): boolean { - if (this.shouldStart(ev)) { - return this.gesture.capture(); - } - this.gesture.release(); - return false; - } - - shouldStart(ev: any): boolean { let menu = this.menu; - if (!menu.enabled || !menu.swipeEnabled) { - console.debug('menu can not start, isEnabled:', menu.enabled, 'isSwipeEnabled:', menu.swipeEnabled, 'side:', menu.side); - return false; - } - - if (ev.distance > 50) { - // the distance is longer than you'd expect a side menu swipe to be - console.debug('menu can not start, distance too far:', ev.distance, 'side:', menu.side); - return false; - } - - console.debug('menu shouldCapture,', menu.side, 'isOpen', menu.isOpen, 'angle', ev.angle, 'distance', ev.distance); - - if (menu.isOpen) { - return true; - } - - let cosine = Math.cos(ev.angle * DEGREES_TO_RADIANS); - if (menu.side === 'right') { - if (cosine < -MIN_COSINE) { - return super.canStart(ev); - } - } else { - if (cosine > MIN_COSINE) { - return super.canStart(ev); - } - } - return false; + return ( + menu.enabled && + menu.swipeEnabled && + (menu.isOpen || super.canStart(ev)) + ); } + // Set CSS, then wait one frame for it to apply before sliding starts onSlideBeforeStart(slide: SlideData, ev: any) { console.debug('menu gesture, onSlideBeforeStart', this.menu.side); @@ -75,29 +41,26 @@ export class MenuContentGesture extends SlideEdgeGesture { let z = (this.menu.side === 'right' ? slide.min : slide.max); let stepValue = (slide.distance / z); console.debug('menu gesture, onSlide', this.menu.side, 'distance', slide.distance, 'min', slide.min, 'max', slide.max, 'z', z, 'stepValue', stepValue); - ev.srcEvent.preventDefault(); ev.preventDefault(); this.menu.swipeProgress(stepValue); } onSlideEnd(slide: SlideData, ev: any) { - this.gesture.release(); - let z = (this.menu.side === 'right' ? slide.min : slide.max); let currentStepValue = (slide.distance / z); - + let velocity = slide.velocity; z = Math.abs(z * 0.5); - let shouldCompleteRight = (ev.velocityX >= 0) - && (ev.velocityX > 0.2 || slide.delta > z); + let shouldCompleteRight = (velocity >= 0) + && (velocity > 0.2 || slide.delta > z); - let shouldCompleteLeft = (ev.velocityX <= 0) - && (ev.velocityX < -0.2 || slide.delta < -z); + let shouldCompleteLeft = (velocity <= 0) + && (velocity < -0.2 || slide.delta < -z); console.debug( 'menu gesture, onSlide', this.menu.side, 'distance', slide.distance, 'delta', slide.delta, - 'velocityX', ev.velocityX, + 'velocity', velocity, 'min', slide.min, 'max', slide.max, 'shouldCompleteLeft', shouldCompleteLeft, @@ -131,27 +94,5 @@ export class MenuContentGesture extends SlideEdgeGesture { max: this.menu.width() }; } - - unlisten() { - this.gesture.release(); - super.unlisten(); - } - - destroy() { - this.gesture.destroy(); - super.destroy(); - } } - -/** - * Gesture attached to the actual menu itself - */ -export class MenuTargetGesture extends MenuContentGesture { - constructor(menu: Menu, menuEle: HTMLElement) { - super(menu, menuEle, { - maxEdgeStart: 0 - }); - this.gesture.priority++; - } -} diff --git a/src/components/menu/menu.ts b/src/components/menu/menu.ts index 11f8e0e7b0..0f12205dfb 100644 --- a/src/components/menu/menu.ts +++ b/src/components/menu/menu.ts @@ -5,7 +5,7 @@ import { Config } from '../../config/config'; import { Ion } from '../ion'; import { isTrueProperty } from '../../util/util'; import { Keyboard } from '../../util/keyboard'; -import { MenuContentGesture, MenuTargetGesture } from './menu-gestures'; +import { MenuContentGesture } from './menu-gestures'; import { MenuController } from './menu-controller'; import { MenuType } from './menu-types'; import { Platform } from '../../platform/platform'; @@ -191,8 +191,7 @@ import { GestureController } from '../../gestures/gesture-controller'; export class Menu extends Ion { private _preventTime: number = 0; private _cntEle: HTMLElement; - private _cntGesture: MenuTargetGesture; - private _menuGesture: MenuContentGesture; + private _cntGesture: MenuContentGesture; private _type: MenuType; private _resizeUnreg: Function; private _isEnabled: boolean = true; @@ -337,8 +336,7 @@ export class Menu extends Ion { self._renderer.setElementAttribute(self._elementRef.nativeElement, 'type', self.type); // add the gestures - self._cntGesture = new MenuContentGesture(self, self.getContentElement()); - self._menuGesture = new MenuTargetGesture(self, self.getNativeElement()); + self._cntGesture = new MenuContentGesture(self, document.body); // register listeners if this menu is enabled // check if more than one menu is on the same side @@ -389,16 +387,12 @@ export class Menu extends Ion { if (self._isEnabled && self._isSwipeEnabled && !self._cntGesture.isListening) { // should listen, but is not currently listening console.debug('menu, gesture listen', self.side); - self._zone.runOutsideAngular(function() { - self._cntGesture.listen(); - self._menuGesture.listen(); - }); + self._cntGesture.listen(); } else if (self._cntGesture.isListening && (!self._isEnabled || !self._isSwipeEnabled)) { // should not listen, but is currently listening console.debug('menu, gesture unlisten', self.side); self._cntGesture.unlisten(); - self._menuGesture.unlisten(); } } } @@ -625,7 +619,6 @@ export class Menu extends Ion { ngOnDestroy() { this._menuCtrl.unregister(this); this._cntGesture && this._cntGesture.destroy(); - this._menuGesture && this._menuGesture.destroy(); this._type && this._type.destroy(); this._resizeUnreg && this._resizeUnreg(); this._cntEle = null; diff --git a/src/components/nav/swipe-back.ts b/src/components/nav/swipe-back.ts index 8a162d7e2a..32a7458292 100644 --- a/src/components/nav/swipe-back.ts +++ b/src/components/nav/swipe-back.ts @@ -8,8 +8,6 @@ import { SlideEdgeGesture } from '../../gestures/slide-edge-gesture'; export class SwipeBackGesture extends SlideEdgeGesture { - private gesture: GestureDelegate; - constructor( element: HTMLElement, options: any, @@ -18,32 +16,26 @@ export class SwipeBackGesture extends SlideEdgeGesture { ) { super(element, assign({ direction: 'x', - maxEdgeStart: 75 + maxEdgeStart: 75, + gesture: gestureCtlr.create('goback-swipe', { + priority: GesturePriority.GoBackSwipe, + }) }, options)); - - this.gesture = gestureCtlr.create('goback-swipe', { - priority: GesturePriority.Navigation, - }); } canStart(ev: any): boolean { - this.gesture.release(); - // the gesture swipe angle must be mainly horizontal and the // gesture distance would be relatively short for a swipe back // and swipe back must be possible on this nav controller return ( - ev.angle > -40 && - ev.angle < 40 && - ev.distance < 50 && this._nav.canSwipeBack() && - super.canStart(ev) && - this.gesture.capture() + super.canStart(ev) ); } + onSlideBeforeStart(slideData: SlideData, ev: any) { - console.debug('swipeBack, onSlideBeforeStart', ev.srcEvent.type); + console.debug('swipeBack, onSlideBeforeStart', ev.type); this._nav.swipeBackStart(); } @@ -54,25 +46,10 @@ export class SwipeBackGesture extends SlideEdgeGesture { } onSlideEnd(slide: SlideData, ev: any) { - let shouldComplete = (Math.abs(ev.velocityX) > 0.2 || Math.abs(slide.delta) > Math.abs(slide.max) * 0.5); - + let shouldComplete = (Math.abs(slide.velocity) > 0.2 || Math.abs(slide.delta) > Math.abs(slide.max) * 0.5); let currentStepValue = (slide.distance / slide.max); console.debug('swipeBack, onSlideEnd, shouldComplete', shouldComplete, 'currentStepValue', currentStepValue); - this._nav.swipeBackEnd(shouldComplete, currentStepValue); - - this.gesture.release(); } - - unlisten() { - this.gesture.release(); - super.unlisten(); - } - - destroy() { - this.gesture.destroy(); - super.destroy(); - } - } diff --git a/src/components/refresher/refresher.ts b/src/components/refresher/refresher.ts index 03081e14da..7acebde8c2 100644 --- a/src/components/refresher/refresher.ts +++ b/src/components/refresher/refresher.ts @@ -201,7 +201,7 @@ export class Refresher { constructor(@Host() private _content: Content, private _zone: NgZone, gestureCtrl: GestureController) { _content.addCssClass('has-refresher'); this._gesture = gestureCtrl.create('refresher', { - priority: GesturePriority.Interactive, + priority: GesturePriority.Refresher, }); } diff --git a/src/gestures/drag-gesture.ts b/src/gestures/drag-gesture.ts index 87dd7a2624..f1ea44d07c 100644 --- a/src/gestures/drag-gesture.ts +++ b/src/gestures/drag-gesture.ts @@ -1,42 +1,142 @@ -import { Gesture } from './gesture'; + import { defaults } from '../util'; +import { GestureDelegate } from '../gestures/gesture-controller'; +import { PointerEvents, UIEventManager } from '../util/ui-event-manager'; +import { PanRecognizer } from './recognizers'; +import { pointerCoord, Coordinates } from '../util/dom'; /** * @private */ +export interface PanGestureConfig { + threshold?: number; + maxAngle?: number; + direction?: 'x' | 'y'; + gesture?: GestureDelegate; +} -export class DragGesture extends Gesture { - public dragging: boolean; +/** + * @private + */ +export class PanGesture { + private dragging: boolean; + private events: UIEventManager = new UIEventManager(false); + private pointerEvents: PointerEvents; + private detector: PanRecognizer; + private started: boolean = false; + private captured: boolean = false; + public isListening: boolean = false; + protected gestute: GestureDelegate; + protected direction: string; - constructor(element: HTMLElement, opts = {}) { - defaults(opts, {}); - super(element, opts); + constructor(private element: HTMLElement, opts: PanGestureConfig = {}) { + defaults(opts, { + threshold: 20, + maxAngle: 40, + direction: 'x' + }); + this.gestute = opts.gesture; + this.direction = opts.direction; + this.detector = new PanRecognizer(opts.direction, opts.threshold, opts.maxAngle); } listen() { - super.listen(); - - this.on('panstart', (ev: UIEvent) => { - if (this.onDragStart(ev) !== false) { - this.dragging = true; - } - }); - - this.on('panmove', (ev: UIEvent) => { - if (!this.dragging) return; - if (this.onDrag(ev) === false) { - this.dragging = false; - } - }); - - this.on('panend', (ev: UIEvent) => { - if (!this.dragging) return; - this.onDragEnd(ev); - this.dragging = false; - }); + if (!this.isListening) { + this.pointerEvents = this.events.pointerEvents({ + element: this.element, + pointerDown: this.pointerDown.bind(this), + pointerMove: this.pointerMove.bind(this), + pointerUp: this.pointerUp.bind(this), + }); + this.isListening = true; + } } - onDrag(ev: any): boolean { return true; } - onDragStart(ev: any): boolean { return true; } - onDragEnd(ev: any): void {} + unlisten() { + this.gestute && this.gestute.release(); + this.events.unlistenAll(); + this.isListening = false; + } + + destroy() { + this.gestute && this.gestute.destroy(); + this.unlisten(); + this.element = null; + } + + pointerDown(ev: any): boolean { + if (this.started) { + return; + } + if (!this.canStart(ev)) { + return false; + } + if (this.gestute) { + // Release fallback + this.gestute.release(); + // Start gesture + if (!this.gestute.start()) { + return false; + } + } + + let coord = pointerCoord(ev); + this.detector.start(coord); + this.started = true; + this.captured = false; + return true; + } + + pointerMove(ev: any) { + if (!this.started) { + return; + } + if (this.captured) { + this.onDragMove(ev); + return; + } + let coord = pointerCoord(ev); + if (this.detector.detect(coord)) { + + if (this.detector.pan() !== 0 && this.canCapture(ev) && + (!this.gestute || this.gestute.capture())) { + this.onDragStart(ev); + this.captured = true; + return; + } + + // Detection/capturing was not successful, aborting! + this.started = false; + this.captured = false; + this.pointerEvents.stop(); + this.notCaptured(ev); + } + } + + pointerUp(ev: any) { + if (!this.started) { + return; + } + this.gestute && this.gestute.release(); + + if (this.captured) { + this.onDragEnd(ev); + } else { + this.notCaptured(ev); + } + this.captured = false; + this.started = false; + } + + getNativeElement(): HTMLElement { + return this.element; + } + + // Implemented in a subclass + canStart(ev: any): boolean { return true; } + canCapture(ev: any): boolean { return true; } + onDragStart(ev: any) { } + onDragMove(ev: any) { } + onDragEnd(ev: any) { } + notCaptured(ev: any) { } } diff --git a/src/gestures/gesture-controller.ts b/src/gestures/gesture-controller.ts index 257bb8ccbf..f768dc252e 100644 --- a/src/gestures/gesture-controller.ts +++ b/src/gestures/gesture-controller.ts @@ -4,11 +4,16 @@ import { App } from '../components/app/app'; export const enum GesturePriority { Minimun = -10000, - NavigationOptional = -20, - Navigation = -10, + VeryLow = -20, + Low = -10, Normal = 0, - Interactive = 10, - Input = 20, + High = 10, + VeryHigh = 20, + + SlidingItem = Low, + MenuSwipe = High, + GoBackSwipe = VeryHigh, + Refresher = Normal, } export const enum DisableScroll { diff --git a/src/gestures/recognizers.ts b/src/gestures/recognizers.ts new file mode 100644 index 0000000000..ad63faaebe --- /dev/null +++ b/src/gestures/recognizers.ts @@ -0,0 +1,58 @@ +import { pointerCoord, Coordinates } from '../util/dom'; + +export class PanRecognizer { + private startCoord: Coordinates; + private dirty: boolean = false; + private threshold: number; + private maxCosine: number; + private _angle: any = 0; + private _isPan: number = 0; + + constructor(private direction: string, threshold: number, maxAngle: number) { + let radians = maxAngle * (Math.PI / 180); + this.maxCosine = Math.cos(radians); + this.threshold = threshold * threshold; + } + + start(coord: Coordinates) { + this.startCoord = coord; + this._angle = 0; + this._isPan = 0; + this.dirty = true; + } + + detect(coord: Coordinates): boolean { + if (!this.dirty) { + return false; + } + let deltaX = (coord.x - this.startCoord.x); + let deltaY = (coord.y - this.startCoord.y); + let distance = deltaX * deltaX + deltaY * deltaY; + if (distance >= this.threshold) { + let angle = Math.atan2(deltaY, deltaX); + let cosine = (this.direction === 'y') + ? Math.sin(angle) + : Math.cos(angle); + + this._angle = angle; + if (cosine > this.maxCosine) { + this._isPan = 1; + } else if (cosine < -this.maxCosine) { + this._isPan = -1; + } else { + this._isPan = 0; + } + this.dirty = false; + return true; + } + return false; + } + + angle(): any { + return this._angle; + } + + pan(): number { + return this._isPan; + } +} diff --git a/src/gestures/slide-edge-gesture.ts b/src/gestures/slide-edge-gesture.ts index 74b5d18567..d40425f06e 100644 --- a/src/gestures/slide-edge-gesture.ts +++ b/src/gestures/slide-edge-gesture.ts @@ -1,6 +1,6 @@ -import {SlideGesture} from './slide-gesture'; -import {defaults} from '../util/util'; -import {windowDimensions} from '../util/dom'; +import { SlideGesture } from './slide-gesture'; +import { defaults } from '../util/util'; +import { pointerCoord, windowDimensions } from '../util/dom'; /** * @private @@ -22,8 +22,9 @@ export class SlideEdgeGesture extends SlideGesture { } canStart(ev: any): boolean { + let coord = pointerCoord(ev); this._d = this.getContainerDimensions(); - return this.edges.every(edge => this._checkEdge(edge, ev.center)); + return this.edges.every(edge => this._checkEdge(edge, coord)); } getContainerDimensions() { diff --git a/src/gestures/slide-gesture.ts b/src/gestures/slide-gesture.ts index dca0d0f19f..421ed1e050 100644 --- a/src/gestures/slide-gesture.ts +++ b/src/gestures/slide-gesture.ts @@ -1,16 +1,15 @@ -import {DragGesture} from './drag-gesture'; -import {clamp} from '../util'; - +import { PanGesture } from './drag-gesture'; +import { clamp } from '../util'; +import { pointerCoord } from '../util/dom'; /** * @private */ -export class SlideGesture extends DragGesture { +export class SlideGesture extends PanGesture { public slide: SlideData = null; constructor(element: HTMLElement, opts = {}) { super(element, opts); - this.element = element; } /* @@ -20,7 +19,7 @@ export class SlideGesture extends DragGesture { getSlideBoundaries(slide: SlideData, ev: any) { return { min: 0, - max: this.element.offsetWidth + max: this.getNativeElement().offsetWidth }; } @@ -33,48 +32,43 @@ export class SlideGesture extends DragGesture { return 0; } - canStart(ev: any): boolean { - return true; - } - - onDragStart(ev: any): boolean { - if (!this.canStart(ev)) { - return false; - } - + onDragStart(ev: any) { this.slide = {}; this.onSlideBeforeStart(this.slide, ev); - var {min, max} = this.getSlideBoundaries(this.slide, ev); + let {min, max} = this.getSlideBoundaries(this.slide, ev); + let coord = pointerCoord(ev); this.slide.min = min; this.slide.max = max; this.slide.elementStartPos = this.getElementStartPos(this.slide, ev); - this.slide.pointerStartPos = ev.center[this.direction]; + this.slide.pos = this.slide.pointerStartPos = coord[this.direction]; + this.slide.timestamp = Date.now(); this.slide.started = true; + this.slide.velocity = 0; this.onSlideStart(this.slide, ev); - - return true; } - onDrag(ev: any): boolean { - if (!this.slide || !this.slide.started) { - return false; - } + onDragMove(ev: any) { + let coord = pointerCoord(ev); + let newPos = coord[this.direction]; + let newTimestamp = Date.now(); + let velocity = (newPos - this.slide.pos) / (newTimestamp - this.slide.timestamp); - this.slide.pos = ev.center[this.direction]; + this.slide.pos = newPos; + this.slide.timestamp = newTimestamp; this.slide.distance = clamp( this.slide.min, - this.slide.pos - this.slide.pointerStartPos + this.slide.elementStartPos, + newPos - this.slide.pointerStartPos + this.slide.elementStartPos, this.slide.max ); - this.slide.delta = this.slide.pos - this.slide.pointerStartPos; + this.slide.velocity = velocity; + this.slide.delta = newPos - this.slide.pointerStartPos; this.onSlide(this.slide, ev); return true; } onDragEnd(ev: any) { - if (!this.slide || !this.slide.started) return; this.onSlideEnd(this.slide, ev); this.slide = null; } @@ -85,6 +79,9 @@ export class SlideGesture extends DragGesture { onSlideEnd(slide?: SlideData, ev?: any): void {} } +/** + * @private + */ export interface SlideData { min?: number; max?: number; @@ -92,6 +89,8 @@ export interface SlideData { delta?: number; started?: boolean; pos?: any; + timestamp?: number; pointerStartPos?: number; elementStartPos?: number; + velocity?: number; }