From c08c21a4a09a2823cb824a77a07328c8b99eb860 Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Fri, 2 Dec 2016 10:55:46 +0100 Subject: [PATCH] refactor(gestures): using new DomController --- src/components/item/item-sliding-gesture.ts | 12 ++- src/components/list/list.ts | 6 +- src/components/menu/menu-gestures.ts | 20 ++-- src/components/menu/menu.ts | 69 ++++++------- src/components/nav/nav.ts | 7 +- src/components/nav/overlay-portal.ts | 6 +- src/components/tabs/tab.ts | 7 +- src/components/toggle/toggle-gesture.ts | 11 +- src/components/toggle/toggle.ts | 30 ++++-- src/gestures/drag-gesture.ts | 15 ++- src/gestures/gesture-controller.ts | 4 +- src/navigation/nav-controller-base.ts | 76 +++++++------- src/navigation/swipe-back.ts | 16 +-- src/util/dom-controller.ts | 108 +++++++++++++++----- src/util/mock-providers.ts | 19 +++- 15 files changed, 241 insertions(+), 165 deletions(-) diff --git a/src/components/item/item-sliding-gesture.ts b/src/components/item/item-sliding-gesture.ts index aa1cb142fc..23b6727dd0 100644 --- a/src/components/item/item-sliding-gesture.ts +++ b/src/components/item/item-sliding-gesture.ts @@ -4,7 +4,7 @@ import { List } from '../list/list'; import { GestureController, GesturePriority, GESTURE_ITEM_SWIPE } from '../../gestures/gesture-controller'; import { PanGesture } from '../../gestures/drag-gesture'; import { pointerCoord } from '../../util/dom'; -import { NativeRafDebouncer } from '../../util/debouncer'; +import { DomController } from '../../util/dom-controller'; /** * @private @@ -17,12 +17,16 @@ export class ItemSlidingGesture extends PanGesture { private firstCoordX: number; private firstTimestamp: number; - constructor(public list: List, gestureCtrl: GestureController) { + constructor( + public list: List, + gestureCtrl: GestureController, + domCtrl: DomController + ) { super(list.getNativeElement(), { maxAngle: 20, - threshold: 10, + threshold: 5, zone: false, - debouncer: new NativeRafDebouncer(), + domController: domCtrl, gesture: gestureCtrl.createGesture({ name: GESTURE_ITEM_SWIPE, priority: GesturePriority.SlidingItem, diff --git a/src/components/list/list.ts b/src/components/list/list.ts index 136ba56eec..7ec56b59fa 100644 --- a/src/components/list/list.ts +++ b/src/components/list/list.ts @@ -5,6 +5,7 @@ import { Ion } from '../ion'; import { isTrueProperty } from '../../util/util'; import { ItemSlidingGesture } from '../item/item-sliding-gesture'; import { GestureController } from '../../gestures/gesture-controller'; +import { DomController } from '../../util/dom-controller'; /** * The List is a widely used interface element in almost any mobile app, @@ -53,7 +54,8 @@ export class List extends Ion { config: Config, elementRef: ElementRef, renderer: Renderer, - public _gestureCtrl: GestureController + private _gestureCtrl: GestureController, + private _domCtrl: DomController, ) { super(config, elementRef, renderer, 'list'); } @@ -95,7 +97,7 @@ export class List extends Ion { } else if (!this._slidingGesture) { console.debug('enableSlidingItems'); - this._slidingGesture = new ItemSlidingGesture(this, this._gestureCtrl); + this._slidingGesture = new ItemSlidingGesture(this, this._gestureCtrl, this._domCtrl); this._slidingGesture.listen(); } } diff --git a/src/components/menu/menu-gestures.ts b/src/components/menu/menu-gestures.ts index 6687de97bf..5b3bb2937c 100644 --- a/src/components/menu/menu-gestures.ts +++ b/src/components/menu/menu-gestures.ts @@ -1,9 +1,8 @@ import { Menu } from './menu'; import { SlideEdgeGesture } from '../../gestures/slide-edge-gesture'; import { SlideData } from '../../gestures/slide-gesture'; -import { assign } from '../../util/util'; import { GestureController, GesturePriority, GESTURE_MENU_SWIPE } from '../../gestures/gesture-controller'; -import { NativeRafDebouncer } from '../../util/debouncer'; +import { DomController } from '../../util/dom-controller'; /** * Gesture attached to the content which the menu is assigned to @@ -12,24 +11,23 @@ export class MenuContentGesture extends SlideEdgeGesture { constructor( public menu: Menu, - contentEle: HTMLElement, gestureCtrl: GestureController, - options: any = {} + domCtrl: DomController, ) { - super(contentEle, assign({ + super(document.body, { direction: 'x', edge: menu.side, threshold: 5, maxEdgeStart: menu.maxEdgeStart || 50, zone: false, passive: true, - debouncer: new NativeRafDebouncer(), + domController: domCtrl, gesture: gestureCtrl.createGesture({ name: GESTURE_MENU_SWIPE, priority: GesturePriority.MenuSwipe, disableScroll: true }) - }, options)); + }); } canStart(ev: any): boolean { @@ -48,19 +46,19 @@ export class MenuContentGesture extends SlideEdgeGesture { // Set CSS, then wait one frame for it to apply before sliding starts onSlideBeforeStart(ev: any) { console.debug('menu gesture, onSlideBeforeStart', this.menu.side); - this.menu.swipeBeforeStart(); + this.menu._swipeBeforeStart(); } onSlideStart() { console.debug('menu gesture, onSlideStart', this.menu.side); - this.menu.swipeStart(); + this.menu._swipeStart(); } onSlide(slide: SlideData, ev: any) { let z = (this.menu.side === 'right' ? slide.min : slide.max); let stepValue = (slide.distance / z); - this.menu.swipeProgress(stepValue); + this.menu._swipeProgress(stepValue); } onSlideEnd(slide: SlideData, ev: any) { @@ -84,7 +82,7 @@ export class MenuContentGesture extends SlideEdgeGesture { 'shouldCompleteRight', shouldCompleteRight, 'currentStepValue', currentStepValue); - this.menu.swipeEnd(shouldCompleteLeft, shouldCompleteRight, currentStepValue, velocity); + this.menu._swipeEnd(shouldCompleteLeft, shouldCompleteRight, currentStepValue, velocity); } getElementStartPos(slide: SlideData, ev: any) { diff --git a/src/components/menu/menu.ts b/src/components/menu/menu.ts index 8c9f608733..d9347ac5c5 100644 --- a/src/components/menu/menu.ts +++ b/src/components/menu/menu.ts @@ -12,6 +12,7 @@ import { Platform } from '../../platform/platform'; import { BlockerDelegate, GestureController, GESTURE_GO_BACK_SWIPE } from '../../gestures/gesture-controller'; import { UIEventManager } from '../../util/ui-event-manager'; import { Content } from '../content/content'; +import { DomController } from '../../util/dom-controller'; /** * @name Menu @@ -190,13 +191,14 @@ import { Content } from '../content/content'; encapsulation: ViewEncapsulation.None, }) export class Menu { + private _cntEle: HTMLElement; - private _cntGesture: MenuContentGesture; + private _gesture: MenuContentGesture; private _type: MenuType; private _isEnabled: boolean = true; private _isSwipeEnabled: boolean = true; private _isAnimating: boolean = false; - private _isPers: boolean = false; + private _isPersistent: boolean = false; private _init: boolean = false; private _events: UIEventManager = new UIEventManager(); private _gestureBlocker: BlockerDelegate; @@ -269,11 +271,11 @@ export class Menu { */ @Input() get persistent(): boolean { - return this._isPers; + return this._isPersistent; } set persistent(val: boolean) { - this._isPers = isTrueProperty(val); + this._isPersistent = isTrueProperty(val); } /** @@ -305,7 +307,8 @@ export class Menu { private _keyboard: Keyboard, private _zone: NgZone, private _gestureCtrl: GestureController, - private _app: App + private _domCtrl: DomController, + private _app: App, ) { this._gestureBlocker = _gestureCtrl.createBlocker({ disable: [GESTURE_GO_BACK_SWIPE] @@ -339,7 +342,7 @@ export class Menu { this.setElementAttribute('type', this.type); // add the gestures - this._cntGesture = new MenuContentGesture(this, document, this._gestureCtrl); + this._gesture = new MenuContentGesture(this, this._gestureCtrl, this._domCtrl); // register listeners if this menu is enabled // check if more than one menu is on the same side @@ -362,11 +365,10 @@ export class Menu { /** * @private */ - onBackdropClick(ev: UIEvent): boolean { + onBackdropClick(ev: UIEvent) { ev.preventDefault(); ev.stopPropagation(); this._menuCtrl.close(); - return false; } /** @@ -376,17 +378,17 @@ export class Menu { if (!this._init) { return; } - + const gesture = this._gesture; // only listen/unlisten if the menu has initialized - if (this._isEnabled && this._isSwipeEnabled && !this._cntGesture.isListening) { + if (this._isEnabled && this._isSwipeEnabled && !gesture.isListening) { // should listen, but is not currently listening console.debug('menu, gesture listen', this.side); - this._cntGesture.listen(); + gesture.listen(); - } else if (this._cntGesture.isListening && (!this._isEnabled || !this._isSwipeEnabled)) { + } else if (gesture.isListening && (!this._isEnabled || !this._isSwipeEnabled)) { // should not listen, but is currently listening console.debug('menu, gesture unlisten', this.side); - this._cntGesture.unlisten(); + gesture.unlisten(); } } @@ -424,7 +426,6 @@ export class Menu { }); } - /** * @private */ @@ -435,10 +436,7 @@ export class Menu { this._app.isEnabled(); } - /** - * @private - */ - swipeBeforeStart() { + _swipeBeforeStart() { if (!this.canSwipe()) { assert(false, 'canSwipe() has to be true'); return; @@ -446,44 +444,36 @@ export class Menu { this._before(); } - /** - * @private - */ - swipeStart() { - // user actively dragging the menu + _swipeStart() { if (!this._isAnimating) { assert(false, '_isAnimating has to be true'); return; } + this._getType().setProgressStart(this.isOpen); } - /** - * @private - */ - swipeProgress(stepValue: number) { - // user actively dragging the menu + _swipeProgress(stepValue: number) { if (!this._isAnimating) { assert(false, '_isAnimating has to be true'); return; } - this._getType().setProgessStep(stepValue); - let ionDrag = this.ionDrag; + this._getType().setProgessStep(stepValue); + const ionDrag = this.ionDrag; if (ionDrag.observers.length > 0) { ionDrag.emit(stepValue); } } - /** - * @private - */ - swipeEnd(shouldCompleteLeft: boolean, shouldCompleteRight: boolean, stepValue: number, velocity: number) { + _swipeEnd(shouldCompleteLeft: boolean, shouldCompleteRight: boolean, stepValue: number, velocity: number) { if (!this._isAnimating) { + assert(false, '_isAnimating has to be true'); return; } + // user has finished dragging the menu - let opening = !this.isOpen; + const opening = !this.isOpen; let shouldComplete = false; if (opening) { shouldComplete = (this.side === 'right') ? shouldCompleteLeft : shouldCompleteRight; @@ -511,6 +501,7 @@ export class Menu { private _after(isOpen: boolean) { assert(this._isAnimating, '_before() should be called while animating'); + // keep opening/closing the menu disabled for a touch more yet // only add listeners/css if it's enabled and isOpen // and only remove listeners/css if it's not open @@ -578,8 +569,8 @@ export class Menu { // then find all the other menus on this same side // and automatically disable other same side menus this._menuCtrl.getMenus() - .filter(m => m.side === this.side && m !== this) - .map(m => m.enabled = false); + .filter(m => m.side === this.side && m !== this) + .map(m => m.enabled = false); } // TODO @@ -660,10 +651,10 @@ export class Menu { ngOnDestroy() { this._menuCtrl.unregister(this); this._events.unlistenAll(); - this._cntGesture && this._cntGesture.destroy(); + this._gesture && this._gesture.destroy(); this._type && this._type.destroy(); - this._cntGesture = null; + this._gesture = null; this._type = null; this._cntEle = null; } diff --git a/src/components/nav/nav.ts b/src/components/nav/nav.ts index dcfb752864..6138b73fc8 100644 --- a/src/components/nav/nav.ts +++ b/src/components/nav/nav.ts @@ -10,6 +10,7 @@ import { NavControllerBase } from '../../navigation/nav-controller-base'; import { NavOptions } from '../../navigation/nav-util'; import { TransitionController } from '../../transitions/transition-controller'; import { ViewController } from '../../navigation/view-controller'; +import { DomController } from '../../util/dom-controller'; /** * @name Nav @@ -66,9 +67,10 @@ export class Nav extends NavControllerBase implements AfterViewInit { cfr: ComponentFactoryResolver, gestureCtrl: GestureController, transCtrl: TransitionController, - @Optional() linker: DeepLinker + @Optional() linker: DeepLinker, + domCtrl: DomController, ) { - super(parent, app, config, keyboard, elementRef, zone, renderer, cfr, gestureCtrl, transCtrl, linker); + super(parent, app, config, keyboard, elementRef, zone, renderer, cfr, gestureCtrl, transCtrl, linker, domCtrl); if (viewCtrl) { // an ion-nav can also act as an ion-page within a parent ion-nav @@ -149,6 +151,7 @@ export class Nav extends NavControllerBase implements AfterViewInit { } set swipeBackEnabled(val: boolean) { this._sbEnabled = isTrueProperty(val); + this._swipeBackCheck(); } /** diff --git a/src/components/nav/overlay-portal.ts b/src/components/nav/overlay-portal.ts index 860e62a7a8..3bdc6d1d7d 100644 --- a/src/components/nav/overlay-portal.ts +++ b/src/components/nav/overlay-portal.ts @@ -7,6 +7,7 @@ import { GestureController } from '../../gestures/gesture-controller'; import { Keyboard } from '../../util/keyboard'; import { NavControllerBase } from '../../navigation/nav-controller-base'; import { TransitionController } from '../../transitions/transition-controller'; +import { DomController } from '../../util/dom-controller'; /** * @private @@ -26,9 +27,10 @@ export class OverlayPortal extends NavControllerBase { gestureCtrl: GestureController, transCtrl: TransitionController, @Optional() linker: DeepLinker, - viewPort: ViewContainerRef + viewPort: ViewContainerRef, + domCtrl: DomController, ) { - super(null, app, config, keyboard, elementRef, zone, renderer, cfr, gestureCtrl, transCtrl, linker); + super(null, app, config, keyboard, elementRef, zone, renderer, cfr, gestureCtrl, transCtrl, linker, domCtrl); this._isPortal = true; this._init = true; this.setViewport(viewPort); diff --git a/src/components/tabs/tab.ts b/src/components/tabs/tab.ts index 77c66cf6e6..288ca03d55 100644 --- a/src/components/tabs/tab.ts +++ b/src/components/tabs/tab.ts @@ -13,7 +13,7 @@ import { TabButton } from './tab-button'; import { Tabs } from './tabs'; import { TransitionController } from '../../transitions/transition-controller'; import { ViewController } from '../../navigation/view-controller'; - +import { DomController } from '../../util/dom-controller'; /** * @name Tab @@ -273,10 +273,11 @@ export class Tab extends NavControllerBase { private _cd: ChangeDetectorRef, gestureCtrl: GestureController, transCtrl: TransitionController, - @Optional() private linker: DeepLinker + @Optional() private linker: DeepLinker, + domCtrl: DomController, ) { // A Tab is a NavController for its child pages - super(parent, app, config, keyboard, elementRef, zone, renderer, cfr, gestureCtrl, transCtrl, linker); + super(parent, app, config, keyboard, elementRef, zone, renderer, cfr, gestureCtrl, transCtrl, linker, domCtrl); this.id = parent.add(this); this._tabsHideOnSubPages = config.getBoolean('tabsHideOnSubPages'); diff --git a/src/components/toggle/toggle-gesture.ts b/src/components/toggle/toggle-gesture.ts index c9cbe2236d..c601bb0ee7 100644 --- a/src/components/toggle/toggle-gesture.ts +++ b/src/components/toggle/toggle-gesture.ts @@ -1,7 +1,7 @@ import { GestureController, GesturePriority, GESTURE_TOGGLE } from '../../gestures/gesture-controller'; import { PanGesture } from '../../gestures/drag-gesture'; import { pointerCoord } from '../../util/dom'; -import { NativeRafDebouncer } from '../../util/debouncer'; +import { DomController } from '../../util/dom-controller'; import { Toggle } from './toggle'; /** @@ -9,11 +9,14 @@ import { Toggle } from './toggle'; */ export class ToggleGesture extends PanGesture { - constructor(public toogle: Toggle, gestureCtrl: GestureController) { + constructor( + public toogle: Toggle, + gestureCtrl: GestureController, + domCtrl: DomController + ) { super(toogle.getNativeElement(), { - maxAngle: 20, threshold: 0, - debouncer: new NativeRafDebouncer(), + domController: domCtrl, gesture: gestureCtrl.createGesture({ name: GESTURE_TOGGLE, priority: GesturePriority.Toggle, diff --git a/src/components/toggle/toggle.ts b/src/components/toggle/toggle.ts index d84f977b16..2dc16bd53e 100644 --- a/src/components/toggle/toggle.ts +++ b/src/components/toggle/toggle.ts @@ -2,14 +2,15 @@ import { AfterContentInit, Component, ElementRef, EventEmitter, forwardRef, Host import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { Config } from '../../config/config'; -import { Form, IonicTapInput } from '../../util/form'; -import { isTrueProperty, assert } from '../../util/util'; import { Ion } from '../ion'; import { Item } from '../item/item'; -import { Key } from '../../util/key'; -import { Haptic } from '../../util/haptic'; import { ToggleGesture } from './toggle-gesture'; import { GestureController } from '../../gestures/gesture-controller'; +import { Key } from '../../util/key'; +import { Haptic } from '../../util/haptic'; +import { Form, IonicTapInput } from '../../util/form'; +import { isTrueProperty, assert } from '../../util/util'; +import { DomController } from '../../util/dom-controller'; export const TOGGLE_VALUE_ACCESSOR: any = { provide: NG_VALUE_ACCESSOR, @@ -118,7 +119,8 @@ export class Toggle extends Ion implements IonicTapInput, AfterContentInit, Cont renderer: Renderer, private _haptic: Haptic, @Optional() public _item: Item, - private _gestureCtrl: GestureController + private _gestureCtrl: GestureController, + private _domCtrl: DomController ) { super(config, elementRef, renderer, 'toggle'); _form.register(this); @@ -135,7 +137,7 @@ export class Toggle extends Ion implements IonicTapInput, AfterContentInit, Cont */ ngAfterContentInit() { this._init = true; - this._gesture = new ToggleGesture(this, this._gestureCtrl); + this._gesture = new ToggleGesture(this, this._gestureCtrl, this._domCtrl); this._gesture.listen(); } @@ -143,6 +145,9 @@ export class Toggle extends Ion implements IonicTapInput, AfterContentInit, Cont * @private */ _onDragStart(startX: number) { + assert(startX, 'startX must be valid'); + console.debug('toggle, _onDragStart', startX); + this._startX = startX; this._activated = true; } @@ -151,9 +156,12 @@ export class Toggle extends Ion implements IonicTapInput, AfterContentInit, Cont * @private */ _onDragMove(currentX: number) { - assert(this._startX, '_startX must be valid'); + if (!this._startX) { + assert(false, '_startX must be valid'); + return; + } - console.debug('toggle, pointerMove', currentX); + console.debug('toggle, _onDragMove', currentX); if (this._checked) { if (currentX + 15 < this._startX) { @@ -175,7 +183,11 @@ export class Toggle extends Ion implements IonicTapInput, AfterContentInit, Cont * @private */ _onDragEnd(endX: number) { - assert(this._startX, '_startX must be valid'); + if (!this._startX) { + assert(false, '_startX must be valid'); + return; + } + console.debug('toggle, _onDragEnd', endX); if (this.checked) { if (this._startX + 4 > endX) { diff --git a/src/gestures/drag-gesture.ts b/src/gestures/drag-gesture.ts index 25619e767f..8d42f902ab 100644 --- a/src/gestures/drag-gesture.ts +++ b/src/gestures/drag-gesture.ts @@ -3,7 +3,7 @@ import { GestureDelegate } from '../gestures/gesture-controller'; import { PanRecognizer } from './recognizers'; import { PointerEvents, PointerEventsConfig, UIEventManager } from '../util/ui-event-manager'; import { pointerCoord } from '../util/dom'; -import { Debouncer, FakeDebouncer } from '../util/debouncer'; +import { DomDebouncer, DomController } from '../util/dom-controller'; /** * @private @@ -13,7 +13,7 @@ export interface PanGestureConfig { maxAngle?: number; direction?: 'x' | 'y'; gesture?: GestureDelegate; - debouncer?: Debouncer; + domController?: DomController; zone?: boolean; capture?: boolean; passive?: boolean; @@ -24,7 +24,7 @@ export interface PanGestureConfig { */ export class PanGesture { - private debouncer: Debouncer; + private debouncer: DomDebouncer; private events: UIEventManager = new UIEventManager(false); private pointerEvents: PointerEvents; private detector: PanRecognizer; @@ -44,10 +44,9 @@ export class PanGesture { capture: false, passive: false, }); - - this.debouncer = (opts.debouncer) - ? opts.debouncer - : new FakeDebouncer(); + if (opts.domController) { + this.debouncer = opts.domController.debouncer(); + } this.gestute = opts.gesture; this.direction = opts.direction; this.eventsConfig = { @@ -124,7 +123,7 @@ export class PanGesture { pointerMove(ev: any) { assert(this.started === true, 'started must be true'); if (this.captured) { - this.debouncer.debounce(() => { + this.debouncer.write(() => { this.onDragMove(ev); }); return; diff --git a/src/gestures/gesture-controller.ts b/src/gestures/gesture-controller.ts index a5a8410150..36519a8c0a 100644 --- a/src/gestures/gesture-controller.ts +++ b/src/gestures/gesture-controller.ts @@ -120,6 +120,7 @@ export class GestureController { if (maxPriority === priority) { this.capturedID = id; this.requestedStart = {}; + console.debug(`${gestureName} captured!`); return true; } delete requestedStart[id]; @@ -170,12 +171,13 @@ export class GestureController { canStart(gestureName: string): boolean { if (this.capturedID) { + console.debug(`${gestureName} can not start becuse gesture was already captured`); // a gesture already captured return false; } if (this.isDisabled(gestureName)) { - console.debug('GestureController: Disabled', gestureName); + console.debug(`${gestureName} is disabled`); return false; } return true; diff --git a/src/navigation/nav-controller-base.ts b/src/navigation/nav-controller-base.ts index 82e2dd0f5b..fcdb828c39 100644 --- a/src/navigation/nav-controller-base.ts +++ b/src/navigation/nav-controller-base.ts @@ -17,7 +17,7 @@ import { NavParams } from './nav-params'; import { SwipeBackGesture } from './swipe-back'; import { Transition } from '../transitions/transition'; import { TransitionController } from '../transitions/transition-controller'; - +import { DomController } from '../util/dom-controller'; /** * @private @@ -32,7 +32,6 @@ export class NavControllerBase extends Ion implements NavController { _queue: TransitionInstruction[] = []; _sbEnabled: boolean; _sbGesture: SwipeBackGesture; - _sbThreshold: number; _sbTrns: Transition; _trnsId: number = null; _trnsTm: boolean = false; @@ -60,12 +59,12 @@ export class NavControllerBase extends Ion implements NavController { public _cfr: ComponentFactoryResolver, public _gestureCtrl: GestureController, public _trnsCtrl: TransitionController, - public _linker: DeepLinker + public _linker: DeepLinker, + private _domCtrl: DomController ) { super(config, elementRef, renderer); this._sbEnabled = config.getBoolean('swipeBackEnabled'); - this._sbThreshold = config.getNumber('swipeBackThreshold', 0); this.id = 'n' + (++ctrlIds); } @@ -151,12 +150,12 @@ export class NavControllerBase extends Ion implements NavController { } setRoot(pageOrViewCtrl: any, params?: any, opts?: NavOptions, done?: Function): Promise { - let viewControllers = [convertToView(this._linker, pageOrViewCtrl, params)]; + const viewControllers = [convertToView(this._linker, pageOrViewCtrl, params)]; return this._setPages(viewControllers, opts, done); } setPages(pages: any[], opts?: NavOptions, done?: Function): Promise { - let viewControllers = convertToViews(this._linker, pages); + const viewControllers = convertToViews(this._linker, pages); return this._setPages(viewControllers, opts, done); } @@ -199,7 +198,7 @@ export class NavControllerBase extends Ion implements NavController { // let's see if there's another to kick off this.setTransitioning(false); - this._sbCheck(); + this._swipeBackCheck(); this._nextTrns(); }; @@ -226,7 +225,7 @@ export class NavControllerBase extends Ion implements NavController { // let's see if there's another to kick off this.setTransitioning(false); - this._sbCheck(); + this._swipeBackCheck(); this._nextTrns(); }; @@ -306,7 +305,7 @@ export class NavControllerBase extends Ion implements NavController { assert(isPresent(ti.removeStart), 'removeView needs removeStart'); assert(isPresent(ti.removeCount), 'removeView needs removeCount'); - let index = this._views.indexOf(ti.removeView); + var index = this._views.indexOf(ti.removeView); if (index >= 0) { ti.removeStart += index; } @@ -342,10 +341,10 @@ export class NavControllerBase extends Ion implements NavController { const removeStart = ti.removeStart; if (isPresent(removeStart)) { - const views = this._views; - const removeEnd = removeStart + ti.removeCount; - let i: number; - let view: ViewController; + var views = this._views; + var removeEnd = removeStart + ti.removeCount; + var i: number; + var view: ViewController; for (i = views.length - 1; i >= 0; i--) { view = views[i]; if ((i < removeStart || i >= removeEnd) && view !== leavingView) { @@ -507,7 +506,7 @@ export class NavControllerBase extends Ion implements NavController { const promises: Promise[] = []; if (leavingView) { - const leavingTestResult = leavingView._lifecycleTest('Leave'); + var leavingTestResult = leavingView._lifecycleTest('Leave'); if (leavingTestResult === false) { // synchronous reject @@ -520,7 +519,7 @@ export class NavControllerBase extends Ion implements NavController { } if (enteringView) { - const enteringTestResult = enteringView._lifecycleTest('Enter'); + var enteringTestResult = enteringView._lifecycleTest('Enter'); if (enteringTestResult === false) { // synchronous reject @@ -564,7 +563,7 @@ export class NavControllerBase extends Ion implements NavController { direction: opts.direction, duration: (opts.animate === false ? 0 : opts.duration), easing: opts.easing, - isRTL: this.config.platform.isRTL(), + isRTL: this._config.platform.isRTL(), ev: opts.ev, }; @@ -627,9 +626,9 @@ export class NavControllerBase extends Ion implements NavController { // we should animate (duration > 0) if the pushed page is not the first one (startup) // or if it is a portal (modal, actionsheet, etc.) - let isFirstPage = !this._init && this._views.length === 1; - let shouldNotAnimate = isFirstPage && !this._isPortal; - let canNotAnimate = this.config.get('animate') === false; + const isFirstPage = !this._init && this._views.length === 1; + const shouldNotAnimate = isFirstPage && !this._isPortal; + const canNotAnimate = this._config.get('animate') === false; if (shouldNotAnimate || canNotAnimate) { opts.animate = false; } @@ -743,7 +742,7 @@ export class NavControllerBase extends Ion implements NavController { } _insertViewAt(view: ViewController, index: number) { - var existingIndex = this._views.indexOf(view); + const existingIndex = this._views.indexOf(view); if (existingIndex > -1) { // this view is already in the stack!! // move it to its new location @@ -897,10 +896,14 @@ export class NavControllerBase extends Ion implements NavController { } destroy() { - for (var view of this._views) { + const views = this._views; + let view: ViewController; + for (var i = 0; i < views.length; i++) { + view = views[i]; view._willUnload(); view._destroy(this._renderer); } + // purge stack this._views.length = 0; @@ -947,44 +950,35 @@ export class NavControllerBase extends Ion implements NavController { swipeBackEnd(shouldComplete: boolean, currentStepValue: number, velocity: number) { if (this._sbTrns && this._sbGesture) { // the swipe back gesture has ended - const dur = this._sbTrns.getDuration() / (Math.abs(velocity) + 1); + var dur = this._sbTrns.getDuration() / (Math.abs(velocity) + 1); this._sbTrns.progressEnd(shouldComplete, currentStepValue, dur); } } - _sbCheck() { - if (!this._sbEnabled && this._isPortal) { - return; - } - - // this nav controller can have swipe to go back - if (!this._sbGesture) { - // create the swipe back gesture if we haven't already - const opts = { - edge: 'left', - threshold: this._sbThreshold - }; - this._sbGesture = new SwipeBackGesture(this, document.body, this._gestureCtrl, opts); - } - + _swipeBackCheck() { if (this.canSwipeBack()) { + if (!this._sbGesture) { + this._sbGesture = new SwipeBackGesture(this, this._gestureCtrl, this._domCtrl); + } this._sbGesture.listen(); - } else { + + } else if (this._sbGesture) { this._sbGesture.unlisten(); } } canSwipeBack(): boolean { return (this._sbEnabled && - !this._children.length && - !this.isTransitioning() && + !this._isPortal && + !this._children.length && + !this.isTransitioning() && this._app.isEnabled() && this.canGoBack()); } canGoBack(): boolean { const activeView = this.getActive(); - return !!(activeView && activeView.enableBack()) || false; + return !!(activeView && activeView.enableBack()); } isTransitioning(): boolean { diff --git a/src/navigation/swipe-back.ts b/src/navigation/swipe-back.ts index 71f6558c65..ad34046efc 100644 --- a/src/navigation/swipe-back.ts +++ b/src/navigation/swipe-back.ts @@ -1,9 +1,9 @@ -import { assign, swipeShouldReset } from '../util/util'; +import { swipeShouldReset } from '../util/util'; import { GestureController, GesturePriority, GESTURE_GO_BACK_SWIPE } from '../gestures/gesture-controller'; import { NavControllerBase } from './nav-controller-base'; import { SlideData } from '../gestures/slide-gesture'; import { SlideEdgeGesture } from '../gestures/slide-edge-gesture'; -import { NativeRafDebouncer } from '../util/debouncer'; +import { DomController } from '../util/dom-controller'; /** * @private @@ -12,22 +12,22 @@ export class SwipeBackGesture extends SlideEdgeGesture { constructor( private _nav: NavControllerBase, - element: HTMLElement, gestureCtlr: GestureController, - options: any, + domCtrl: DomController, ) { - super(element, assign({ + super(document.body, { direction: 'x', + edge: 'left', maxEdgeStart: 75, - zone: false, threshold: 5, - debouncer: new NativeRafDebouncer(), + zone: false, + domController: domCtrl, gesture: gestureCtlr.createGesture({ name: GESTURE_GO_BACK_SWIPE, priority: GesturePriority.GoBackSwipe, disableScroll: true }) - }, options)); + }); } canStart(ev: any): boolean { diff --git a/src/util/dom-controller.ts b/src/util/dom-controller.ts index 7fad2ada51..94e8123c8a 100644 --- a/src/util/dom-controller.ts +++ b/src/util/dom-controller.ts @@ -7,29 +7,83 @@ import { nativeRaf } from './dom'; import { removeArrayItem } from './util'; +export type DomCallback = { (timeStamp: number) }; + +export class DomDebouncer { + + private writeTask: Function = null; + private readTask: Function = null; + + constructor(private dom: DomController) { } + + read(fn: DomCallback): Function { + if (this.readTask) { + return; + } + return this.readTask = this.dom.read((t) => { + this.readTask = null; + fn(t); + }); + } + + write(fn: DomCallback, ctx?: any): Function { + if (this.writeTask) { + return; + } + + return this.writeTask = this.dom.write((t) => { + this.writeTask = null; + fn(t); + }); + } + + cancel() { + const writeTask = this.writeTask; + writeTask && this.dom.cancelW(writeTask); + this.writeTask = null; + + const readTask = this.readTask; + readTask && this.dom.cancelR(readTask); + this.readTask = null; + } +} + export class DomController { + private r: Function[] = []; private w: Function[] = []; private q: boolean; - read(fn: {(timeStamp: number)}, ctx?: any): Function { + debouncer(): DomDebouncer { + return new DomDebouncer(this); + } + + read(fn: DomCallback, ctx?: any): Function { const task = !ctx ? fn : fn.bind(ctx); this.r.push(task); this.queue(); return task; } - write(fn: {(timeStamp: number)}, ctx?: any): Function { + write(fn: DomCallback, ctx?: any): Function { const task = !ctx ? fn : fn.bind(ctx); this.w.push(task); this.queue(); return task; } - cancel(task: any) { + cancel(task: any): boolean { return removeArrayItem(this.r, task) || removeArrayItem(this.w, task); } + cancelW(task: any): boolean { + return removeArrayItem(this.w, task); + } + + cancelR(task: any): boolean { + return removeArrayItem(this.r, task); + } + protected queue() { const self = this; if (!self.q) { @@ -41,32 +95,32 @@ export class DomController { } protected flush(timeStamp: number) { - let err; - let task; - try { - // ******** DOM READS **************** - while (task = this.r.shift()) { - task(timeStamp); - } - - // ******** DOM WRITES **************** - while (task = this.w.shift()) { - task(timeStamp); - } - } catch (e) { - err = e; - } - - this.q = false; - - if (this.r.length || this.w.length) { - this.queue(); - } - - if (err) { - throw err; + this.dispatch(timeStamp); + } finally { + this.q = false; } } + private dispatch(timeStamp: number) { + let i: number; + const r = this.r; + const rLen = r.length; + const w = this.w; + const wLen = w.length; + + // ******** DOM READS **************** + for (i = 0; i < rLen; i++) { + r[i](timeStamp); + } + + // ******** DOM WRITES **************** + for (i = 0; i < wLen; i++) { + w[i](timeStamp); + } + + r.length = 0; + w.length = 0; + } + } diff --git a/src/util/mock-providers.ts b/src/util/mock-providers.ts index 166bd753e6..160ee9c502 100644 --- a/src/util/mock-providers.ts +++ b/src/util/mock-providers.ts @@ -22,6 +22,7 @@ import { ViewController } from '../navigation/view-controller'; import { NavControllerBase } from '../navigation/nav-controller-base'; import { Haptic } from './haptic'; +import { DomController } from './dom-controller'; export const mockConfig = function(config?: any, url: string = '/', platform?: Platform) { const c = new Config(); @@ -246,6 +247,8 @@ export const mockNavController = function(): NavControllerBase { let trnsCtrl = mockTrasitionController(config); + let dom = new DomController(); + let nav = new NavControllerBase( null, app, @@ -257,7 +260,8 @@ export const mockNavController = function(): NavControllerBase { componentFactoryResolver, gestureCtrl, trnsCtrl, - linker + linker, + dom, ); nav._viewInit = function(enteringView: ViewController) { @@ -296,6 +300,8 @@ export const mockOverlayPortal = function(app: App, config: Config, platform: Pl let deepLinker = new DeepLinker(app, serializer, location); + let dom = new DomController(); + return new OverlayPortal( app, config, @@ -307,7 +313,8 @@ export const mockOverlayPortal = function(app: App, config: Config, platform: Pl gestureCtrl, null, deepLinker, - null + null, + dom ); }; @@ -334,6 +341,8 @@ export const mockTab = function(parentTabs: Tabs): Tab { let linker = mockDeepLinker(null, app); + let dom = new DomController(); + let tab = new Tab( parentTabs, app, @@ -346,7 +355,8 @@ export const mockTab = function(parentTabs: Tabs): Tab { changeDetectorRef, gestureCtrl, null, - linker + linker, + dom ); tab.load = (opts: any, cb: Function) => { @@ -371,7 +381,8 @@ export const mockTabs = function(app?: App): Tabs { export const mockMenu = function (): Menu { let app = mockApp(); let gestureCtrl = new GestureController(app); - return new Menu(null, null, null, null, null, null, null, gestureCtrl, app); + let dom = new DomController(); + return new Menu(null, null, null, null, null, null, null, gestureCtrl, dom, app); }; export const mockDeepLinkConfig = function(links?: any[]): DeepLinkConfig {