mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-21 21:15:24 +08:00
fix(nav): swipe to go back gesture
- smoother by debouncing touch events (reduces bank) - dynamic animation duration - intelligent behavior based in the position, speed and direccion of the swipe (sharing logic with sliding item) fixes #8919 fixes #8958 fixes #7934
This commit is contained in:

committed by
Adam Bradley

parent
033e1eae17
commit
04d61ee47a
@ -861,9 +861,32 @@ export class Animation {
|
|||||||
* Start the animation with a user controlled progress.
|
* Start the animation with a user controlled progress.
|
||||||
*/
|
*/
|
||||||
progressStart() {
|
progressStart() {
|
||||||
|
// ensure all past transition end events have been cleared
|
||||||
|
this._clearAsync();
|
||||||
|
|
||||||
|
// fire off all the "before" function that have DOM READS in them
|
||||||
|
// elements will be in the DOM, however visibily hidden
|
||||||
|
// so we can read their dimensions if need be
|
||||||
|
// ******** DOM READ ****************
|
||||||
|
this._beforeReadFn();
|
||||||
|
|
||||||
|
// fire off all the "before" function that have DOM WRITES in them
|
||||||
|
// ******** DOM WRITE ****************
|
||||||
|
this._beforeWriteFn();
|
||||||
|
|
||||||
|
// ******** DOM WRITE ****************
|
||||||
|
this._progressStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* DOM WRITE
|
||||||
|
* RECURSION
|
||||||
|
*/
|
||||||
|
_progressStart() {
|
||||||
for (var i = 0; i < this._cL; i++) {
|
for (var i = 0; i < this._cL; i++) {
|
||||||
// ******** DOM WRITE ****************
|
// ******** DOM WRITE ****************
|
||||||
this._c[i].progressStart();
|
this._c[i]._progressStart();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ******** DOM WRITE ****************
|
// ******** DOM WRITE ****************
|
||||||
@ -907,13 +930,14 @@ export class Animation {
|
|||||||
/**
|
/**
|
||||||
* End the progress animation.
|
* End the progress animation.
|
||||||
*/
|
*/
|
||||||
progressEnd(shouldComplete: boolean, currentStepValue: number) {
|
progressEnd(shouldComplete: boolean, currentStepValue: number, maxDelta: number = 0) {
|
||||||
console.debug('Animation, progressEnd, shouldComplete', shouldComplete, 'currentStepValue', currentStepValue);
|
console.debug('Animation, progressEnd, shouldComplete', shouldComplete, 'currentStepValue', currentStepValue);
|
||||||
|
|
||||||
this._isAsync = (currentStepValue > 0.05 && currentStepValue < 0.95);
|
this._isAsync = (currentStepValue > 0.05 && currentStepValue < 0.95);
|
||||||
|
|
||||||
const dur = 64;
|
|
||||||
const stepValue = shouldComplete ? 1 : 0;
|
const stepValue = shouldComplete ? 1 : 0;
|
||||||
|
const factor = Math.max(Math.abs(currentStepValue - stepValue), 0.5) * 2;
|
||||||
|
const dur = 64 + factor * maxDelta;
|
||||||
|
|
||||||
this._progressEnd(shouldComplete, stepValue, dur, this._isAsync);
|
this._progressEnd(shouldComplete, stepValue, dur, this._isAsync);
|
||||||
|
|
||||||
@ -922,7 +946,7 @@ export class Animation {
|
|||||||
// set the async TRANSITION END event
|
// set the async TRANSITION END event
|
||||||
// and run onFinishes when the transition ends
|
// and run onFinishes when the transition ends
|
||||||
// ******** DOM WRITE ****************
|
// ******** DOM WRITE ****************
|
||||||
this._asyncEnd(dur, true);
|
this._asyncEnd(dur, shouldComplete);
|
||||||
|
|
||||||
// this animation has a duration so we need another RAF
|
// this animation has a duration so we need another RAF
|
||||||
// for the CSS TRANSITION properties to kick in
|
// for the CSS TRANSITION properties to kick in
|
||||||
|
@ -71,7 +71,7 @@ export class App {
|
|||||||
// listen for hardware back button events
|
// listen for hardware back button events
|
||||||
// register this back button action with a default priority
|
// register this back button action with a default priority
|
||||||
_platform.registerBackButtonAction(this.navPop.bind(this));
|
_platform.registerBackButtonAction(this.navPop.bind(this));
|
||||||
this._canDisableScroll = _config.get('canDisableScroll', true);
|
this._canDisableScroll = _config.get('canDisableScroll', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
Menu
|
Menu
|
||||||
</ion-title>
|
</ion-title>
|
||||||
|
|
||||||
<button ion-button menuToggle="right" right color="secondary">
|
<button ion-button menuToggle="right" right color="danger">
|
||||||
<ion-icon name="menu"></ion-icon>
|
<ion-icon name="menu"></ion-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component, ContentChildren, ContentChild, Dire
|
|||||||
|
|
||||||
import { CSS, nativeRaf, nativeTimeout, clearNativeTimeout } from '../../util/dom';
|
import { CSS, nativeRaf, nativeTimeout, clearNativeTimeout } from '../../util/dom';
|
||||||
import { Item } from './item';
|
import { Item } from './item';
|
||||||
import { isPresent, assert } from '../../util/util';
|
import { isPresent, swipeShouldReset, assert } from '../../util/util';
|
||||||
import { List } from '../list/list';
|
import { List } from '../list/list';
|
||||||
|
|
||||||
const SWIPE_MARGIN = 30;
|
const SWIPE_MARGIN = 30;
|
||||||
@ -320,10 +320,10 @@ export class ItemSliding {
|
|||||||
|
|
||||||
// Check if the drag didn't clear the buttons mid-point
|
// Check if the drag didn't clear the buttons mid-point
|
||||||
// and we aren't moving fast enough to swipe open
|
// and we aren't moving fast enough to swipe open
|
||||||
let isCloseDirection = (this._openAmount > 0) === !(velocity < 0);
|
let isResetDirection = (this._openAmount > 0) === !(velocity < 0);
|
||||||
let isMovingFast = Math.abs(velocity) > 0.3;
|
let isMovingFast = Math.abs(velocity) > 0.3;
|
||||||
let isOnCloseZone = Math.abs(this._openAmount) < Math.abs(restingPoint / 2);
|
let isOnCloseZone = Math.abs(this._openAmount) < Math.abs(restingPoint / 2);
|
||||||
if (shouldClose(isCloseDirection, isMovingFast, isOnCloseZone)) {
|
if (swipeShouldReset(isResetDirection, isMovingFast, isOnCloseZone)) {
|
||||||
restingPoint = 0;
|
restingPoint = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -463,22 +463,3 @@ export class ItemSliding {
|
|||||||
this._renderer.setElementClass(this._elementRef.nativeElement, cssClass, shouldAdd);
|
this._renderer.setElementClass(this._elementRef.nativeElement, cssClass, shouldAdd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldClose(isCloseDirection: boolean, isMovingFast: boolean, isOnCloseZone: boolean): boolean {
|
|
||||||
// The logic required to know when the sliding item should close (openAmount=0)
|
|
||||||
// depends on three booleans (isCloseDirection, isMovingFast, isOnCloseZone)
|
|
||||||
// and it ended up being too complicated to be written manually without errors
|
|
||||||
// so the truth table is attached below: (0=false, 1=true)
|
|
||||||
// isCloseDirection | isMovingFast | isOnCloseZone || shouldClose
|
|
||||||
// 0 | 0 | 0 || 0
|
|
||||||
// 0 | 0 | 1 || 1
|
|
||||||
// 0 | 1 | 0 || 0
|
|
||||||
// 0 | 1 | 1 || 0
|
|
||||||
// 1 | 0 | 0 || 0
|
|
||||||
// 1 | 0 | 1 || 1
|
|
||||||
// 1 | 1 | 0 || 1
|
|
||||||
// 1 | 1 | 1 || 1
|
|
||||||
// The resulting expression was generated by resolving the K-map (Karnaugh map):
|
|
||||||
let shouldClose = (!isMovingFast && isOnCloseZone) || (isCloseDirection && isMovingFast);
|
|
||||||
return shouldClose;
|
|
||||||
}
|
|
||||||
|
@ -132,7 +132,6 @@ export class MenuController {
|
|||||||
}
|
}
|
||||||
return menu.open();
|
return menu.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve(false);
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ 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 { assign } from '../../util/util';
|
||||||
import { GestureController, GesturePriority } from '../../gestures/gesture-controller';
|
import { GestureController, GesturePriority } from '../../gestures/gesture-controller';
|
||||||
|
import { NativeRafDebouncer } from '../../util/debouncer';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gesture attached to the content which the menu is assigned to
|
* Gesture attached to the content which the menu is assigned to
|
||||||
@ -11,8 +12,8 @@ export class MenuContentGesture extends SlideEdgeGesture {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public menu: Menu,
|
public menu: Menu,
|
||||||
gestureCtrl: GestureController,
|
|
||||||
contentEle: HTMLElement,
|
contentEle: HTMLElement,
|
||||||
|
gestureCtrl: GestureController,
|
||||||
options: any = {}) {
|
options: any = {}) {
|
||||||
super(contentEle, assign({
|
super(contentEle, assign({
|
||||||
direction: 'x',
|
direction: 'x',
|
||||||
@ -20,6 +21,7 @@ export class MenuContentGesture extends SlideEdgeGesture {
|
|||||||
threshold: 0,
|
threshold: 0,
|
||||||
maxEdgeStart: menu.maxEdgeStart || 50,
|
maxEdgeStart: menu.maxEdgeStart || 50,
|
||||||
maxAngle: 40,
|
maxAngle: 40,
|
||||||
|
debouncer: new NativeRafDebouncer(),
|
||||||
gesture: gestureCtrl.create('menu-swipe', {
|
gesture: gestureCtrl.create('menu-swipe', {
|
||||||
priority: GesturePriority.MenuSwipe,
|
priority: GesturePriority.MenuSwipe,
|
||||||
})
|
})
|
||||||
|
@ -198,6 +198,7 @@ export class Menu {
|
|||||||
private _isPers: boolean = false;
|
private _isPers: boolean = false;
|
||||||
private _init: boolean = false;
|
private _init: boolean = false;
|
||||||
private _events: UIEventManager = new UIEventManager();
|
private _events: UIEventManager = new UIEventManager();
|
||||||
|
private _gestureID: number = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
@ -303,7 +304,11 @@ export class Menu {
|
|||||||
private _keyboard: Keyboard,
|
private _keyboard: Keyboard,
|
||||||
private _zone: NgZone,
|
private _zone: NgZone,
|
||||||
private _gestureCtrl: GestureController
|
private _gestureCtrl: GestureController
|
||||||
) {}
|
) {
|
||||||
|
if (_gestureCtrl) {
|
||||||
|
this._gestureID = _gestureCtrl.newID();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
@ -332,7 +337,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, this._gestureCtrl, document.body);
|
this._cntGesture = new MenuContentGesture(this, document.body, this._gestureCtrl);
|
||||||
|
|
||||||
// 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
|
||||||
@ -471,7 +476,7 @@ export class Menu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _before() {
|
private _before() {
|
||||||
assert(this._isAnimating === false, '_before should be called when we are not animating');
|
assert(!this._isAnimating, '_before() should not be called while animating');
|
||||||
|
|
||||||
// this places the menu into the correct location before it animates in
|
// this places the menu into the correct location before it animates in
|
||||||
// this css class doesn't actually kick off any animations
|
// this css class doesn't actually kick off any animations
|
||||||
@ -483,8 +488,7 @@ export class Menu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _after(isOpen: boolean) {
|
private _after(isOpen: boolean) {
|
||||||
assert(this._isAnimating === true, '_after should be called when we are 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
|
||||||
@ -494,8 +498,10 @@ export class Menu {
|
|||||||
|
|
||||||
this._events.unlistenAll();
|
this._events.unlistenAll();
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
this._cntEle.classList.add('menu-content-open');
|
// Disable swipe to go back gesture
|
||||||
|
this._gestureCtrl.disableGesture('goback-swipe', this._gestureID);
|
||||||
|
|
||||||
|
this._cntEle.classList.add('menu-content-open');
|
||||||
let callback = this.onBackdropClick.bind(this);
|
let callback = this.onBackdropClick.bind(this);
|
||||||
this._events.pointerEvents({
|
this._events.pointerEvents({
|
||||||
element: this._cntEle,
|
element: this._cntEle,
|
||||||
@ -508,6 +514,9 @@ export class Menu {
|
|||||||
this.ionOpen.emit(true);
|
this.ionOpen.emit(true);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
// Enable swipe to go back gesture
|
||||||
|
this._gestureCtrl.enableGesture('goback-swipe', this._gestureID);
|
||||||
|
|
||||||
this._cntEle.classList.remove('menu-content-open');
|
this._cntEle.classList.remove('menu-content-open');
|
||||||
this.setElementClass('show-menu', false);
|
this.setElementClass('show-menu', false);
|
||||||
this.backdrop.setElementClass('show-menu', false);
|
this.backdrop.setElementClass('show-menu', false);
|
||||||
|
@ -147,6 +147,7 @@ export class E2EPage {
|
|||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
<button ion-button full (click)="submit()">Submit</button>
|
<button ion-button full (click)="submit()">Submit</button>
|
||||||
|
<div padding>
|
||||||
<p>ionViewCanEnter ({{called.ionViewCanEnter}})</p>
|
<p>ionViewCanEnter ({{called.ionViewCanEnter}})</p>
|
||||||
<p>ionViewCanLeave ({{called.ionViewCanLeave}})</p>
|
<p>ionViewCanLeave ({{called.ionViewCanLeave}})</p>
|
||||||
<p>ionViewWillLoad ({{called.ionViewWillLoad}})</p>
|
<p>ionViewWillLoad ({{called.ionViewWillLoad}})</p>
|
||||||
@ -155,6 +156,7 @@ export class E2EPage {
|
|||||||
<p>ionViewDidEnter ({{called.ionViewDidEnter}})</p>
|
<p>ionViewDidEnter ({{called.ionViewDidEnter}})</p>
|
||||||
<p>ionViewWillLeave ({{called.ionViewWillLeave}})</p>
|
<p>ionViewWillLeave ({{called.ionViewWillLeave}})</p>
|
||||||
<p>ionViewDidLeave ({{called.ionViewDidLeave}})</p>
|
<p>ionViewDidLeave ({{called.ionViewDidLeave}})</p>
|
||||||
|
</div>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
`,
|
`,
|
||||||
providers: [SomeComponentProvider]
|
providers: [SomeComponentProvider]
|
||||||
@ -519,10 +521,12 @@ export class ModalFirstPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ionViewWillLeave() {
|
ionViewWillLeave() {
|
||||||
|
console.log('ModalFirstPage ionViewWillLeave fired');
|
||||||
this.called.ionViewWillLeave++;
|
this.called.ionViewWillLeave++;
|
||||||
}
|
}
|
||||||
|
|
||||||
ionViewDidLeave() {
|
ionViewDidLeave() {
|
||||||
|
console.log('ModalFirstPage ionViewDidLeave fired');
|
||||||
this.called.ionViewDidLeave++;
|
this.called.ionViewDidLeave++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -626,7 +630,8 @@ export class E2EApp {
|
|||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
IonicModule.forRoot(E2EApp, {
|
IonicModule.forRoot(E2EApp, {
|
||||||
statusbarPadding: true
|
statusbarPadding: true,
|
||||||
|
swipeBackEnabled: true
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
bootstrap: [IonicApp],
|
bootstrap: [IonicApp],
|
||||||
|
@ -3,7 +3,7 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
|||||||
|
|
||||||
import { clamp, isNumber, isPresent, isString, isTrueProperty } from '../../util/util';
|
import { clamp, isNumber, isPresent, isString, isTrueProperty } from '../../util/util';
|
||||||
import { Config } from '../../config/config';
|
import { Config } from '../../config/config';
|
||||||
import { Debouncer } from '../../util/debouncer';
|
import { TimeoutDebouncer } from '../../util/debouncer';
|
||||||
import { Form } from '../../util/form';
|
import { Form } from '../../util/form';
|
||||||
import { Ion } from '../ion';
|
import { Ion } from '../ion';
|
||||||
import { Item } from '../item/item';
|
import { Item } from '../item/item';
|
||||||
@ -217,7 +217,7 @@ export class Range extends Ion implements AfterViewInit, ControlValueAccessor, O
|
|||||||
_step: number = 1;
|
_step: number = 1;
|
||||||
_snaps: boolean = false;
|
_snaps: boolean = false;
|
||||||
|
|
||||||
_debouncer: Debouncer = new Debouncer(0);
|
_debouncer: TimeoutDebouncer = new TimeoutDebouncer(0);
|
||||||
_events: UIEventManager = new UIEventManager();
|
_events: UIEventManager = new UIEventManager();
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
|
@ -4,7 +4,7 @@ import { NgControl } from '@angular/forms';
|
|||||||
import { Config } from '../../config/config';
|
import { Config } from '../../config/config';
|
||||||
import { Ion } from '../ion';
|
import { Ion } from '../ion';
|
||||||
import { isPresent, isTrueProperty } from '../../util/util';
|
import { isPresent, isTrueProperty } from '../../util/util';
|
||||||
import { Debouncer } from '../../util/debouncer';
|
import { TimeoutDebouncer } from '../../util/debouncer';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -61,7 +61,7 @@ export class Searchbar extends Ion {
|
|||||||
_autocomplete: string = 'off';
|
_autocomplete: string = 'off';
|
||||||
_autocorrect: string = 'off';
|
_autocorrect: string = 'off';
|
||||||
_isActive: boolean = false;
|
_isActive: boolean = false;
|
||||||
_debouncer: Debouncer = new Debouncer(250);
|
_debouncer: TimeoutDebouncer = new TimeoutDebouncer(250);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @input {string} The predefined color to use. For example: `"primary"`, `"secondary"`, `"danger"`.
|
* @input {string} The predefined color to use. For example: `"primary"`, `"secondary"`, `"danger"`.
|
||||||
|
@ -134,6 +134,12 @@ export class Tab1Page1 {
|
|||||||
templateUrl: './tab1page2.html'
|
templateUrl: './tab1page2.html'
|
||||||
})
|
})
|
||||||
export class Tab1Page2 {
|
export class Tab1Page2 {
|
||||||
|
constructor(public tabs: Tabs) { }
|
||||||
|
|
||||||
|
favoritesTab() {
|
||||||
|
// TODO fix this with tabsHideOnSubPages=true
|
||||||
|
this.tabs.select(1);
|
||||||
|
}
|
||||||
|
|
||||||
ionViewWillEnter() {
|
ionViewWillEnter() {
|
||||||
console.log('Tab1Page2, ionViewWillEnter');
|
console.log('Tab1Page2, ionViewWillEnter');
|
||||||
@ -346,7 +352,7 @@ export const deepLinkConfig: DeepLinkConfig = {
|
|||||||
Tab3Page1
|
Tab3Page1
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
IonicModule.forRoot(E2EApp, null, deepLinkConfig)
|
IonicModule.forRoot(E2EApp, {tabsHideOnSubPages: true}, deepLinkConfig)
|
||||||
],
|
],
|
||||||
bootstrap: [IonicApp],
|
bootstrap: [IonicApp],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
<ion-content padding>
|
<ion-content padding>
|
||||||
|
|
||||||
<p><button ion-button navPush="Tab1Page3">Go to Tab 1, Page 3</button></p>
|
<p><button ion-button navPush="Tab1Page3">Go to Tab 1, Page 3</button></p>
|
||||||
|
<p><button ion-button (click)="favoritesTab()">Favorites Tab</button></p>
|
||||||
<p><button ion-button class="e2eBackToTab1Page1" navPop>Back to Tab 1, Page 1</button></p>
|
<p><button ion-button class="e2eBackToTab1Page1" navPop>Back to Tab 1, Page 1</button></p>
|
||||||
<div f></div><div f></div><div f></div><div f></div><div f></div><div f></div><div f></div><div f></div><div f></div><div f></div>
|
<div f></div><div f></div><div f></div><div f></div><div f></div><div f></div><div f></div><div f></div><div f></div><div f></div>
|
||||||
<div f></div><div f></div><div f></div><div f></div><div f></div><div f></div><div f></div><div f></div><div f></div><div f></div>
|
<div f></div><div f></div><div f></div><div f></div><div f></div><div f></div><div f></div><div f></div><div f></div><div f></div>
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { defaults } from '../util/util';
|
import { defaults } from '../util/util';
|
||||||
import { GestureDelegate } from '../gestures/gesture-controller';
|
import { GestureDelegate } from '../gestures/gesture-controller';
|
||||||
import { PanRecognizer } from './recognizers';
|
import { PanRecognizer } from './recognizers';
|
||||||
import { PointerEvents, 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';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
@ -12,12 +13,16 @@ export interface PanGestureConfig {
|
|||||||
maxAngle?: number;
|
maxAngle?: number;
|
||||||
direction?: 'x' | 'y';
|
direction?: 'x' | 'y';
|
||||||
gesture?: GestureDelegate;
|
gesture?: GestureDelegate;
|
||||||
|
debouncer?: Debouncer;
|
||||||
|
zone?: boolean;
|
||||||
|
capture?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
export class PanGesture {
|
export class PanGesture {
|
||||||
|
private debouncer: Debouncer;
|
||||||
private events: UIEventManager = new UIEventManager(false);
|
private events: UIEventManager = new UIEventManager(false);
|
||||||
private pointerEvents: PointerEvents;
|
private pointerEvents: PointerEvents;
|
||||||
private detector: PanRecognizer;
|
private detector: PanRecognizer;
|
||||||
@ -26,31 +31,45 @@ export class PanGesture {
|
|||||||
public isListening: boolean = false;
|
public isListening: boolean = false;
|
||||||
protected gestute: GestureDelegate;
|
protected gestute: GestureDelegate;
|
||||||
protected direction: string;
|
protected direction: string;
|
||||||
|
private eventsConfig: PointerEventsConfig;
|
||||||
|
|
||||||
constructor(private element: HTMLElement, opts: PanGestureConfig = {}) {
|
constructor(private element: HTMLElement, opts: PanGestureConfig = {}) {
|
||||||
defaults(opts, {
|
defaults(opts, {
|
||||||
threshold: 20,
|
threshold: 20,
|
||||||
maxAngle: 40,
|
maxAngle: 40,
|
||||||
direction: 'x'
|
direction: 'x',
|
||||||
|
zone: true,
|
||||||
|
capture: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.debouncer = (opts.debouncer)
|
||||||
|
? opts.debouncer
|
||||||
|
: new FakeDebouncer();
|
||||||
this.gestute = opts.gesture;
|
this.gestute = opts.gesture;
|
||||||
this.direction = opts.direction;
|
this.direction = opts.direction;
|
||||||
this.detector = new PanRecognizer(opts.direction, opts.threshold, opts.maxAngle);
|
this.eventsConfig = {
|
||||||
}
|
|
||||||
|
|
||||||
listen() {
|
|
||||||
if (!this.isListening) {
|
|
||||||
this.pointerEvents = this.events.pointerEvents({
|
|
||||||
element: this.element,
|
element: this.element,
|
||||||
pointerDown: this.pointerDown.bind(this),
|
pointerDown: this.pointerDown.bind(this),
|
||||||
pointerMove: this.pointerMove.bind(this),
|
pointerMove: this.pointerMove.bind(this),
|
||||||
pointerUp: this.pointerUp.bind(this),
|
pointerUp: this.pointerUp.bind(this),
|
||||||
});
|
zone: opts.zone,
|
||||||
this.isListening = true;
|
capture: opts.capture
|
||||||
|
};
|
||||||
|
this.detector = new PanRecognizer(opts.direction, opts.threshold, opts.maxAngle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
listen() {
|
||||||
|
if (this.isListening) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.pointerEvents = this.events.pointerEvents(this.eventsConfig);
|
||||||
|
this.isListening = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
unlisten() {
|
unlisten() {
|
||||||
|
if (!this.isListening) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.gestute && this.gestute.release();
|
this.gestute && this.gestute.release();
|
||||||
this.events.unlistenAll();
|
this.events.unlistenAll();
|
||||||
this.isListening = false;
|
this.isListening = false;
|
||||||
@ -58,6 +77,7 @@ export class PanGesture {
|
|||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this.gestute && this.gestute.destroy();
|
this.gestute && this.gestute.destroy();
|
||||||
|
this.gestute = null;
|
||||||
this.unlisten();
|
this.unlisten();
|
||||||
this.element = null;
|
this.element = null;
|
||||||
}
|
}
|
||||||
@ -86,6 +106,7 @@ export class PanGesture {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pointerMove(ev: any) {
|
pointerMove(ev: any) {
|
||||||
|
this.debouncer.debounce(() => {
|
||||||
if (!this.started) {
|
if (!this.started) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -109,9 +130,12 @@ export class PanGesture {
|
|||||||
this.pointerEvents.stop();
|
this.pointerEvents.stop();
|
||||||
this.notCaptured(ev);
|
this.notCaptured(ev);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pointerUp(ev: any) {
|
pointerUp(ev: any) {
|
||||||
|
this.debouncer.cancel();
|
||||||
|
|
||||||
if (!this.started) {
|
if (!this.started) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -178,10 +178,10 @@ export class NavControllerBase extends Ion implements NavController {
|
|||||||
// transition has successfully resolved
|
// transition has successfully resolved
|
||||||
this._trnsId = null;
|
this._trnsId = null;
|
||||||
resolve && resolve(hasCompleted, isAsync, enteringName, leavingName, direction);
|
resolve && resolve(hasCompleted, isAsync, enteringName, leavingName, direction);
|
||||||
this._sbCheck();
|
|
||||||
|
|
||||||
// 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._nextTrns();
|
this._nextTrns();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -204,11 +204,10 @@ export class NavControllerBase extends Ion implements NavController {
|
|||||||
this._trnsCtrl.destroy(trns.trnsId);
|
this._trnsCtrl.destroy(trns.trnsId);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._sbCheck();
|
|
||||||
|
|
||||||
reject && reject(false, false, rejectReason);
|
reject && reject(false, false, rejectReason);
|
||||||
|
|
||||||
this.setTransitioning(false);
|
this.setTransitioning(false);
|
||||||
|
this._sbCheck();
|
||||||
this._nextTrns();
|
this._nextTrns();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -371,11 +370,14 @@ export class NavControllerBase extends Ion implements NavController {
|
|||||||
// and there is not a view that needs to visually transition out
|
// and there is not a view that needs to visually transition out
|
||||||
// then just destroy them and don't transition anything
|
// then just destroy them and don't transition anything
|
||||||
// batch all of lifecycles together
|
// batch all of lifecycles together
|
||||||
|
// let's make sure, callbacks are zoned
|
||||||
|
this._zone.run(() => {
|
||||||
for (view of destroyQueue) {
|
for (view of destroyQueue) {
|
||||||
this._willLeave(view);
|
this._willLeave(view);
|
||||||
this._didLeave(view);
|
this._didLeave(view);
|
||||||
this._willUnload(view);
|
this._willUnload(view);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// once all lifecycle events has been delivered, we can safely detroy the views
|
// once all lifecycle events has been delivered, we can safely detroy the views
|
||||||
for (view of destroyQueue) {
|
for (view of destroyQueue) {
|
||||||
@ -445,7 +447,7 @@ export class NavControllerBase extends Ion implements NavController {
|
|||||||
|
|
||||||
// successfully finished loading the entering view
|
// successfully finished loading the entering view
|
||||||
// fire off the "didLoad" lifecycle events
|
// fire off the "didLoad" lifecycle events
|
||||||
this._didLoad(view);
|
this._zone.run(this._didLoad.bind(this, view));
|
||||||
}
|
}
|
||||||
|
|
||||||
_viewTest(enteringView: ViewController, leavingView: ViewController, ti: TransitionInstruction) {
|
_viewTest(enteringView: ViewController, leavingView: ViewController, ti: TransitionInstruction) {
|
||||||
@ -652,6 +654,10 @@ export class NavControllerBase extends Ion implements NavController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._cleanup(transition.enteringView);
|
this._cleanup(transition.enteringView);
|
||||||
|
} else {
|
||||||
|
// If transition does not complete, we have to cleanup anyway, because
|
||||||
|
// previous pages in the stack are not hidden probably.
|
||||||
|
this._cleanup(transition.leavingView);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (transition.isRoot()) {
|
if (transition.isRoot()) {
|
||||||
@ -761,12 +767,14 @@ export class NavControllerBase extends Ion implements NavController {
|
|||||||
|
|
||||||
_willLoad(view: ViewController) {
|
_willLoad(view: ViewController) {
|
||||||
assert(this.isTransitioning(), 'nav controller should be transitioning');
|
assert(this.isTransitioning(), 'nav controller should be transitioning');
|
||||||
|
assert(NgZone.isInAngularZone(), 'callback should be zoned');
|
||||||
|
|
||||||
view._willLoad();
|
view._willLoad();
|
||||||
}
|
}
|
||||||
|
|
||||||
_didLoad(view: ViewController) {
|
_didLoad(view: ViewController) {
|
||||||
assert(this.isTransitioning(), 'nav controller should be transitioning');
|
assert(this.isTransitioning(), 'nav controller should be transitioning');
|
||||||
|
assert(NgZone.isInAngularZone(), 'callback should be zoned');
|
||||||
|
|
||||||
view._didLoad();
|
view._didLoad();
|
||||||
this.viewDidLoad.emit(view);
|
this.viewDidLoad.emit(view);
|
||||||
@ -775,6 +783,7 @@ export class NavControllerBase extends Ion implements NavController {
|
|||||||
|
|
||||||
_willEnter(view: ViewController) {
|
_willEnter(view: ViewController) {
|
||||||
assert(this.isTransitioning(), 'nav controller should be transitioning');
|
assert(this.isTransitioning(), 'nav controller should be transitioning');
|
||||||
|
assert(NgZone.isInAngularZone(), 'callback should be zoned');
|
||||||
|
|
||||||
view._willEnter();
|
view._willEnter();
|
||||||
this.viewWillEnter.emit(view);
|
this.viewWillEnter.emit(view);
|
||||||
@ -783,6 +792,7 @@ export class NavControllerBase extends Ion implements NavController {
|
|||||||
|
|
||||||
_didEnter(view: ViewController) {
|
_didEnter(view: ViewController) {
|
||||||
assert(this.isTransitioning(), 'nav controller should be transitioning');
|
assert(this.isTransitioning(), 'nav controller should be transitioning');
|
||||||
|
assert(NgZone.isInAngularZone(), 'callback should be zoned');
|
||||||
|
|
||||||
view._didEnter();
|
view._didEnter();
|
||||||
this.viewDidEnter.emit(view);
|
this.viewDidEnter.emit(view);
|
||||||
@ -791,6 +801,7 @@ export class NavControllerBase extends Ion implements NavController {
|
|||||||
|
|
||||||
_willLeave(view: ViewController) {
|
_willLeave(view: ViewController) {
|
||||||
assert(this.isTransitioning(), 'nav controller should be transitioning');
|
assert(this.isTransitioning(), 'nav controller should be transitioning');
|
||||||
|
assert(NgZone.isInAngularZone(), 'callback should be zoned');
|
||||||
|
|
||||||
view._willLeave();
|
view._willLeave();
|
||||||
this.viewWillLeave.emit(view);
|
this.viewWillLeave.emit(view);
|
||||||
@ -799,6 +810,7 @@ export class NavControllerBase extends Ion implements NavController {
|
|||||||
|
|
||||||
_didLeave(view: ViewController) {
|
_didLeave(view: ViewController) {
|
||||||
assert(this.isTransitioning(), 'nav controller should be transitioning');
|
assert(this.isTransitioning(), 'nav controller should be transitioning');
|
||||||
|
assert(NgZone.isInAngularZone(), 'callback should be zoned');
|
||||||
|
|
||||||
view._didLeave();
|
view._didLeave();
|
||||||
this.viewDidLeave.emit(view);
|
this.viewDidLeave.emit(view);
|
||||||
@ -807,6 +819,7 @@ export class NavControllerBase extends Ion implements NavController {
|
|||||||
|
|
||||||
_willUnload(view: ViewController) {
|
_willUnload(view: ViewController) {
|
||||||
assert(this.isTransitioning(), 'nav controller should be transitioning');
|
assert(this.isTransitioning(), 'nav controller should be transitioning');
|
||||||
|
assert(NgZone.isInAngularZone(), 'callback should be zoned');
|
||||||
|
|
||||||
view._willUnload();
|
view._willUnload();
|
||||||
this.viewWillUnload.emit(view);
|
this.viewWillUnload.emit(view);
|
||||||
@ -830,18 +843,19 @@ export class NavControllerBase extends Ion implements NavController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
let view;
|
for (var view of this._views) {
|
||||||
for (view of this._views) {
|
|
||||||
view._willUnload();
|
view._willUnload();
|
||||||
view._destroy(this._renderer);
|
view._destroy(this._renderer);
|
||||||
}
|
}
|
||||||
// purge stack
|
// purge stack
|
||||||
this._views.length = 0;
|
this._views.length = 0;
|
||||||
|
|
||||||
|
// release swipe back gesture and transition
|
||||||
this._sbGesture && this._sbGesture.destroy();
|
this._sbGesture && this._sbGesture.destroy();
|
||||||
this._sbTrns && this._sbTrns.destroy();
|
this._sbTrns && this._sbTrns.destroy();
|
||||||
this._sbGesture = this._sbTrns = null;
|
this._sbGesture = this._sbTrns = null;
|
||||||
|
|
||||||
|
// Unregister navcontroller
|
||||||
if (this.parent && this.parent.unregisterChildNav) {
|
if (this.parent && this.parent.unregisterChildNav) {
|
||||||
this.parent.unregisterChildNav(this);
|
this.parent.unregisterChildNav(this);
|
||||||
}
|
}
|
||||||
@ -863,7 +877,6 @@ export class NavControllerBase extends Ion implements NavController {
|
|||||||
removeCount: 1,
|
removeCount: 1,
|
||||||
opts: opts,
|
opts: opts,
|
||||||
}, null);
|
}, null);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
swipeBackProgress(stepValue: number) {
|
swipeBackProgress(stepValue: number) {
|
||||||
@ -880,41 +893,31 @@ export class NavControllerBase extends Ion implements NavController {
|
|||||||
swipeBackEnd(shouldComplete: boolean, currentStepValue: number) {
|
swipeBackEnd(shouldComplete: boolean, currentStepValue: number) {
|
||||||
if (this._sbTrns && this._sbGesture) {
|
if (this._sbTrns && this._sbGesture) {
|
||||||
// the swipe back gesture has ended
|
// the swipe back gesture has ended
|
||||||
this._sbTrns.progressEnd(shouldComplete, currentStepValue);
|
this._sbTrns.progressEnd(shouldComplete, currentStepValue, 300);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_sbCheck() {
|
_sbCheck() {
|
||||||
if (this._sbEnabled && !this._isPortal) {
|
if (!this._sbEnabled && this._isPortal) {
|
||||||
// this nav controller can have swipe to go back
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this nav controller can have swipe to go back
|
||||||
if (!this._sbGesture) {
|
if (!this._sbGesture) {
|
||||||
// create the swipe back gesture if we haven't already
|
// create the swipe back gesture if we haven't already
|
||||||
const opts = {
|
const opts = {
|
||||||
edge: 'left',
|
edge: 'left',
|
||||||
threshold: this._sbThreshold
|
threshold: this._sbThreshold
|
||||||
};
|
};
|
||||||
this._sbGesture = new SwipeBackGesture(this.getNativeElement(), opts, this, this._gestureCtrl);
|
this._sbGesture = new SwipeBackGesture(this, document.body, this._gestureCtrl, opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.canSwipeBack()) {
|
if (this.canSwipeBack()) {
|
||||||
// it is be possible to swipe back
|
|
||||||
if (!this._sbGesture.isListening) {
|
|
||||||
this._zone.runOutsideAngular(() => {
|
|
||||||
// start listening if it's not already
|
|
||||||
console.debug('swipeBack gesture, listen');
|
|
||||||
this._sbGesture.listen();
|
this._sbGesture.listen();
|
||||||
});
|
} else {
|
||||||
}
|
|
||||||
|
|
||||||
} else if (this._sbGesture.isListening) {
|
|
||||||
// it should not be possible to swipe back
|
|
||||||
// but the gesture is still listening
|
|
||||||
console.debug('swipeBack gesture, unlisten');
|
|
||||||
this._sbGesture.unlisten();
|
this._sbGesture.unlisten();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
canSwipeBack(): boolean {
|
canSwipeBack(): boolean {
|
||||||
return (this._sbEnabled &&
|
return (this._sbEnabled &&
|
||||||
|
@ -1,23 +1,27 @@
|
|||||||
import { assign } from '../util/util';
|
import { assign, swipeShouldReset } from '../util/util';
|
||||||
import { GestureController, GesturePriority } from '../gestures/gesture-controller';
|
import { GestureController, GesturePriority, DisableScroll } 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';
|
||||||
|
|
||||||
export class SwipeBackGesture extends SlideEdgeGesture {
|
export class SwipeBackGesture extends SlideEdgeGesture {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
element: HTMLElement,
|
|
||||||
options: any,
|
|
||||||
private _nav: NavControllerBase,
|
private _nav: NavControllerBase,
|
||||||
gestureCtlr: GestureController
|
element: HTMLElement,
|
||||||
|
gestureCtlr: GestureController,
|
||||||
|
options: any,
|
||||||
) {
|
) {
|
||||||
super(element, assign({
|
super(element, assign({
|
||||||
direction: 'x',
|
direction: 'x',
|
||||||
maxEdgeStart: 75,
|
maxEdgeStart: 75,
|
||||||
|
zone: false,
|
||||||
|
threshold: 0,
|
||||||
|
maxAngle: 40,
|
||||||
|
debouncer: new NativeRafDebouncer(),
|
||||||
gesture: gestureCtlr.create('goback-swipe', {
|
gesture: gestureCtlr.create('goback-swipe', {
|
||||||
priority: GesturePriority.GoBackSwipe,
|
priority: GesturePriority.GoBackSwipe,
|
||||||
|
disableScroll: DisableScroll.DuringCapture
|
||||||
})
|
})
|
||||||
}, options));
|
}, options));
|
||||||
}
|
}
|
||||||
@ -32,23 +36,25 @@ export class SwipeBackGesture extends SlideEdgeGesture {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
onSlideBeforeStart(ev: any) {
|
onSlideBeforeStart(ev: any) {
|
||||||
console.debug('swipeBack, onSlideBeforeStart', ev.type);
|
|
||||||
this._nav.swipeBackStart();
|
this._nav.swipeBackStart();
|
||||||
}
|
}
|
||||||
|
|
||||||
onSlide(slide: SlideData) {
|
onSlide(slide: SlideData, ev: any) {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
|
||||||
let stepValue = (slide.distance / slide.max);
|
let stepValue = (slide.distance / slide.max);
|
||||||
console.debug('swipeBack, onSlide, distance', slide.distance, 'max', slide.max, 'stepValue', stepValue);
|
|
||||||
this._nav.swipeBackProgress(stepValue);
|
this._nav.swipeBackProgress(stepValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
onSlideEnd(slide: SlideData, ev: any) {
|
onSlideEnd(slide: SlideData, ev: any) {
|
||||||
let shouldComplete = (Math.abs(slide.velocity) > 0.2 || Math.abs(slide.delta) > Math.abs(slide.max) * 0.5);
|
const currentStepValue = (slide.distance / slide.max);
|
||||||
let currentStepValue = (slide.distance / slide.max);
|
const isResetDirecction = slide.velocity < 0;
|
||||||
|
const isMovingFast = Math.abs(slide.velocity) > 0.4;
|
||||||
|
const isInResetZone = Math.abs(slide.delta) < Math.abs(slide.max) * 0.5;
|
||||||
|
const shouldComplete = !swipeShouldReset(isResetDirecction, isMovingFast, isInResetZone);
|
||||||
|
|
||||||
console.debug('swipeBack, onSlideEnd, shouldComplete', shouldComplete, 'currentStepValue', currentStepValue);
|
|
||||||
this._nav.swipeBackEnd(shouldComplete, currentStepValue);
|
this._nav.swipeBackEnd(shouldComplete, currentStepValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,7 +110,6 @@ export const PLATFORM_CONFIGS: {[key: string]: PlatformConfig} = {
|
|||||||
swipeBackThreshold: 40,
|
swipeBackThreshold: 40,
|
||||||
tapPolyfill: isIOSDevice,
|
tapPolyfill: isIOSDevice,
|
||||||
virtualScrollEventAssist: !(window.indexedDB),
|
virtualScrollEventAssist: !(window.indexedDB),
|
||||||
canDisableScroll: isIOSDevice,
|
|
||||||
},
|
},
|
||||||
isMatch(p: Platform) {
|
isMatch(p: Platform) {
|
||||||
return p.isPlatformMatch('ios', ['iphone', 'ipad', 'ipod'], ['windows phone']);
|
return p.isPlatformMatch('ios', ['iphone', 'ipad', 'ipod'], ['windows phone']);
|
||||||
|
@ -53,7 +53,7 @@ export class MDTransition extends PageTransition {
|
|||||||
// leaving content
|
// leaving content
|
||||||
this.duration(opts.duration || 200).easing('cubic-bezier(0.47,0,0.745,0.715)');
|
this.duration(opts.duration || 200).easing('cubic-bezier(0.47,0,0.745,0.715)');
|
||||||
const leavingPage = new Animation(leavingView.pageRef());
|
const leavingPage = new Animation(leavingView.pageRef());
|
||||||
this.add(leavingPage.fromTo(TRANSLATEY, CENTER, OFF_BOTTOM).fromTo('opacity', 0.99, 0));
|
this.add(leavingPage.fromTo(TRANSLATEY, CENTER, OFF_BOTTOM).fromTo('opacity', 1, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,20 @@
|
|||||||
|
|
||||||
export class Debouncer {
|
import { nativeRaf } from './dom';
|
||||||
|
|
||||||
|
export interface Debouncer {
|
||||||
|
debounce(Function);
|
||||||
|
cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class FakeDebouncer implements Debouncer {
|
||||||
|
debounce(callback: Function) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
cancel() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TimeoutDebouncer implements Debouncer {
|
||||||
private timer: number = null;
|
private timer: number = null;
|
||||||
callback: Function;
|
callback: Function;
|
||||||
|
|
||||||
@ -11,10 +26,7 @@ export class Debouncer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
schedule() {
|
schedule() {
|
||||||
if (this.timer) {
|
this.cancel();
|
||||||
clearTimeout(this.timer);
|
|
||||||
this.timer = null;
|
|
||||||
}
|
|
||||||
if (this.wait <= 0) {
|
if (this.wait <= 0) {
|
||||||
this.callback();
|
this.callback();
|
||||||
} else {
|
} else {
|
||||||
@ -22,4 +34,44 @@ export class Debouncer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cancel() {
|
||||||
|
if (this.timer) {
|
||||||
|
clearTimeout(this.timer);
|
||||||
|
this.timer = null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NativeRafDebouncer implements Debouncer {
|
||||||
|
callback: Function = null;
|
||||||
|
fireFunc: Function;
|
||||||
|
ptr: number = null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.fireFunc = this.fire.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
debounce(callback: Function) {
|
||||||
|
if (this.callback === null) {
|
||||||
|
this.callback = callback;
|
||||||
|
this.ptr = nativeRaf(this.fireFunc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fire() {
|
||||||
|
this.callback();
|
||||||
|
this.callback = null;
|
||||||
|
this.ptr = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel() {
|
||||||
|
if (this.ptr !== null) {
|
||||||
|
cancelAnimationFrame(this.ptr);
|
||||||
|
this.ptr = null;
|
||||||
|
this.callback = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -72,15 +72,7 @@ export const mockTrasitionController = function(config: Config) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const mockZone = function(): NgZone {
|
export const mockZone = function(): NgZone {
|
||||||
let zone: any = {
|
return new NgZone(false);
|
||||||
run: function(cb: any) {
|
|
||||||
cb();
|
|
||||||
},
|
|
||||||
runOutsideAngular: function(cb: any) {
|
|
||||||
cb();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return zone;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mockChangeDetectorRef = function(): ChangeDetectorRef {
|
export const mockChangeDetectorRef = function(): ChangeDetectorRef {
|
||||||
|
@ -156,6 +156,28 @@ export function reorderArray(array: any[], indexes: {from: number, to: number}):
|
|||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
export function swipeShouldReset(isResetDirection: boolean, isMovingFast: boolean, isOnResetZone: boolean): boolean {
|
||||||
|
// The logic required to know when the sliding item should close (openAmount=0)
|
||||||
|
// depends on three booleans (isCloseDirection, isMovingFast, isOnCloseZone)
|
||||||
|
// and it ended up being too complicated to be written manually without errors
|
||||||
|
// so the truth table is attached below: (0=false, 1=true)
|
||||||
|
// isCloseDirection | isMovingFast | isOnCloseZone || shouldClose
|
||||||
|
// 0 | 0 | 0 || 0
|
||||||
|
// 0 | 0 | 1 || 1
|
||||||
|
// 0 | 1 | 0 || 0
|
||||||
|
// 0 | 1 | 1 || 0
|
||||||
|
// 1 | 0 | 0 || 0
|
||||||
|
// 1 | 0 | 1 || 1
|
||||||
|
// 1 | 1 | 0 || 1
|
||||||
|
// 1 | 1 | 1 || 1
|
||||||
|
// The resulting expression was generated by resolving the K-map (Karnaugh map):
|
||||||
|
let shouldClose = (!isMovingFast && isOnResetZone) || (isResetDirection && isMovingFast);
|
||||||
|
return shouldClose;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const ASSERT_ENABLED = true;
|
const ASSERT_ENABLED = true;
|
||||||
/**
|
/**
|
||||||
|
Reference in New Issue
Block a user