refactor(gestures): using new DomController

This commit is contained in:
Manu Mtz.-Almeida
2016-12-02 10:55:46 +01:00
parent 3532a71c29
commit c08c21a4a0
15 changed files with 241 additions and 165 deletions

View File

@ -4,7 +4,7 @@ import { List } from '../list/list';
import { GestureController, GesturePriority, GESTURE_ITEM_SWIPE } from '../../gestures/gesture-controller'; import { GestureController, GesturePriority, GESTURE_ITEM_SWIPE } from '../../gestures/gesture-controller';
import { PanGesture } from '../../gestures/drag-gesture'; import { PanGesture } from '../../gestures/drag-gesture';
import { pointerCoord } from '../../util/dom'; import { pointerCoord } from '../../util/dom';
import { NativeRafDebouncer } from '../../util/debouncer'; import { DomController } from '../../util/dom-controller';
/** /**
* @private * @private
@ -17,12 +17,16 @@ export class ItemSlidingGesture extends PanGesture {
private firstCoordX: number; private firstCoordX: number;
private firstTimestamp: number; private firstTimestamp: number;
constructor(public list: List, gestureCtrl: GestureController) { constructor(
public list: List,
gestureCtrl: GestureController,
domCtrl: DomController
) {
super(list.getNativeElement(), { super(list.getNativeElement(), {
maxAngle: 20, maxAngle: 20,
threshold: 10, threshold: 5,
zone: false, zone: false,
debouncer: new NativeRafDebouncer(), domController: domCtrl,
gesture: gestureCtrl.createGesture({ gesture: gestureCtrl.createGesture({
name: GESTURE_ITEM_SWIPE, name: GESTURE_ITEM_SWIPE,
priority: GesturePriority.SlidingItem, priority: GesturePriority.SlidingItem,

View File

@ -5,6 +5,7 @@ import { Ion } from '../ion';
import { isTrueProperty } from '../../util/util'; import { isTrueProperty } from '../../util/util';
import { ItemSlidingGesture } from '../item/item-sliding-gesture'; import { ItemSlidingGesture } from '../item/item-sliding-gesture';
import { GestureController } from '../../gestures/gesture-controller'; 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, * The List is a widely used interface element in almost any mobile app,
@ -53,7 +54,8 @@ export class List extends Ion {
config: Config, config: Config,
elementRef: ElementRef, elementRef: ElementRef,
renderer: Renderer, renderer: Renderer,
public _gestureCtrl: GestureController private _gestureCtrl: GestureController,
private _domCtrl: DomController,
) { ) {
super(config, elementRef, renderer, 'list'); super(config, elementRef, renderer, 'list');
} }
@ -95,7 +97,7 @@ export class List extends Ion {
} else if (!this._slidingGesture) { } else if (!this._slidingGesture) {
console.debug('enableSlidingItems'); console.debug('enableSlidingItems');
this._slidingGesture = new ItemSlidingGesture(this, this._gestureCtrl); this._slidingGesture = new ItemSlidingGesture(this, this._gestureCtrl, this._domCtrl);
this._slidingGesture.listen(); this._slidingGesture.listen();
} }
} }

View File

@ -1,9 +1,8 @@
import { Menu } from './menu'; import { Menu } from './menu';
import { SlideEdgeGesture } from '../../gestures/slide-edge-gesture'; import { SlideEdgeGesture } from '../../gestures/slide-edge-gesture';
import { SlideData } from '../../gestures/slide-gesture'; import { SlideData } from '../../gestures/slide-gesture';
import { assign } from '../../util/util';
import { GestureController, GesturePriority, GESTURE_MENU_SWIPE } from '../../gestures/gesture-controller'; 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 * Gesture attached to the content which the menu is assigned to
@ -12,24 +11,23 @@ export class MenuContentGesture extends SlideEdgeGesture {
constructor( constructor(
public menu: Menu, public menu: Menu,
contentEle: HTMLElement,
gestureCtrl: GestureController, gestureCtrl: GestureController,
options: any = {} domCtrl: DomController,
) { ) {
super(contentEle, assign({ super(document.body, {
direction: 'x', direction: 'x',
edge: menu.side, edge: menu.side,
threshold: 5, threshold: 5,
maxEdgeStart: menu.maxEdgeStart || 50, maxEdgeStart: menu.maxEdgeStart || 50,
zone: false, zone: false,
passive: true, passive: true,
debouncer: new NativeRafDebouncer(), domController: domCtrl,
gesture: gestureCtrl.createGesture({ gesture: gestureCtrl.createGesture({
name: GESTURE_MENU_SWIPE, name: GESTURE_MENU_SWIPE,
priority: GesturePriority.MenuSwipe, priority: GesturePriority.MenuSwipe,
disableScroll: true disableScroll: true
}) })
}, options)); });
} }
canStart(ev: any): boolean { 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 // Set CSS, then wait one frame for it to apply before sliding starts
onSlideBeforeStart(ev: any) { onSlideBeforeStart(ev: any) {
console.debug('menu gesture, onSlideBeforeStart', this.menu.side); console.debug('menu gesture, onSlideBeforeStart', this.menu.side);
this.menu.swipeBeforeStart(); this.menu._swipeBeforeStart();
} }
onSlideStart() { onSlideStart() {
console.debug('menu gesture, onSlideStart', this.menu.side); console.debug('menu gesture, onSlideStart', this.menu.side);
this.menu.swipeStart(); this.menu._swipeStart();
} }
onSlide(slide: SlideData, ev: any) { onSlide(slide: SlideData, ev: any) {
let z = (this.menu.side === 'right' ? slide.min : slide.max); let z = (this.menu.side === 'right' ? slide.min : slide.max);
let stepValue = (slide.distance / z); let stepValue = (slide.distance / z);
this.menu.swipeProgress(stepValue); this.menu._swipeProgress(stepValue);
} }
onSlideEnd(slide: SlideData, ev: any) { onSlideEnd(slide: SlideData, ev: any) {
@ -84,7 +82,7 @@ export class MenuContentGesture extends SlideEdgeGesture {
'shouldCompleteRight', shouldCompleteRight, 'shouldCompleteRight', shouldCompleteRight,
'currentStepValue', currentStepValue); 'currentStepValue', currentStepValue);
this.menu.swipeEnd(shouldCompleteLeft, shouldCompleteRight, currentStepValue, velocity); this.menu._swipeEnd(shouldCompleteLeft, shouldCompleteRight, currentStepValue, velocity);
} }
getElementStartPos(slide: SlideData, ev: any) { getElementStartPos(slide: SlideData, ev: any) {

View File

@ -12,6 +12,7 @@ import { Platform } from '../../platform/platform';
import { BlockerDelegate, GestureController, GESTURE_GO_BACK_SWIPE } from '../../gestures/gesture-controller'; import { BlockerDelegate, GestureController, GESTURE_GO_BACK_SWIPE } from '../../gestures/gesture-controller';
import { UIEventManager } from '../../util/ui-event-manager'; import { UIEventManager } from '../../util/ui-event-manager';
import { Content } from '../content/content'; import { Content } from '../content/content';
import { DomController } from '../../util/dom-controller';
/** /**
* @name Menu * @name Menu
@ -190,13 +191,14 @@ import { Content } from '../content/content';
encapsulation: ViewEncapsulation.None, encapsulation: ViewEncapsulation.None,
}) })
export class Menu { export class Menu {
private _cntEle: HTMLElement; private _cntEle: HTMLElement;
private _cntGesture: MenuContentGesture; private _gesture: MenuContentGesture;
private _type: MenuType; private _type: MenuType;
private _isEnabled: boolean = true; private _isEnabled: boolean = true;
private _isSwipeEnabled: boolean = true; private _isSwipeEnabled: boolean = true;
private _isAnimating: boolean = false; private _isAnimating: boolean = false;
private _isPers: boolean = false; private _isPersistent: boolean = false;
private _init: boolean = false; private _init: boolean = false;
private _events: UIEventManager = new UIEventManager(); private _events: UIEventManager = new UIEventManager();
private _gestureBlocker: BlockerDelegate; private _gestureBlocker: BlockerDelegate;
@ -269,11 +271,11 @@ export class Menu {
*/ */
@Input() @Input()
get persistent(): boolean { get persistent(): boolean {
return this._isPers; return this._isPersistent;
} }
set persistent(val: boolean) { set persistent(val: boolean) {
this._isPers = isTrueProperty(val); this._isPersistent = isTrueProperty(val);
} }
/** /**
@ -305,7 +307,8 @@ export class Menu {
private _keyboard: Keyboard, private _keyboard: Keyboard,
private _zone: NgZone, private _zone: NgZone,
private _gestureCtrl: GestureController, private _gestureCtrl: GestureController,
private _app: App private _domCtrl: DomController,
private _app: App,
) { ) {
this._gestureBlocker = _gestureCtrl.createBlocker({ this._gestureBlocker = _gestureCtrl.createBlocker({
disable: [GESTURE_GO_BACK_SWIPE] disable: [GESTURE_GO_BACK_SWIPE]
@ -339,7 +342,7 @@ export class Menu {
this.setElementAttribute('type', this.type); this.setElementAttribute('type', this.type);
// add the gestures // add the gestures
this._cntGesture = new MenuContentGesture(this, <any>document, this._gestureCtrl); this._gesture = new MenuContentGesture(this, this._gestureCtrl, this._domCtrl);
// register listeners if this menu is enabled // register listeners if this menu is enabled
// check if more than one menu is on the same side // check if more than one menu is on the same side
@ -362,11 +365,10 @@ export class Menu {
/** /**
* @private * @private
*/ */
onBackdropClick(ev: UIEvent): boolean { onBackdropClick(ev: UIEvent) {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
this._menuCtrl.close(); this._menuCtrl.close();
return false;
} }
/** /**
@ -376,17 +378,17 @@ export class Menu {
if (!this._init) { if (!this._init) {
return; return;
} }
const gesture = this._gesture;
// only listen/unlisten if the menu has initialized // 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 // should listen, but is not currently listening
console.debug('menu, gesture listen', this.side); 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 // should not listen, but is currently listening
console.debug('menu, gesture unlisten', this.side); console.debug('menu, gesture unlisten', this.side);
this._cntGesture.unlisten(); gesture.unlisten();
} }
} }
@ -424,7 +426,6 @@ export class Menu {
}); });
} }
/** /**
* @private * @private
*/ */
@ -435,10 +436,7 @@ export class Menu {
this._app.isEnabled(); this._app.isEnabled();
} }
/** _swipeBeforeStart() {
* @private
*/
swipeBeforeStart() {
if (!this.canSwipe()) { if (!this.canSwipe()) {
assert(false, 'canSwipe() has to be true'); assert(false, 'canSwipe() has to be true');
return; return;
@ -446,44 +444,36 @@ export class Menu {
this._before(); this._before();
} }
/** _swipeStart() {
* @private
*/
swipeStart() {
// user actively dragging the menu
if (!this._isAnimating) { if (!this._isAnimating) {
assert(false, '_isAnimating has to be true'); assert(false, '_isAnimating has to be true');
return; return;
} }
this._getType().setProgressStart(this.isOpen); this._getType().setProgressStart(this.isOpen);
} }
/** _swipeProgress(stepValue: number) {
* @private
*/
swipeProgress(stepValue: number) {
// user actively dragging the menu
if (!this._isAnimating) { if (!this._isAnimating) {
assert(false, '_isAnimating has to be true'); assert(false, '_isAnimating has to be true');
return; return;
} }
this._getType().setProgessStep(stepValue);
let ionDrag = this.ionDrag; this._getType().setProgessStep(stepValue);
const ionDrag = this.ionDrag;
if (ionDrag.observers.length > 0) { if (ionDrag.observers.length > 0) {
ionDrag.emit(stepValue); ionDrag.emit(stepValue);
} }
} }
/** _swipeEnd(shouldCompleteLeft: boolean, shouldCompleteRight: boolean, stepValue: number, velocity: number) {
* @private
*/
swipeEnd(shouldCompleteLeft: boolean, shouldCompleteRight: boolean, stepValue: number, velocity: number) {
if (!this._isAnimating) { if (!this._isAnimating) {
assert(false, '_isAnimating has to be true');
return; return;
} }
// user has finished dragging the menu // user has finished dragging the menu
let opening = !this.isOpen; const opening = !this.isOpen;
let shouldComplete = false; let shouldComplete = false;
if (opening) { if (opening) {
shouldComplete = (this.side === 'right') ? shouldCompleteLeft : shouldCompleteRight; shouldComplete = (this.side === 'right') ? shouldCompleteLeft : shouldCompleteRight;
@ -511,6 +501,7 @@ export class Menu {
private _after(isOpen: boolean) { private _after(isOpen: boolean) {
assert(this._isAnimating, '_before() should be called while animating'); assert(this._isAnimating, '_before() should be called while animating');
// keep opening/closing the menu disabled for a touch more yet // keep opening/closing the menu disabled for a touch more yet
// only add listeners/css if it's enabled and isOpen // only add listeners/css if it's enabled and isOpen
// and only remove listeners/css if it's not open // and only remove listeners/css if it's not open
@ -660,10 +651,10 @@ export class Menu {
ngOnDestroy() { ngOnDestroy() {
this._menuCtrl.unregister(this); this._menuCtrl.unregister(this);
this._events.unlistenAll(); this._events.unlistenAll();
this._cntGesture && this._cntGesture.destroy(); this._gesture && this._gesture.destroy();
this._type && this._type.destroy(); this._type && this._type.destroy();
this._cntGesture = null; this._gesture = null;
this._type = null; this._type = null;
this._cntEle = null; this._cntEle = null;
} }

View File

@ -10,6 +10,7 @@ import { NavControllerBase } from '../../navigation/nav-controller-base';
import { NavOptions } from '../../navigation/nav-util'; import { NavOptions } from '../../navigation/nav-util';
import { TransitionController } from '../../transitions/transition-controller'; import { TransitionController } from '../../transitions/transition-controller';
import { ViewController } from '../../navigation/view-controller'; import { ViewController } from '../../navigation/view-controller';
import { DomController } from '../../util/dom-controller';
/** /**
* @name Nav * @name Nav
@ -66,9 +67,10 @@ export class Nav extends NavControllerBase implements AfterViewInit {
cfr: ComponentFactoryResolver, cfr: ComponentFactoryResolver,
gestureCtrl: GestureController, gestureCtrl: GestureController,
transCtrl: TransitionController, 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) { if (viewCtrl) {
// an ion-nav can also act as an ion-page within a parent ion-nav // 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) { set swipeBackEnabled(val: boolean) {
this._sbEnabled = isTrueProperty(val); this._sbEnabled = isTrueProperty(val);
this._swipeBackCheck();
} }
/** /**

View File

@ -7,6 +7,7 @@ import { GestureController } from '../../gestures/gesture-controller';
import { Keyboard } from '../../util/keyboard'; import { Keyboard } from '../../util/keyboard';
import { NavControllerBase } from '../../navigation/nav-controller-base'; import { NavControllerBase } from '../../navigation/nav-controller-base';
import { TransitionController } from '../../transitions/transition-controller'; import { TransitionController } from '../../transitions/transition-controller';
import { DomController } from '../../util/dom-controller';
/** /**
* @private * @private
@ -26,9 +27,10 @@ export class OverlayPortal extends NavControllerBase {
gestureCtrl: GestureController, gestureCtrl: GestureController,
transCtrl: TransitionController, transCtrl: TransitionController,
@Optional() linker: DeepLinker, @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._isPortal = true;
this._init = true; this._init = true;
this.setViewport(viewPort); this.setViewport(viewPort);

View File

@ -13,7 +13,7 @@ import { TabButton } from './tab-button';
import { Tabs } from './tabs'; import { Tabs } from './tabs';
import { TransitionController } from '../../transitions/transition-controller'; import { TransitionController } from '../../transitions/transition-controller';
import { ViewController } from '../../navigation/view-controller'; import { ViewController } from '../../navigation/view-controller';
import { DomController } from '../../util/dom-controller';
/** /**
* @name Tab * @name Tab
@ -273,10 +273,11 @@ export class Tab extends NavControllerBase {
private _cd: ChangeDetectorRef, private _cd: ChangeDetectorRef,
gestureCtrl: GestureController, gestureCtrl: GestureController,
transCtrl: TransitionController, transCtrl: TransitionController,
@Optional() private linker: DeepLinker @Optional() private linker: DeepLinker,
domCtrl: DomController,
) { ) {
// A Tab is a NavController for its child pages // 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.id = parent.add(this);
this._tabsHideOnSubPages = config.getBoolean('tabsHideOnSubPages'); this._tabsHideOnSubPages = config.getBoolean('tabsHideOnSubPages');

View File

@ -1,7 +1,7 @@
import { GestureController, GesturePriority, GESTURE_TOGGLE } from '../../gestures/gesture-controller'; import { GestureController, GesturePriority, GESTURE_TOGGLE } from '../../gestures/gesture-controller';
import { PanGesture } from '../../gestures/drag-gesture'; import { PanGesture } from '../../gestures/drag-gesture';
import { pointerCoord } from '../../util/dom'; import { pointerCoord } from '../../util/dom';
import { NativeRafDebouncer } from '../../util/debouncer'; import { DomController } from '../../util/dom-controller';
import { Toggle } from './toggle'; import { Toggle } from './toggle';
/** /**
@ -9,11 +9,14 @@ import { Toggle } from './toggle';
*/ */
export class ToggleGesture extends PanGesture { export class ToggleGesture extends PanGesture {
constructor(public toogle: Toggle, gestureCtrl: GestureController) { constructor(
public toogle: Toggle,
gestureCtrl: GestureController,
domCtrl: DomController
) {
super(toogle.getNativeElement(), { super(toogle.getNativeElement(), {
maxAngle: 20,
threshold: 0, threshold: 0,
debouncer: new NativeRafDebouncer(), domController: domCtrl,
gesture: gestureCtrl.createGesture({ gesture: gestureCtrl.createGesture({
name: GESTURE_TOGGLE, name: GESTURE_TOGGLE,
priority: GesturePriority.Toggle, priority: GesturePriority.Toggle,

View File

@ -2,14 +2,15 @@ import { AfterContentInit, Component, ElementRef, EventEmitter, forwardRef, Host
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Config } from '../../config/config'; import { Config } from '../../config/config';
import { Form, IonicTapInput } from '../../util/form';
import { isTrueProperty, assert } from '../../util/util';
import { Ion } from '../ion'; import { Ion } from '../ion';
import { Item } from '../item/item'; import { Item } from '../item/item';
import { Key } from '../../util/key';
import { Haptic } from '../../util/haptic';
import { ToggleGesture } from './toggle-gesture'; import { ToggleGesture } from './toggle-gesture';
import { GestureController } from '../../gestures/gesture-controller'; 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 = { export const TOGGLE_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR, provide: NG_VALUE_ACCESSOR,
@ -118,7 +119,8 @@ export class Toggle extends Ion implements IonicTapInput, AfterContentInit, Cont
renderer: Renderer, renderer: Renderer,
private _haptic: Haptic, private _haptic: Haptic,
@Optional() public _item: Item, @Optional() public _item: Item,
private _gestureCtrl: GestureController private _gestureCtrl: GestureController,
private _domCtrl: DomController
) { ) {
super(config, elementRef, renderer, 'toggle'); super(config, elementRef, renderer, 'toggle');
_form.register(this); _form.register(this);
@ -135,7 +137,7 @@ export class Toggle extends Ion implements IonicTapInput, AfterContentInit, Cont
*/ */
ngAfterContentInit() { ngAfterContentInit() {
this._init = true; this._init = true;
this._gesture = new ToggleGesture(this, this._gestureCtrl); this._gesture = new ToggleGesture(this, this._gestureCtrl, this._domCtrl);
this._gesture.listen(); this._gesture.listen();
} }
@ -143,6 +145,9 @@ export class Toggle extends Ion implements IonicTapInput, AfterContentInit, Cont
* @private * @private
*/ */
_onDragStart(startX: number) { _onDragStart(startX: number) {
assert(startX, 'startX must be valid');
console.debug('toggle, _onDragStart', startX);
this._startX = startX; this._startX = startX;
this._activated = true; this._activated = true;
} }
@ -151,9 +156,12 @@ export class Toggle extends Ion implements IonicTapInput, AfterContentInit, Cont
* @private * @private
*/ */
_onDragMove(currentX: number) { _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 (this._checked) {
if (currentX + 15 < this._startX) { if (currentX + 15 < this._startX) {
@ -175,7 +183,11 @@ export class Toggle extends Ion implements IonicTapInput, AfterContentInit, Cont
* @private * @private
*/ */
_onDragEnd(endX: number) { _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.checked) {
if (this._startX + 4 > endX) { if (this._startX + 4 > endX) {

View File

@ -3,7 +3,7 @@ import { GestureDelegate } from '../gestures/gesture-controller';
import { PanRecognizer } from './recognizers'; import { PanRecognizer } from './recognizers';
import { PointerEvents, PointerEventsConfig, UIEventManager } from '../util/ui-event-manager'; import { PointerEvents, PointerEventsConfig, UIEventManager } from '../util/ui-event-manager';
import { pointerCoord } from '../util/dom'; import { pointerCoord } from '../util/dom';
import { Debouncer, FakeDebouncer } from '../util/debouncer'; import { DomDebouncer, DomController } from '../util/dom-controller';
/** /**
* @private * @private
@ -13,7 +13,7 @@ export interface PanGestureConfig {
maxAngle?: number; maxAngle?: number;
direction?: 'x' | 'y'; direction?: 'x' | 'y';
gesture?: GestureDelegate; gesture?: GestureDelegate;
debouncer?: Debouncer; domController?: DomController;
zone?: boolean; zone?: boolean;
capture?: boolean; capture?: boolean;
passive?: boolean; passive?: boolean;
@ -24,7 +24,7 @@ export interface PanGestureConfig {
*/ */
export class PanGesture { export class PanGesture {
private debouncer: Debouncer; private debouncer: DomDebouncer;
private events: UIEventManager = new UIEventManager(false); private events: UIEventManager = new UIEventManager(false);
private pointerEvents: PointerEvents; private pointerEvents: PointerEvents;
private detector: PanRecognizer; private detector: PanRecognizer;
@ -44,10 +44,9 @@ export class PanGesture {
capture: false, capture: false,
passive: false, passive: false,
}); });
if (opts.domController) {
this.debouncer = (opts.debouncer) this.debouncer = opts.domController.debouncer();
? opts.debouncer }
: new FakeDebouncer();
this.gestute = opts.gesture; this.gestute = opts.gesture;
this.direction = opts.direction; this.direction = opts.direction;
this.eventsConfig = { this.eventsConfig = {
@ -124,7 +123,7 @@ export class PanGesture {
pointerMove(ev: any) { pointerMove(ev: any) {
assert(this.started === true, 'started must be true'); assert(this.started === true, 'started must be true');
if (this.captured) { if (this.captured) {
this.debouncer.debounce(() => { this.debouncer.write(() => {
this.onDragMove(ev); this.onDragMove(ev);
}); });
return; return;

View File

@ -120,6 +120,7 @@ export class GestureController {
if (maxPriority === priority) { if (maxPriority === priority) {
this.capturedID = id; this.capturedID = id;
this.requestedStart = {}; this.requestedStart = {};
console.debug(`${gestureName} captured!`);
return true; return true;
} }
delete requestedStart[id]; delete requestedStart[id];
@ -170,12 +171,13 @@ export class GestureController {
canStart(gestureName: string): boolean { canStart(gestureName: string): boolean {
if (this.capturedID) { if (this.capturedID) {
console.debug(`${gestureName} can not start becuse gesture was already captured`);
// a gesture already captured // a gesture already captured
return false; return false;
} }
if (this.isDisabled(gestureName)) { if (this.isDisabled(gestureName)) {
console.debug('GestureController: Disabled', gestureName); console.debug(`${gestureName} is disabled`);
return false; return false;
} }
return true; return true;

View File

@ -17,7 +17,7 @@ import { NavParams } from './nav-params';
import { SwipeBackGesture } from './swipe-back'; import { SwipeBackGesture } from './swipe-back';
import { Transition } from '../transitions/transition'; import { Transition } from '../transitions/transition';
import { TransitionController } from '../transitions/transition-controller'; import { TransitionController } from '../transitions/transition-controller';
import { DomController } from '../util/dom-controller';
/** /**
* @private * @private
@ -32,7 +32,6 @@ export class NavControllerBase extends Ion implements NavController {
_queue: TransitionInstruction[] = []; _queue: TransitionInstruction[] = [];
_sbEnabled: boolean; _sbEnabled: boolean;
_sbGesture: SwipeBackGesture; _sbGesture: SwipeBackGesture;
_sbThreshold: number;
_sbTrns: Transition; _sbTrns: Transition;
_trnsId: number = null; _trnsId: number = null;
_trnsTm: boolean = false; _trnsTm: boolean = false;
@ -60,12 +59,12 @@ export class NavControllerBase extends Ion implements NavController {
public _cfr: ComponentFactoryResolver, public _cfr: ComponentFactoryResolver,
public _gestureCtrl: GestureController, public _gestureCtrl: GestureController,
public _trnsCtrl: TransitionController, public _trnsCtrl: TransitionController,
public _linker: DeepLinker public _linker: DeepLinker,
private _domCtrl: DomController
) { ) {
super(config, elementRef, renderer); super(config, elementRef, renderer);
this._sbEnabled = config.getBoolean('swipeBackEnabled'); this._sbEnabled = config.getBoolean('swipeBackEnabled');
this._sbThreshold = config.getNumber('swipeBackThreshold', 0);
this.id = 'n' + (++ctrlIds); 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<any> { setRoot(pageOrViewCtrl: any, params?: any, opts?: NavOptions, done?: Function): Promise<any> {
let viewControllers = [convertToView(this._linker, pageOrViewCtrl, params)]; const viewControllers = [convertToView(this._linker, pageOrViewCtrl, params)];
return this._setPages(viewControllers, opts, done); return this._setPages(viewControllers, opts, done);
} }
setPages(pages: any[], opts?: NavOptions, done?: Function): Promise<any> { setPages(pages: any[], opts?: NavOptions, done?: Function): Promise<any> {
let viewControllers = convertToViews(this._linker, pages); const viewControllers = convertToViews(this._linker, pages);
return this._setPages(viewControllers, opts, done); 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 // let's see if there's another to kick off
this.setTransitioning(false); this.setTransitioning(false);
this._sbCheck(); this._swipeBackCheck();
this._nextTrns(); this._nextTrns();
}; };
@ -226,7 +225,7 @@ export class NavControllerBase extends Ion implements NavController {
// let's see if there's another to kick off // let's see if there's another to kick off
this.setTransitioning(false); this.setTransitioning(false);
this._sbCheck(); this._swipeBackCheck();
this._nextTrns(); this._nextTrns();
}; };
@ -306,7 +305,7 @@ export class NavControllerBase extends Ion implements NavController {
assert(isPresent(ti.removeStart), 'removeView needs removeStart'); assert(isPresent(ti.removeStart), 'removeView needs removeStart');
assert(isPresent(ti.removeCount), 'removeView needs removeCount'); assert(isPresent(ti.removeCount), 'removeView needs removeCount');
let index = this._views.indexOf(ti.removeView); var index = this._views.indexOf(ti.removeView);
if (index >= 0) { if (index >= 0) {
ti.removeStart += index; ti.removeStart += index;
} }
@ -342,10 +341,10 @@ export class NavControllerBase extends Ion implements NavController {
const removeStart = ti.removeStart; const removeStart = ti.removeStart;
if (isPresent(removeStart)) { if (isPresent(removeStart)) {
const views = this._views; var views = this._views;
const removeEnd = removeStart + ti.removeCount; var removeEnd = removeStart + ti.removeCount;
let i: number; var i: number;
let view: ViewController; var view: ViewController;
for (i = views.length - 1; i >= 0; i--) { for (i = views.length - 1; i >= 0; i--) {
view = views[i]; view = views[i];
if ((i < removeStart || i >= removeEnd) && view !== leavingView) { if ((i < removeStart || i >= removeEnd) && view !== leavingView) {
@ -507,7 +506,7 @@ export class NavControllerBase extends Ion implements NavController {
const promises: Promise<any>[] = []; const promises: Promise<any>[] = [];
if (leavingView) { if (leavingView) {
const leavingTestResult = leavingView._lifecycleTest('Leave'); var leavingTestResult = leavingView._lifecycleTest('Leave');
if (leavingTestResult === false) { if (leavingTestResult === false) {
// synchronous reject // synchronous reject
@ -520,7 +519,7 @@ export class NavControllerBase extends Ion implements NavController {
} }
if (enteringView) { if (enteringView) {
const enteringTestResult = enteringView._lifecycleTest('Enter'); var enteringTestResult = enteringView._lifecycleTest('Enter');
if (enteringTestResult === false) { if (enteringTestResult === false) {
// synchronous reject // synchronous reject
@ -564,7 +563,7 @@ export class NavControllerBase extends Ion implements NavController {
direction: opts.direction, direction: opts.direction,
duration: (opts.animate === false ? 0 : opts.duration), duration: (opts.animate === false ? 0 : opts.duration),
easing: opts.easing, easing: opts.easing,
isRTL: this.config.platform.isRTL(), isRTL: this._config.platform.isRTL(),
ev: opts.ev, 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) // we should animate (duration > 0) if the pushed page is not the first one (startup)
// or if it is a portal (modal, actionsheet, etc.) // or if it is a portal (modal, actionsheet, etc.)
let isFirstPage = !this._init && this._views.length === 1; const isFirstPage = !this._init && this._views.length === 1;
let shouldNotAnimate = isFirstPage && !this._isPortal; const shouldNotAnimate = isFirstPage && !this._isPortal;
let canNotAnimate = this.config.get('animate') === false; const canNotAnimate = this._config.get('animate') === false;
if (shouldNotAnimate || canNotAnimate) { if (shouldNotAnimate || canNotAnimate) {
opts.animate = false; opts.animate = false;
} }
@ -743,7 +742,7 @@ export class NavControllerBase extends Ion implements NavController {
} }
_insertViewAt(view: ViewController, index: number) { _insertViewAt(view: ViewController, index: number) {
var existingIndex = this._views.indexOf(view); const existingIndex = this._views.indexOf(view);
if (existingIndex > -1) { if (existingIndex > -1) {
// this view is already in the stack!! // this view is already in the stack!!
// move it to its new location // move it to its new location
@ -897,10 +896,14 @@ export class NavControllerBase extends Ion implements NavController {
} }
destroy() { 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._willUnload();
view._destroy(this._renderer); view._destroy(this._renderer);
} }
// purge stack // purge stack
this._views.length = 0; this._views.length = 0;
@ -947,35 +950,26 @@ export class NavControllerBase extends Ion implements NavController {
swipeBackEnd(shouldComplete: boolean, currentStepValue: number, velocity: number) { swipeBackEnd(shouldComplete: boolean, currentStepValue: number, velocity: number) {
if (this._sbTrns && this._sbGesture) { if (this._sbTrns && this._sbGesture) {
// the swipe back gesture has ended // 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); this._sbTrns.progressEnd(shouldComplete, currentStepValue, dur);
} }
} }
_sbCheck() { _swipeBackCheck() {
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);
}
if (this.canSwipeBack()) { if (this.canSwipeBack()) {
if (!this._sbGesture) {
this._sbGesture = new SwipeBackGesture(this, this._gestureCtrl, this._domCtrl);
}
this._sbGesture.listen(); this._sbGesture.listen();
} else {
} else if (this._sbGesture) {
this._sbGesture.unlisten(); this._sbGesture.unlisten();
} }
} }
canSwipeBack(): boolean { canSwipeBack(): boolean {
return (this._sbEnabled && return (this._sbEnabled &&
!this._isPortal &&
!this._children.length && !this._children.length &&
!this.isTransitioning() && !this.isTransitioning() &&
this._app.isEnabled() && this._app.isEnabled() &&
@ -984,7 +978,7 @@ export class NavControllerBase extends Ion implements NavController {
canGoBack(): boolean { canGoBack(): boolean {
const activeView = this.getActive(); const activeView = this.getActive();
return !!(activeView && activeView.enableBack()) || false; return !!(activeView && activeView.enableBack());
} }
isTransitioning(): boolean { isTransitioning(): boolean {

View File

@ -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 { GestureController, GesturePriority, GESTURE_GO_BACK_SWIPE } from '../gestures/gesture-controller';
import { NavControllerBase } from './nav-controller-base'; import { NavControllerBase } from './nav-controller-base';
import { SlideData } from '../gestures/slide-gesture'; import { SlideData } from '../gestures/slide-gesture';
import { SlideEdgeGesture } from '../gestures/slide-edge-gesture'; import { SlideEdgeGesture } from '../gestures/slide-edge-gesture';
import { NativeRafDebouncer } from '../util/debouncer'; import { DomController } from '../util/dom-controller';
/** /**
* @private * @private
@ -12,22 +12,22 @@ export class SwipeBackGesture extends SlideEdgeGesture {
constructor( constructor(
private _nav: NavControllerBase, private _nav: NavControllerBase,
element: HTMLElement,
gestureCtlr: GestureController, gestureCtlr: GestureController,
options: any, domCtrl: DomController,
) { ) {
super(element, assign({ super(document.body, {
direction: 'x', direction: 'x',
edge: 'left',
maxEdgeStart: 75, maxEdgeStart: 75,
zone: false,
threshold: 5, threshold: 5,
debouncer: new NativeRafDebouncer(), zone: false,
domController: domCtrl,
gesture: gestureCtlr.createGesture({ gesture: gestureCtlr.createGesture({
name: GESTURE_GO_BACK_SWIPE, name: GESTURE_GO_BACK_SWIPE,
priority: GesturePriority.GoBackSwipe, priority: GesturePriority.GoBackSwipe,
disableScroll: true disableScroll: true
}) })
}, options)); });
} }
canStart(ev: any): boolean { canStart(ev: any): boolean {

View File

@ -7,29 +7,83 @@ import { nativeRaf } from './dom';
import { removeArrayItem } from './util'; 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 { export class DomController {
private r: Function[] = []; private r: Function[] = [];
private w: Function[] = []; private w: Function[] = [];
private q: boolean; 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); const task = !ctx ? fn : fn.bind(ctx);
this.r.push(task); this.r.push(task);
this.queue(); this.queue();
return task; return task;
} }
write(fn: {(timeStamp: number)}, ctx?: any): Function { write(fn: DomCallback, ctx?: any): Function {
const task = !ctx ? fn : fn.bind(ctx); const task = !ctx ? fn : fn.bind(ctx);
this.w.push(task); this.w.push(task);
this.queue(); this.queue();
return task; return task;
} }
cancel(task: any) { cancel(task: any): boolean {
return removeArrayItem(this.r, task) || removeArrayItem(this.w, task); 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() { protected queue() {
const self = this; const self = this;
if (!self.q) { if (!self.q) {
@ -41,32 +95,32 @@ export class DomController {
} }
protected flush(timeStamp: number) { protected flush(timeStamp: number) {
let err;
let task;
try { try {
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 **************** // ******** DOM READS ****************
while (task = this.r.shift()) { for (i = 0; i < rLen; i++) {
task(timeStamp); r[i](timeStamp);
} }
// ******** DOM WRITES **************** // ******** DOM WRITES ****************
while (task = this.w.shift()) { for (i = 0; i < wLen; i++) {
task(timeStamp); w[i](timeStamp);
}
} catch (e) {
err = e;
} }
this.q = false; r.length = 0;
w.length = 0;
if (this.r.length || this.w.length) {
this.queue();
}
if (err) {
throw err;
}
} }
} }

View File

@ -22,6 +22,7 @@ import { ViewController } from '../navigation/view-controller';
import { NavControllerBase } from '../navigation/nav-controller-base'; import { NavControllerBase } from '../navigation/nav-controller-base';
import { Haptic } from './haptic'; import { Haptic } from './haptic';
import { DomController } from './dom-controller';
export const mockConfig = function(config?: any, url: string = '/', platform?: Platform) { export const mockConfig = function(config?: any, url: string = '/', platform?: Platform) {
const c = new Config(); const c = new Config();
@ -246,6 +247,8 @@ export const mockNavController = function(): NavControllerBase {
let trnsCtrl = mockTrasitionController(config); let trnsCtrl = mockTrasitionController(config);
let dom = new DomController();
let nav = new NavControllerBase( let nav = new NavControllerBase(
null, null,
app, app,
@ -257,7 +260,8 @@ export const mockNavController = function(): NavControllerBase {
componentFactoryResolver, componentFactoryResolver,
gestureCtrl, gestureCtrl,
trnsCtrl, trnsCtrl,
linker linker,
dom,
); );
nav._viewInit = function(enteringView: ViewController) { 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 deepLinker = new DeepLinker(app, serializer, location);
let dom = new DomController();
return new OverlayPortal( return new OverlayPortal(
app, app,
config, config,
@ -307,7 +313,8 @@ export const mockOverlayPortal = function(app: App, config: Config, platform: Pl
gestureCtrl, gestureCtrl,
null, null,
deepLinker, deepLinker,
null null,
dom
); );
}; };
@ -334,6 +341,8 @@ export const mockTab = function(parentTabs: Tabs): Tab {
let linker = mockDeepLinker(null, app); let linker = mockDeepLinker(null, app);
let dom = new DomController();
let tab = new Tab( let tab = new Tab(
parentTabs, parentTabs,
app, app,
@ -346,7 +355,8 @@ export const mockTab = function(parentTabs: Tabs): Tab {
changeDetectorRef, changeDetectorRef,
gestureCtrl, gestureCtrl,
null, null,
linker linker,
dom
); );
tab.load = (opts: any, cb: Function) => { tab.load = (opts: any, cb: Function) => {
@ -371,7 +381,8 @@ export const mockTabs = function(app?: App): Tabs {
export const mockMenu = function (): Menu { export const mockMenu = function (): Menu {
let app = mockApp(); let app = mockApp();
let gestureCtrl = new GestureController(app); 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 { export const mockDeepLinkConfig = function(links?: any[]): DeepLinkConfig {