diff --git a/packages/core/src/components/gesture/gesture.ts b/packages/core/src/components/gesture/gesture.ts deleted file mode 100644 index 0aca2fad62..0000000000 --- a/packages/core/src/components/gesture/gesture.ts +++ /dev/null @@ -1,397 +0,0 @@ -import { BlockerDelegate } from './gesture-controller'; -import { Component, Listen, Prop, Watch } from '@stencil/core'; -import { Ionic } from '@stencil/core'; -import { GestureCallback, GestureDetail, GlobalNamespace } from '../../utils/interfaces'; -import { applyStyles, getElementReference, pointerCoordX, pointerCoordY } from '../../utils/helpers'; -import { GestureController, GestureDelegate, BLOCK_ALL } from './gesture-controller'; -import { PanRecognizer } from './recognizers'; - - -@Component({ - tag: 'ion-gesture' -}) -export class Gesture { - private $el: HTMLElement; - private ctrl: GestureController; - private detail: GestureDetail = {}; - private positions: number[] = []; - private gesture: GestureDelegate; - private lastTouch = 0; - private pan: PanRecognizer; - private hasCapturedPan = false; - private hasPress = false; - private hasStartedPan = false; - private requiresMove = false; - private isMoveQueued = false; - private blocker: BlockerDelegate; - - @Prop() attachTo: string = 'child'; - @Prop() autoBlockAll: boolean = false; - @Prop() block: string = null; - @Prop() disableScroll: boolean = false; - @Prop() direction: string = 'x'; - @Prop() gestureName: string = ''; - @Prop() gesturePriority: number = 0; - @Prop() maxAngle: number = 40; - @Prop() threshold: number = 20; - @Prop() type: string = 'pan'; - - @Prop() canStart: GestureCallback; - @Prop() onStart: GestureCallback; - @Prop() onMove: GestureCallback; - @Prop() onEnd: GestureCallback; - @Prop() onPress: GestureCallback; - @Prop() notCaptured: GestureCallback; - - - ionViewDidLoad() { - this.ctrl = (Ionic).controllers.gesture = ((Ionic).controllers.gesture || new GestureController()); - - this.gesture = this.ctrl.createGesture(this.gestureName, this.gesturePriority, this.disableScroll); - - const types = this.type.replace(/\s/g, '').toLowerCase().split(','); - - if (types.indexOf('pan') > -1) { - this.pan = new PanRecognizer(this.direction, this.threshold, this.maxAngle); - this.requiresMove = true; - } - this.hasPress = (types.indexOf('press') > -1); - - if (this.pan || this.hasPress) { - Ionic.listener.enable(this, 'touchstart', true, this.attachTo); - Ionic.listener.enable(this, 'mousedown', true, this.attachTo); - - Ionic.dom.write(() => { - applyStyles(getElementReference(this.$el, this.attachTo), GESTURE_INLINE_STYLES); - }); - } - - if (this.autoBlockAll) { - this.blocker = this.ctrl.createBlocker(BLOCK_ALL); - this.blocker.block(); - } - } - - - @Watch('block') - blockChange(block: string) { - if (this.blocker) { - this.blocker.destroy(); - } - if (block) { - this.blocker = this.ctrl.createBlocker(block.split(',')); - } - } - - // DOWN ************************* - - @Listen('touchstart', { passive: true, enabled: false }) - onTouchStart(ev: TouchEvent) { - this.lastTouch = now(ev); - - this.enableMouse(false); - this.enableTouch(true); - - this.pointerDown(ev, this.lastTouch); - } - - - @Listen('mousedown', { passive: true, enabled: false }) - onMouseDown(ev: MouseEvent) { - const timeStamp = now(ev); - - if (this.lastTouch === 0 || (this.lastTouch + MOUSE_WAIT < timeStamp)) { - this.enableMouse(true); - this.enableTouch(false); - - this.pointerDown(ev, timeStamp); - } - } - - - private pointerDown(ev: UIEvent, timeStamp: number): boolean { - if (!this.gesture || this.hasStartedPan) { - return false; - } - - const detail = this.detail; - - detail.startX = detail.currentX = pointerCoordX(ev); - detail.startY = detail.currentY = pointerCoordY(ev); - detail.startTimeStamp = detail.timeStamp = timeStamp; - detail.velocityX = detail.velocityY = detail.deltaX = detail.deltaY = 0; - detail.directionX = detail.directionY = detail.velocityDirectionX = detail.velocityDirectionY = null; - detail.event = ev; - this.positions.length = 0; - - if (this.canStart && this.canStart(detail) === false) { - return false; - } - - this.positions.push(detail.currentX, detail.currentY, timeStamp); - - // Release fallback - this.gesture.release(); - - // Start gesture - if (!this.gesture.start()) { - return false; - } - - if (this.pan) { - this.hasStartedPan = true; - this.hasCapturedPan = false; - - this.pan.start(detail.startX, detail.startY); - } - - return true; - } - - - // MOVE ************************* - - @Listen('touchmove', { passive: true, enabled: false }) - onTouchMove(ev: TouchEvent) { - this.lastTouch = this.detail.timeStamp = now(ev); - - this.pointerMove(ev); - } - - - @Listen('document:mousemove', { passive: true, enabled: false }) - onMoveMove(ev: TouchEvent) { - const timeStamp = now(ev); - - if (this.lastTouch === 0 || (this.lastTouch + MOUSE_WAIT < timeStamp)) { - this.detail.timeStamp = timeStamp; - this.pointerMove(ev); - } - } - - private pointerMove(ev: UIEvent) { - const detail = this.detail; - this.calcGestureData(ev); - - if (this.pan) { - if (this.hasCapturedPan) { - - if (!this.isMoveQueued) { - this.isMoveQueued = true; - - Ionic.dom.write(() => { - this.isMoveQueued = false; - detail.type = 'pan'; - - if (this.onMove) { - this.onMove(detail); - } else { - Ionic.emit(this, 'ionGestureMove', { detail: this.detail }); - } - }); - } - - } else if (this.pan.detect(detail.currentX, detail.currentY)) { - if (this.pan.isGesture() !== 0) { - if (!this.tryToCapturePan(ev)) { - this.abortGesture(); - } - } - } - } - } - - private calcGestureData(ev: UIEvent) { - const detail = this.detail; - detail.currentX = pointerCoordX(ev); - detail.currentY = pointerCoordY(ev); - detail.deltaX = (detail.currentX - detail.startX); - detail.deltaY = (detail.currentY - detail.startY); - detail.event = ev; - - // figure out which direction we're movin' - detail.directionX = detail.velocityDirectionX = (detail.deltaX > 0 ? 'left' : (detail.deltaX < 0 ? 'right' : null)); - detail.directionY = detail.velocityDirectionY = (detail.deltaY > 0 ? 'up' : (detail.deltaY < 0 ? 'down' : null)); - - const positions = this.positions; - positions.push(detail.currentX, detail.currentY, detail.timeStamp); - - var endPos = (positions.length - 1); - var startPos = endPos; - var timeRange = (detail.timeStamp - 100); - - // move pointer to position measured 100ms ago - for (var i = endPos; i > 0 && positions[i] > timeRange; i -= 3) { - startPos = i; - } - - if (startPos !== endPos) { - // compute relative movement between these two points - var movedX = (positions[startPos - 2] - positions[endPos - 2]); - var movedY = (positions[startPos - 1] - positions[endPos - 1]); - var factor = 16.67 / (positions[endPos] - positions[startPos]); - - // based on XXms compute the movement to apply for each render step - detail.velocityX = movedX * factor; - detail.velocityY = movedY * factor; - - detail.velocityDirectionX = (movedX > 0 ? 'left' : (movedX < 0 ? 'right' : null)); - detail.velocityDirectionY = (movedY > 0 ? 'up' : (movedY < 0 ? 'down' : null)); - } - } - - private tryToCapturePan(ev: UIEvent): boolean { - if (this.gesture && !this.gesture.capture()) { - return false; - } - - this.detail.event = ev; - - if (this.onStart) { - this.onStart(this.detail); - } else { - Ionic.emit(this, 'ionGestureStart', { detail: this.detail }); - } - - this.hasCapturedPan = true; - - return true; - } - - private abortGesture() { - this.hasStartedPan = false; - this.hasCapturedPan = false; - - this.gesture && this.gesture.release(); - - this.enable(false); - this.notCaptured && this.notCaptured(this.detail); - } - - - // END ************************* - - @Listen('touchend', { passive: true, enabled: false }) - onTouchEnd(ev: TouchEvent) { - this.lastTouch = this.detail.timeStamp = now(ev); - - this.pointerUp(ev); - this.enableTouch(false); - } - - - @Listen('document:mouseup', { passive: true, enabled: false }) - onMouseUp(ev: TouchEvent) { - const timeStamp = now(ev); - - if (this.lastTouch === 0 || (this.lastTouch + MOUSE_WAIT < timeStamp)) { - this.detail.timeStamp = timeStamp; - this.pointerUp(ev); - this.enableMouse(false); - } - } - - - private pointerUp(ev: UIEvent) { - const detail = this.detail; - - this.gesture && this.gesture.release(); - - detail.event = ev; - - this.calcGestureData(ev); - - if (this.pan) { - if (this.hasCapturedPan) { - detail.type = 'pan'; - if (this.onEnd) { - this.onEnd(detail); - } else { - Ionic.emit(this, 'ionGestureEnd', { detail: detail }); - } - - } else if (this.hasPress) { - this.detectPress(); - - } else { - if (this.notCaptured) { - this.notCaptured(detail); - } else { - Ionic.emit(this, 'ionGestureNotCaptured', { detail: detail }); - } - } - - } else if (this.hasPress) { - this.detectPress(); - } - - this.hasCapturedPan = false; - this.hasStartedPan = false; - } - - - private detectPress() { - const detail = this.detail; - - if (Math.abs(detail.startX - detail.currentX) < 10 && Math.abs(detail.startY - detail.currentY) < 10) { - detail.type = 'press'; - - if (this.onPress) { - this.onPress(detail); - } else { - Ionic.emit(this, 'ionPress', { detail: detail }); - } - } - } - - - // ENABLE LISTENERS ************************* - - private enableMouse(shouldEnable: boolean) { - if (this.requiresMove) { - Ionic.listener.enable(this, 'document:mousemove', shouldEnable); - } - Ionic.listener.enable(this, 'document:mouseup', shouldEnable); - } - - - private enableTouch(shouldEnable: boolean) { - if (this.requiresMove) { - Ionic.listener.enable(this, 'touchmove', shouldEnable); - } - Ionic.listener.enable(this, 'touchend', shouldEnable); - } - - - private enable(shouldEnable: boolean) { - this.enableMouse(shouldEnable); - this.enableTouch(shouldEnable); - } - - - ionViewDidUnload() { - if (this.blocker) { - this.blocker.destroy(); - this.blocker = null; - } - this.gesture && this.gesture.destroy(); - this.ctrl = this.gesture = this.pan = this.detail = this.detail.event = null; - } - -} - - -const GESTURE_INLINE_STYLES = { - 'touch-action': 'none', - 'user-select': 'none', - '-webkit-user-drag': 'none', - '-webkit-tap-highlight-color': 'rgba(0,0,0,0)' -}; - -const MOUSE_WAIT = 2500; - - -function now(ev: UIEvent) { - return ev.timeStamp || Date.now(); -} - diff --git a/packages/core/src/components/scroll/scroll.ts b/packages/core/src/components/scroll/scroll.ts deleted file mode 100644 index b42d0f543f..0000000000 --- a/packages/core/src/components/scroll/scroll.ts +++ /dev/null @@ -1,361 +0,0 @@ -import { Component, Listen, Prop, Ionic } from '@stencil/core'; -import { GestureController, GestureDelegate } from '../gesture/gesture-controller'; -import { GlobalNamespace, ScrollCallback, ScrollDetail } from '../../utils/interfaces'; -import { Scroll as IScroll } from './scroll-interface'; - - -@Component({ - tag: 'ion-scroll' -}) -export class Scroll implements IScroll { - private $el: HTMLElement; - private gesture: GestureDelegate; - private positions: number[] = []; - private _l: number; - private _t: number; - private tmr: any; - private queued = false; - - isScrolling: boolean = false; - detail: ScrollDetail = {}; - - @Prop() enabled: boolean = true; - @Prop() jsScroll: boolean = false; - @Prop() ionScrollStart: ScrollCallback; - @Prop() ionScroll: ScrollCallback; - @Prop() ionScrollEnd: ScrollCallback; - - - ionViewDidLoad() { - if (Ionic.isServer) return; - - const ctrl = (Ionic).controllers.gesture = ((Ionic).controllers.gesture || new GestureController()); - - this.gesture = ctrl.createGesture('scroll', 100, false); - } - - - // Native Scroll ************************* - - @Listen('scroll', { passive: true }) - onNativeScroll() { - const self = this; - - if (!self.queued && self.enabled) { - self.queued = true; - - Ionic.dom.read(function(timeStamp) { - self.queued = false; - self.onScroll(timeStamp || Date.now()); - }); - } - } - - onScroll(timeStamp: number) { - const self = this; - const detail = self.detail; - const positions = self.positions; - - detail.timeStamp = timeStamp; - - // get the current scrollTop - // ******** DOM READ **************** - detail.scrollTop = self.getTop(); - - // get the current scrollLeft - // ******** DOM READ **************** - detail.scrollLeft = self.getLeft(); - - if (!self.isScrolling) { - // currently not scrolling, so this is a scroll start - self.isScrolling = true; - - // remember the start positions - detail.startY = detail.scrollTop; - detail.startX = detail.scrollLeft; - - // new scroll, so do some resets - detail.velocityY = detail.velocityX = detail.deltaY = detail.deltaX = positions.length = 0; - - // emit only on the first scroll event - if (self.ionScrollStart) { - self.ionScrollStart(detail); - } else { - Ionic.emit(this, 'ionScrollStart', { detail: detail }); - } - } - - detail.directionX = detail.velocityDirectionX = (detail.deltaX > 0 ? 'left' : (detail.deltaX < 0 ? 'right' : null)); - detail.directionY = detail.velocityDirectionY = (detail.deltaY > 0 ? 'up' : (detail.deltaY < 0 ? 'down' : null)); - - // actively scrolling - positions.push(detail.scrollTop, detail.scrollLeft, detail.timeStamp); - - if (positions.length > 3) { - // we've gotten at least 2 scroll events so far - detail.deltaY = (detail.scrollTop - detail.startY); - detail.deltaX = (detail.scrollLeft - detail.startX); - - var endPos = (positions.length - 1); - var startPos = endPos; - var timeRange = (detail.timeStamp - 100); - - // move pointer to position measured 100ms ago - for (var i = endPos; i > 0 && positions[i] > timeRange; i -= 3) { - startPos = i; - } - - if (startPos !== endPos) { - // compute relative movement between these two points - var movedTop = (positions[startPos - 2] - positions[endPos - 2]); - var movedLeft = (positions[startPos - 1] - positions[endPos - 1]); - var factor = 16.67 / (positions[endPos] - positions[startPos]); - - // based on XXms compute the movement to apply for each render step - detail.velocityY = movedTop * factor; - detail.velocityX = movedLeft * factor; - - // figure out which direction we're scrolling - detail.velocityDirectionX = (movedLeft > 0 ? 'left' : (movedLeft < 0 ? 'right' : null)); - detail.velocityDirectionY = (movedTop > 0 ? 'up' : (movedTop < 0 ? 'down' : null)); - } - } - - clearTimeout(self.tmr); - self.tmr = setTimeout(function() { - - // haven't scrolled in a while, so it's a scrollend - self.isScrolling = false; - - Ionic.dom.read(function(timeStamp) { - if (!self.isScrolling) { - self.onEnd(timeStamp); - } - }); - }, 80); - - // emit on each scroll event - if (self.ionScrollStart) { - self.ionScroll(detail); - } else { - Ionic.emit(this, 'ionScroll', { detail: detail }); - } - } - - - onEnd(timeStamp: number) { - const self = this; - const detail = self.detail; - - detail.timeStamp = timeStamp || Date.now(); - - // emit that the scroll has ended - if (self.ionScrollEnd) { - self.ionScrollEnd(detail); - - } else { - Ionic.emit(this, 'ionScrollEnd', { detail: detail }); - } - } - - - enableJsScroll(contentTop: number, contentBottom: number) { - this.jsScroll = true; - - Ionic.listener.enable(this, 'scroll', false); - - Ionic.listener.enable(this, 'touchstart', true); - - contentTop; contentBottom; - } - - - // Touch Scroll ************************* - - @Listen('touchstart', { passive: true, enabled: false }) - onTouchStart() { - if (!this.enabled) { - return; - } - - Ionic.listener.enable(this, 'touchmove', true); - Ionic.listener.enable(this, 'touchend', true); - - throw 'jsScroll: TODO!'; - } - - @Listen('touchmove', { passive: true, enabled: false }) - onTouchMove() { - if (!this.enabled) { - return; - } - } - - @Listen('touchend', { passive: true, enabled: false }) - onTouchEnd() { - Ionic.listener.enable(this, 'touchmove', false); - Ionic.listener.enable(this, 'touchend', false); - - if (!this.enabled) { - return; - } - } - - - /** - * DOM READ - */ - getTop() { - if (this.jsScroll) { - return this._t; - } - return this._t = this.$el.scrollTop; - } - - /** - * DOM READ - */ - getLeft() { - if (this.jsScroll) { - return 0; - } - return this._l = this.$el.scrollLeft; - } - - /** - * DOM WRITE - */ - setTop(top: number) { - this._t = top; - - if (this.jsScroll) { - this.$el.style.transform = this.$el.style.webkitTransform = `translate3d(${this._l * -1}px,${top * -1}px,0px)`; - - } else { - this.$el.scrollTop = top; - } - } - - /** - * DOM WRITE - */ - setLeft(left: number) { - this._l = left; - - if (this.jsScroll) { - this.$el.style.transform = this.$el.style.webkitTransform = `translate3d(${left * -1}px,${this._t * -1}px,0px)`; - - } else { - this.$el.scrollLeft = left; - } - } - - scrollTo(x: number, y: number, duration: number, done?: Function): Promise { - // scroll animation loop w/ easing - // credit https://gist.github.com/dezinezync/5487119 - - let promise: Promise; - if (done === undefined) { - // only create a promise if a done callback wasn't provided - // done can be a null, which avoids any functions - promise = new Promise(resolve => { - done = resolve; - }); - } - - const self = this; - const el = self.$el; - if (!el) { - // invalid element - done(); - return promise; - } - - if (duration < 32) { - self.setTop(y); - self.setLeft(x); - done(); - return promise; - } - - const fromY = el.scrollTop; - const fromX = el.scrollLeft; - - const maxAttempts = (duration / 16) + 100; - - let startTime: number; - let attempts = 0; - let stopScroll = false; - - // scroll loop - function step(timeStamp: number) { - attempts++; - - if (!self.$el || stopScroll || attempts > maxAttempts) { - self.isScrolling = false; - el.style.transform = el.style.webkitTransform = ''; - done(); - return; - } - - let time = Math.min(1, ((timeStamp - startTime) / duration)); - - // where .5 would be 50% of time on a linear scale easedT gives a - // fraction based on the easing method - let easedT = (--time) * time * time + 1; - - if (fromY !== y) { - self.setTop((easedT * (y - fromY)) + fromY); - } - - if (fromX !== x) { - self.setLeft(Math.floor((easedT * (x - fromX)) + fromX)); - } - - if (easedT < 1) { - // do not use DomController here - // must use nativeRaf in order to fire in the next frame - Ionic.dom.raf(step); - - } else { - stopScroll = true; - self.isScrolling = false; - el.style.transform = el.style.webkitTransform = ''; - done(); - } - } - - // start scroll loop - self.isScrolling = true; - - // chill out for a frame first - Ionic.dom.write(() => { - Ionic.dom.write(timeStamp => { - startTime = timeStamp; - step(timeStamp); - }); - }); - - return promise; - } - - scrollToTop(duration: number): Promise { - return this.scrollTo(0, 0, duration); - } - - scrollToBottom(duration: number): Promise { - let y = 0; - if (this.$el) { - y = this.$el.scrollHeight - this.$el.clientHeight; - } - return this.scrollTo(0, y, duration); - } - - - ionViewDidUnload() { - this.gesture && this.gesture.destroy(); - this.gesture = this.detail = this.detail.event = null; - } - -} -