chore(): begin adding ionic components to mono-repo.

This commit is contained in:
Josh Thomas
2017-06-21 09:33:06 -05:00
parent 1181fe98fc
commit bd5b67304d
2159 changed files with 15687 additions and 147 deletions

View File

@@ -0,0 +1,22 @@
import { Injectable } from '@angular/core';
import { HammerGestureConfig } from '@angular/platform-browser';
/**
* @hidden
* This class overrides the default Angular gesture config.
*/
@Injectable()
export class IonicGestureConfig extends HammerGestureConfig {
buildHammer(element: HTMLElement) {
const mc = new (<any> window).Hammer(element);
for (let eventName in this.overrides) {
mc.get(eventName).set(this.overrides[eventName]);
}
return mc;
}
}

View File

@@ -0,0 +1,308 @@
import { forwardRef, Inject, Injectable } from '@angular/core';
import { App } from '../components/app/app';
import { assert } from '../util/util';
/** @hidden */
export const GESTURE_GO_BACK_SWIPE = 'goback-swipe';
/** @hidden */
export const GESTURE_MENU_SWIPE = 'menu-swipe';
/** @hidden */
export const GESTURE_ITEM_SWIPE = 'item-swipe';
/** @hidden */
export const GESTURE_REFRESHER = 'refresher';
/** @hidden */
export const GESTURE_TOGGLE = 'toggle';
/** @hidden */
export const GESTURE_PRIORITY_SLIDING_ITEM = -10;
/** @hidden */
export const GESTURE_PRIORITY_REFRESHER = 0;
/** @hidden */
export const GESTURE_PRIORITY_MENU_SWIPE = 10;
/** @hidden */
export const GESTURE_PRIORITY_GO_BACK_SWIPE = 20;
/** @hidden */
export const GESTURE_PRIORITY_TOGGLE = 30;
/**
* @hidden
*/
export interface GestureOptions {
name: string;
disableScroll?: boolean;
priority?: number;
}
/**
* @hidden
*/
export interface BlockerOptions {
disableScroll?: boolean;
disable?: string[];
}
/**
* @hidden
*/
export const BLOCK_ALL: BlockerOptions = {
disable: [GESTURE_MENU_SWIPE, GESTURE_GO_BACK_SWIPE],
disableScroll: true
};
/**
* @hidden
*/
@Injectable()
export class GestureController {
private id: number = 1;
private requestedStart: { [eventId: number]: number } = {};
private disabledGestures: { [eventName: string]: Set<number> } = {};
private disabledScroll: Set<number> = new Set<number>();
private capturedID: number = null;
constructor(@Inject(forwardRef(() => App)) private _app: App) { }
createGesture(opts: GestureOptions): GestureDelegate {
if (!opts.name) {
throw new Error('name is undefined');
}
return new GestureDelegate(opts.name, this.newID(), this,
opts.priority || 0,
!!opts.disableScroll
);
}
createBlocker(opts: BlockerOptions = {}): BlockerDelegate {
return new BlockerDelegate(this.newID(), this,
opts.disable,
!!opts.disableScroll
);
}
newID(): number {
let id = this.id; this.id++;
return id;
}
start(gestureName: string, id: number, priority: number): boolean {
if (!this.canStart(gestureName)) {
delete this.requestedStart[id];
return false;
}
this.requestedStart[id] = priority;
return true;
}
capture(gestureName: string, id: number, priority: number): boolean {
if (!this.start(gestureName, id, priority)) {
return false;
}
let requestedStart = this.requestedStart;
let maxPriority = -10000;
for (let gestureID in requestedStart) {
maxPriority = Math.max(maxPriority, requestedStart[gestureID]);
}
if (maxPriority === priority) {
this.capturedID = id;
this.requestedStart = {};
console.debug(`${gestureName} captured!`);
return true;
}
delete requestedStart[id];
console.debug(`${gestureName} can not start because it is has lower priority`);
return false;
}
release(id: number) {
delete this.requestedStart[id];
if (this.capturedID && id === this.capturedID) {
this.capturedID = null;
}
}
disableGesture(gestureName: string, id: number) {
let set = this.disabledGestures[gestureName];
if (!set) {
set = new Set<number>();
this.disabledGestures[gestureName] = set;
}
set.add(id);
}
enableGesture(gestureName: string, id: number) {
let set = this.disabledGestures[gestureName];
if (set) {
set.delete(id);
}
}
disableScroll(id: number) {
let isEnabled = !this.isScrollDisabled();
this.disabledScroll.add(id);
if (this._app && isEnabled && this.isScrollDisabled()) {
console.debug('GestureController: Disabling scrolling');
this._app._setDisableScroll(true);
}
}
enableScroll(id: number) {
let isDisabled = this.isScrollDisabled();
this.disabledScroll.delete(id);
if (this._app && isDisabled && !this.isScrollDisabled()) {
console.debug('GestureController: Enabling scrolling');
this._app._setDisableScroll(false);
}
}
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(`${gestureName} is disabled`);
return false;
}
return true;
}
isCaptured(): boolean {
return !!this.capturedID;
}
isScrollDisabled(): boolean {
return this.disabledScroll.size > 0;
}
isDisabled(gestureName: string): boolean {
let disabled = this.disabledGestures[gestureName];
return !!(disabled && disabled.size > 0);
}
}
/**
* @hidden
*/
export class GestureDelegate {
constructor(
private name: string,
private id: number,
private controller: GestureController,
private priority: number,
private disableScroll: boolean
) { }
canStart(): boolean {
if (!this.controller) {
assert(false, 'delegate was destroyed');
return false;
}
return this.controller.canStart(this.name);
}
start(): boolean {
if (!this.controller) {
assert(false, 'delegate was destroyed');
return false;
}
return this.controller.start(this.name, this.id, this.priority);
}
capture(): boolean {
if (!this.controller) {
assert(false, 'delegate was destroyed');
return false;
}
let captured = this.controller.capture(this.name, this.id, this.priority);
if (captured && this.disableScroll) {
this.controller.disableScroll(this.id);
}
return captured;
}
release() {
if (!this.controller) {
assert(false, 'delegate was destroyed');
return;
}
this.controller.release(this.id);
if (this.disableScroll) {
this.controller.enableScroll(this.id);
}
}
destroy() {
this.release();
this.controller = null;
}
}
/**
* @hidden
*/
export class BlockerDelegate {
blocked: boolean = false;
constructor(
private id: number,
private controller: GestureController,
private disable: string[],
private disableScroll: boolean
) { }
block() {
if (!this.controller) {
assert(false, 'delegate was destroyed');
return;
}
if (this.disable) {
this.disable.forEach(gesture => {
this.controller.disableGesture(gesture, this.id);
});
}
if (this.disableScroll) {
this.controller.disableScroll(this.id);
}
this.blocked = true;
}
unblock() {
if (!this.controller) {
assert(false, 'delegate was destroyed');
return;
}
if (this.disable) {
this.disable.forEach(gesture => {
this.controller.enableGesture(gesture, this.id);
});
}
if (this.disableScroll) {
this.controller.enableScroll(this.id);
}
this.blocked = false;
}
destroy() {
this.unblock();
this.controller = null;
}
}

View File

@@ -0,0 +1,79 @@
import { defaults } from '../util/util';
import { Hammer, DIRECTION_HORIZONTAL, DIRECTION_VERTICAL } from './hammer';
/**
* @hidden
* A gesture recognizer class.
*
* TODO(mlynch): Re-enable the DOM event simulation that was causing issues (or verify hammer does this already, it might);
*/
export class Gesture {
private _hammer: any;
private _options: any;
private _callbacks: any = {};
public element: HTMLElement;
public direction: string;
public isListening: boolean = false;
constructor(element: HTMLElement, opts: any = {}) {
defaults(opts, {
domEvents: true
});
this.element = element;
// Map 'x' or 'y' string to hammerjs opts
this.direction = opts.direction || 'x';
opts.direction = this.direction === 'x' ?
DIRECTION_HORIZONTAL :
DIRECTION_VERTICAL;
this._options = opts;
}
options(opts: any) {
Object.assign(this._options, opts);
}
on(type: string, cb: Function) {
if (type === 'pinch' || type === 'rotate') {
this._hammer.get(type).set({enable: true});
}
this._hammer.on(type, cb);
(this._callbacks[type] || (this._callbacks[type] = [])).push(cb);
}
off(type: string, cb: Function) {
this._hammer.off(type, this._callbacks[type] ? cb : null);
}
listen() {
if (!this.isListening) {
this._hammer = Hammer(this.element, this._options);
}
this.isListening = true;
}
unlisten() {
let eventType: string;
let i: number;
if (this._hammer && this.isListening) {
for (eventType in this._callbacks) {
for (i = 0; i < this._callbacks[eventType].length; i++) {
this._hammer.off(eventType, this._callbacks[eventType]);
}
}
this._hammer.destroy();
}
this._callbacks = {};
this._hammer = null;
this.isListening = false;
}
destroy() {
this.unlisten();
this.element = this._options = null;
}
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,191 @@
import { assert, defaults } from '../util/util';
import { DomDebouncer, DomController } from '../platform/dom-controller';
import { GestureDelegate } from './gesture-controller';
import { PanRecognizer } from './recognizers';
import { Platform } from '../platform/platform';
import { pointerCoord } from '../util/dom';
import { PointerEvents, PointerEventsConfig } from './pointer-events';
import { UIEventManager } from './ui-event-manager';
/**
* @hidden
*/
export class PanGesture {
private debouncer: DomDebouncer;
private events: UIEventManager;
private pointerEvents: PointerEvents;
private detector: PanRecognizer;
protected started: boolean;
private captured: boolean;
public isListening: boolean;
protected gestute: GestureDelegate;
protected direction: string;
private eventsConfig: PointerEventsConfig;
constructor(public plt: Platform, private element: HTMLElement, opts: PanGestureConfig = {}) {
defaults(opts, {
threshold: 20,
maxAngle: 40,
direction: 'x',
zone: true,
capture: false,
passive: false,
});
this.events = new UIEventManager(plt);
if (opts.domController) {
this.debouncer = opts.domController.debouncer();
}
this.gestute = opts.gesture;
this.direction = opts.direction;
this.eventsConfig = {
element: this.element,
pointerDown: this.pointerDown.bind(this),
pointerMove: this.pointerMove.bind(this),
pointerUp: this.pointerUp.bind(this),
zone: opts.zone,
capture: opts.capture,
passive: opts.passive
};
if (opts.threshold > 0) {
this.detector = new PanRecognizer(opts.direction, opts.threshold, opts.maxAngle);
}
}
listen() {
if (!this.isListening) {
this.pointerEvents = this.events.pointerEvents(this.eventsConfig);
this.isListening = true;
}
}
unlisten() {
if (this.isListening) {
this.gestute && this.gestute.release();
this.events.unlistenAll();
this.isListening = false;
}
}
destroy() {
this.gestute && this.gestute.destroy();
this.gestute = null;
this.unlisten();
this.events.destroy();
this.events = this.element = this.gestute = null;
}
pointerDown(ev: any): boolean {
if (this.started) {
return;
}
if (!this.canStart(ev)) {
return false;
}
if (this.gestute) {
// Release fallback
this.gestute.release();
// Start gesture
if (!this.gestute.start()) {
return false;
}
}
this.started = true;
this.captured = false;
const coord = pointerCoord(ev);
if (this.detector) {
this.detector.start(coord);
} else {
if (!this.tryToCapture(ev)) {
this.started = false;
this.captured = false;
this.gestute.release();
return false;
}
}
return true;
}
pointerMove(ev: any) {
assert(this.started === true, 'started must be true');
if (this.captured) {
this.debouncer.write(() => {
this.onDragMove(ev);
});
return;
}
assert(this.detector, 'detector has to be valid');
const coord = pointerCoord(ev);
if (this.detector.detect(coord)) {
if (this.detector.pan() !== 0) {
if (!this.tryToCapture(ev)) {
this.abort(ev);
}
}
}
}
pointerUp(ev: any) {
assert(this.started, 'started failed');
this.debouncer.cancel();
this.gestute && this.gestute.release();
if (this.captured) {
this.onDragEnd(ev);
} else {
this.notCaptured(ev);
}
this.captured = false;
this.started = false;
}
tryToCapture(ev: any): boolean {
assert(this.started === true, 'started has be true');
assert(this.captured === false, 'captured has be false');
if (this.gestute && !this.gestute.capture()) {
return false;
}
this.onDragStart(ev);
this.captured = true;
return true;
}
abort(ev: any) {
this.started = false;
this.captured = false;
this.gestute.release();
this.pointerEvents.stop();
this.notCaptured(ev);
}
getNativeElement(): HTMLElement {
return this.element;
}
// Implemented in a subclass
canStart(ev: any): boolean { return true; }
onDragStart(ev: any) { }
onDragMove(ev: any) { }
onDragEnd(ev: any) { }
notCaptured(ev: any) { }
}
/**
* @hidden
*/
export interface PanGestureConfig {
threshold?: number;
maxAngle?: number;
direction?: 'x' | 'y';
gesture?: GestureDelegate;
domController?: DomController;
zone?: boolean;
capture?: boolean;
passive?: boolean;
}

View File

@@ -0,0 +1,135 @@
import { assert } from '../util/util';
import { Platform, EventListenerOptions } from '../platform/platform';
/**
* @hidden
*/
export class PointerEvents {
private rmTouchStart: Function = null;
private rmTouchMove: Function = null;
private rmTouchEnd: Function = null;
private rmTouchCancel: Function = null;
private rmMouseStart: Function = null;
private rmMouseMove: Function = null;
private rmMouseUp: Function = null;
private bindTouchEnd: any;
private bindMouseUp: any;
private lastTouchEvent: number = 0;
mouseWait: number = 2 * 1000;
lastEventType: number;
constructor(
private plt: Platform,
private ele: any,
private pointerDown: any,
private pointerMove: any,
private pointerUp: any,
private option: EventListenerOptions
) {
assert(ele, 'element can not be null');
assert(pointerDown, 'pointerDown can not be null');
this.bindTouchEnd = this.handleTouchEnd.bind(this);
this.bindMouseUp = this.handleMouseUp.bind(this);
this.rmTouchStart = this.plt.registerListener(ele, 'touchstart', this.handleTouchStart.bind(this), option);
this.rmMouseStart = this.plt.registerListener(ele, 'mousedown', this.handleMouseDown.bind(this), option);
}
private handleTouchStart(ev: any) {
assert(this.ele, 'element can not be null');
assert(this.pointerDown, 'pointerDown can not be null');
this.lastTouchEvent = Date.now() + this.mouseWait;
this.lastEventType = POINTER_EVENT_TYPE_TOUCH;
if (!this.pointerDown(ev, POINTER_EVENT_TYPE_TOUCH)) {
return;
}
if (!this.rmTouchMove && this.pointerMove) {
this.rmTouchMove = this.plt.registerListener(this.ele, 'touchmove', this.pointerMove, this.option);
}
if (!this.rmTouchEnd) {
this.rmTouchEnd = this.plt.registerListener(this.ele, 'touchend', this.bindTouchEnd, this.option);
}
if (!this.rmTouchCancel) {
this.rmTouchCancel = this.plt.registerListener(this.ele, 'touchcancel', this.bindTouchEnd, this.option);
}
}
private handleMouseDown(ev: any) {
assert(this.ele, 'element can not be null');
assert(this.pointerDown, 'pointerDown can not be null');
if (this.lastTouchEvent > Date.now()) {
console.debug('mousedown event dropped because of previous touch');
return;
}
this.lastEventType = POINTER_EVENT_TYPE_MOUSE;
if (!this.pointerDown(ev, POINTER_EVENT_TYPE_MOUSE)) {
return;
}
if (!this.rmMouseMove && this.pointerMove) {
this.rmMouseMove = this.plt.registerListener(this.plt.doc(), 'mousemove', this.pointerMove, this.option);
}
if (!this.rmMouseUp) {
this.rmMouseUp = this.plt.registerListener(this.plt.doc(), 'mouseup', this.bindMouseUp, this.option);
}
}
private handleTouchEnd(ev: any) {
this.stopTouch();
this.pointerUp && this.pointerUp(ev, POINTER_EVENT_TYPE_TOUCH);
}
private handleMouseUp(ev: any) {
this.stopMouse();
this.pointerUp && this.pointerUp(ev, POINTER_EVENT_TYPE_MOUSE);
}
private stopTouch() {
this.rmTouchMove && this.rmTouchMove();
this.rmTouchEnd && this.rmTouchEnd();
this.rmTouchCancel && this.rmTouchCancel();
this.rmTouchMove = this.rmTouchEnd = this.rmTouchCancel = null;
}
private stopMouse() {
this.rmMouseMove && this.rmMouseMove();
this.rmMouseUp && this.rmMouseUp();
this.rmMouseMove = this.rmMouseUp = null;
}
stop() {
this.stopTouch();
this.stopMouse();
}
destroy() {
this.rmTouchStart && this.rmTouchStart();
this.rmMouseStart && this.rmMouseStart();
this.stop();
this.ele = this.pointerUp = this.pointerMove = this.pointerDown = this.rmTouchStart = this.rmMouseStart = null;
}
}
export const POINTER_EVENT_TYPE_MOUSE = 1;
export const POINTER_EVENT_TYPE_TOUCH = 2;
export interface PointerEventsConfig {
element?: HTMLElement;
pointerDown: (ev: any) => boolean;
pointerMove?: (ev: any) => void;
pointerUp?: (ev: any) => void;
zone?: boolean;
capture?: boolean;
passive?: boolean;
}

View File

@@ -0,0 +1,62 @@
import { PointerCoordinates } from '../util/dom';
export class PanRecognizer {
private startCoord: PointerCoordinates;
private dirty: boolean = false;
private threshold: number;
private maxCosine: number;
private _angle: any = 0;
private _isPan: number = 0;
constructor(private direction: string, threshold: number, maxAngle: number) {
const radians = maxAngle * (Math.PI / 180);
this.maxCosine = Math.cos(radians);
this.threshold = threshold * threshold;
}
start(coord: PointerCoordinates) {
this.startCoord = coord;
this._angle = 0;
this._isPan = 0;
this.dirty = true;
}
detect(coord: PointerCoordinates): boolean {
if (!this.dirty) {
return false;
}
const deltaX = (coord.x - this.startCoord.x);
const deltaY = (coord.y - this.startCoord.y);
const distance = deltaX * deltaX + deltaY * deltaY;
if (distance >= this.threshold) {
var angle = Math.atan2(deltaY, deltaX);
var cosine = (this.direction === 'y')
? Math.sin(angle)
: Math.cos(angle);
this._angle = angle;
if (cosine > this.maxCosine) {
this._isPan = 1;
} else if (cosine < -this.maxCosine) {
this._isPan = -1;
} else {
this._isPan = 0;
}
this.dirty = false;
return true;
}
return false;
}
angle(): any {
return this._angle;
}
pan(): number {
return this._isPan;
}
}

View File

@@ -0,0 +1,163 @@
import { PointerCoordinates } from '../util/dom';
interface Point {
coord: PointerCoordinates;
duration: number;
}
export class Simulate {
private index: number = 0;
private points: Point[] = [];
public timedelta: number = 1 / 60;
public static from(x: any, y?: number): Simulate {
let s = new Simulate();
return s.start(x, y);
}
reset(): Simulate {
this.index = 0;
return this;
}
start(x: any, y?: number): Simulate {
this.points = [];
return this.to(x, y);
}
to(x: any, y?: number): Simulate {
this.newPoint(parseCoordinates(x, y), 1);
return this;
}
delta(x: any, y?: number): Simulate {
let newPoint = parseCoordinates(x, y);
let prevCoord = this.getLastPoint().coord;
newPoint.x += prevCoord.x;
newPoint.y += prevCoord.y;
this.newPoint(newPoint, 1);
return this;
}
deltaPolar(angle: number, distance: number): Simulate {
angle *= Math.PI / 180;
let prevCoord = this.getLastPoint().coord;
let coord = {
x: prevCoord.x + (Math.cos(angle) * distance),
y: prevCoord.y + (Math.sin(angle) * distance)
};
this.newPoint(coord, 1);
return this;
}
toPolar(angle: number, distance: number): Simulate {
angle *= Math.PI / 180;
let coord = {
x: Math.cos(angle) * distance,
y: Math.sin(angle) * distance
};
this.newPoint(coord, 1);
return this;
}
duration(duration: number): Simulate {
this.getLastPoint().duration = duration;
return this;
}
velocity(vel: number): Simulate {
let p1 = this.getLastPoint();
let p2 = this.getPreviousPoint();
let d = distance(p1, p2);
return this.duration(d / vel);
}
swipeRight(maxAngle: number, distance: number): Simulate {
// x------>
let angle = randomAngle(maxAngle);
return this.deltaPolar(angle, distance);
}
swipeLeft(maxAngle: number, distance: number): Simulate {
// <------x
let angle = randomAngle(maxAngle) + 180;
return this.deltaPolar(angle, distance);
}
swipeTop(maxAngle: number, distance: number): Simulate {
let angle = randomAngle(maxAngle) + 90;
return this.deltaPolar(angle, distance);
}
swipeBottom(maxAngle: number, distance: number): Simulate {
let angle = randomAngle(maxAngle) - 90;
return this.deltaPolar(angle, distance);
}
run(callback: Function) {
let points = this.points;
let len = points.length - 1;
let i = 0;
for (; i < len; i++) {
var p1 = points[i].coord;
var p2 = points[i + 1].coord;
var duration = points[i + 1].duration;
var vectorX = p2.x - p1.x;
var vectorY = p2.y - p1.y;
var nuSteps = Math.ceil(duration / this.timedelta);
vectorX /= nuSteps;
vectorY /= nuSteps;
for (let j = 0; j < nuSteps; j++) {
callback({
x: p1.x + vectorX * j,
y: p1.y + vectorY * j
});
}
}
this.index = i;
return this;
}
private newPoint(coord: PointerCoordinates, duration: number) {
this.points.push({
coord: coord,
duration: duration,
});
}
private getLastPoint(): Point {
let len = this.points.length;
if (len > 0) {
return this.points[len - 1];
}
throw new Error('can not call point');
}
private getPreviousPoint(): Point {
let len = this.points.length;
if (len > 1) {
return this.points[len - 2];
}
throw new Error('can not call point');
}
}
function randomAngle(maxAngle: number): number {
return (Math.random() * maxAngle * 2) - maxAngle;
}
function distance(a: PointerCoordinates, b: PointerCoordinates): number {
let deltaX = a.x - b.x;
let deltaY = a.y - a.y;
return Math.hypot(deltaX, deltaY);
}
function parseCoordinates(coord: PointerCoordinates | number, y?: number): PointerCoordinates {
if (typeof coord === 'number') {
return { x: coord, y: y };
}
return coord;
}

View File

@@ -0,0 +1,65 @@
import { SlideGesture } from './slide-gesture';
import { defaults } from '../util/util';
import { pointerCoord } from '../util/dom';
import { Platform } from '../platform/platform';
/**
* @hidden
*/
export class SlideEdgeGesture extends SlideGesture {
public edges: string[];
public maxEdgeStart: any;
private _d: any;
constructor(plt: Platform, element: HTMLElement, opts: any = {}) {
defaults(opts, {
edge: 'start',
maxEdgeStart: 50
});
super(plt, element, opts);
// Can check corners through use of eg 'left top'
this.setEdges(opts.edge);
this.maxEdgeStart = opts.maxEdgeStart;
}
setEdges(edges: string) {
const isRTL = this.plt.isRTL;
this.edges = edges.split(' ').map((value) => {
switch (value) {
case 'start': return isRTL ? 'right' : 'left';
case 'end': return isRTL ? 'left' : 'right';
default: return value;
}
});
}
canStart(ev: any): boolean {
const coord = pointerCoord(ev);
this._d = this.getContainerDimensions();
return this.edges.every(edge => this._checkEdge(edge, coord));
}
getContainerDimensions() {
const plt = this.plt;
return {
left: 0,
top: 0,
width: plt.width(),
height: plt.height()
};
}
_checkEdge(edge: string, pos: any): boolean {
const data = this._d;
const maxEdgeStart = this.maxEdgeStart;
switch (edge) {
case 'left': return pos.x <= data.left + maxEdgeStart;
case 'right': return pos.x >= data.width - maxEdgeStart;
case 'top': return pos.y <= data.top + maxEdgeStart;
case 'bottom': return pos.y >= data.height - maxEdgeStart;
}
return false;
}
}

View File

@@ -0,0 +1,108 @@
import { PanGesture } from './pan-gesture';
import { clamp, assert } from '../util/util';
import { Platform } from '../platform/platform';
import { pointerCoord } from '../util/dom';
/**
* @hidden
*/
export class SlideGesture extends PanGesture {
public slide: SlideData = null;
constructor(plt: Platform, element: HTMLElement, opts = {}) {
super(plt, element, opts);
}
/*
* Get the min and max for the slide. pageX/pageY.
* Only called on dragstart.
*/
getSlideBoundaries(slide: SlideData, ev: any) {
return {
min: 0,
max: this.getNativeElement().offsetWidth
};
}
/*
* Get the element's pos when the drag starts.
* For example, an open side menu starts at 100% and a closed
* sidemenu starts at 0%.
*/
getElementStartPos(slide: SlideData, ev: any) {
return 0;
}
onDragStart(ev: any) {
this.onSlideBeforeStart(ev);
let coord = <any>pointerCoord(ev);
let pos = coord[this.direction];
this.slide = {
min: 0,
max: 0,
pointerStartPos: pos,
pos: pos,
timestamp: Date.now(),
elementStartPos: 0,
started: true,
delta: 0,
distance: 0,
velocity: 0,
};
// TODO: we should run this in the next frame
let {min, max} = this.getSlideBoundaries(this.slide, ev);
this.slide.min = min;
this.slide.max = max;
this.slide.elementStartPos = this.getElementStartPos(this.slide, ev);
this.onSlideStart(this.slide, ev);
}
onDragMove(ev: any) {
let slide: SlideData = this.slide;
assert(slide.min !== slide.max, 'slide data must be properly initialized');
let coord = <any>pointerCoord(ev);
let newPos = coord[this.direction];
let newTimestamp = Date.now();
let velocity = (this.plt.isRTL ? (slide.pos - newPos) : (newPos - slide.pos)) / (newTimestamp - slide.timestamp);
slide.pos = newPos;
slide.timestamp = newTimestamp;
slide.distance = clamp(
slide.min,
(this.plt.isRTL ? slide.pointerStartPos - newPos : newPos - slide.pointerStartPos) + slide.elementStartPos,
slide.max
);
slide.velocity = velocity;
slide.delta = (this.plt.isRTL ? slide.pointerStartPos - newPos : newPos - slide.pointerStartPos);
this.onSlide(slide, ev);
}
onDragEnd(ev: any) {
this.onSlideEnd(this.slide, ev);
this.slide = null;
}
onSlideBeforeStart(ev?: any): void {}
onSlideStart(slide?: SlideData, ev?: any): void {}
onSlide(slide?: SlideData, ev?: any): void {}
onSlideEnd(slide?: SlideData, ev?: any): void {}
}
/**
* @hidden
*/
export interface SlideData {
min: number;
max: number;
distance: number;
delta: number;
started: boolean;
pos: any;
timestamp: number;
pointerStartPos: number;
elementStartPos: number;
velocity: number;
}

View File

@@ -0,0 +1,343 @@
import { GestureController, GestureOptions } from '../gesture-controller';
describe('gesture controller', () => {
it('should create an instance of GestureController', () => {
let c = new GestureController(null);
expect(c.isCaptured()).toEqual(false);
expect(c.isScrollDisabled()).toEqual(false);
});
it('should test scrolling enable/disable stack', () => {
let c = new GestureController(null);
c.enableScroll(1);
expect(c.isScrollDisabled()).toEqual(false);
c.disableScroll(1);
expect(c.isScrollDisabled()).toEqual(true);
c.disableScroll(1);
c.disableScroll(1);
expect(c.isScrollDisabled()).toEqual(true);
c.enableScroll(1);
expect(c.isScrollDisabled()).toEqual(false);
for (let i = 0; i < 100; i++) {
for (let j = 0; j < 100; j++) {
c.disableScroll(j);
}
}
for (let i = 0; i < 100; i++) {
expect(c.isScrollDisabled()).toEqual(true);
c.enableScroll(50 - i);
c.enableScroll(i);
}
expect(c.isScrollDisabled()).toEqual(false);
});
it('should test gesture enable/disable stack', () => {
let c = new GestureController(null);
c.enableGesture('swipe', 1);
expect(c.isDisabled('swipe')).toEqual(false);
c.disableGesture('swipe', 1);
expect(c.isDisabled('swipe')).toEqual(true);
c.disableGesture('swipe', 1);
c.disableGesture('swipe', 1);
expect(c.isDisabled('swipe')).toEqual(true);
c.enableGesture('swipe', 1);
expect(c.isDisabled('swipe')).toEqual(false);
// Disabling gestures multiple times
for (let gestureName = 0; gestureName < 10; gestureName++) {
for (let i = 0; i < 50; i++) {
for (let j = 0; j < 50; j++) {
c.disableGesture(gestureName.toString(), j);
}
}
}
for (let gestureName = 0; gestureName < 10; gestureName++) {
for (let i = 0; i < 49; i++) {
c.enableGesture(gestureName.toString(), i);
}
expect(c.isDisabled(gestureName.toString())).toEqual(true);
c.enableGesture(gestureName.toString(), 49);
expect(c.isDisabled(gestureName.toString())).toEqual(false);
}
});
it('should test if canStart', () => {
let c = new GestureController(null);
expect(c.canStart('event')).toEqual(true);
expect(c.canStart('event1')).toEqual(true);
expect(c.canStart('event')).toEqual(true);
expect(c['requestedStart']).toEqual({});
expect(c.isCaptured()).toEqual(false);
});
it('should throw error if initializing without a name a gesture delegate without options', () => {
let c = new GestureController(null);
expect(() => {
let a = {};
c.createGesture(<GestureOptions>a);
}).toThrowError();
});
it('should initialize without options', () => {
let c = new GestureController(null);
let g = c.createGesture({
name: 'event',
});
expect(g['name']).toEqual('event');
expect(g['priority']).toEqual(0);
expect(g['disableScroll']).toEqual(false);
expect(g['controller']).toEqual(c);
expect(g['id']).toEqual(1);
let g2 = c.createGesture({ name: 'event2' });
expect(g2['id']).toEqual(2);
});
it('should initialize a delegate with options', () => {
let c = new GestureController(null);
let g = c.createGesture({
name: 'swipe',
priority: -123,
disableScroll: true,
});
expect(g['name']).toEqual('swipe');
expect(g['priority']).toEqual(-123);
expect(g['disableScroll']).toEqual(true);
expect(g['controller']).toEqual(c);
expect(g['id']).toEqual(1);
});
it('should test if several gestures can be started', () => {
let c = new GestureController(null);
let g1 = c.createGesture({ name: 'swipe' });
let g2 = c.createGesture({name: 'swipe1', priority: 3});
let g3 = c.createGesture({name: 'swipe2', priority: 4});
for (let i = 0; i < 10; i++) {
expect(g1.start()).toEqual(true);
expect(g2.start()).toEqual(true);
expect(g3.start()).toEqual(true);
}
expect(c['requestedStart']).toEqual({
1: 0,
2: 3,
3: 4
});
g1.release();
g1.release();
expect(c['requestedStart']).toEqual({
2: 3,
3: 4
});
expect(g1.start()).toEqual(true);
expect(g2.start()).toEqual(true);
g3.destroy();
expect(g3['controller']).toBeNull();
expect(c['requestedStart']).toEqual({
1: 0,
2: 3,
});
});
it('should test if several gestures try to capture at the same time', () => {
let c = new GestureController(null);
let g1 = c.createGesture({name: 'swipe1'});
let g2 = c.createGesture({name: 'swipe2', priority: 2 });
let g3 = c.createGesture({name: 'swipe3', priority: 3 });
let g4 = c.createGesture({name: 'swipe4', priority: 4 });
let g5 = c.createGesture({name: 'swipe5', priority: 5 });
// Low priority capture() returns false
expect(g2.start()).toEqual(true);
expect(g3.start()).toEqual(true);
expect(g1.capture()).toEqual(false);
expect(c['requestedStart']).toEqual({
2: 2,
3: 3
});
// Low priority start() + capture() returns false
expect(g2.capture()).toEqual(false);
expect(c['requestedStart']).toEqual({
3: 3
});
// Higher priority capture() return true
expect(g4.capture()).toEqual(true);
expect(c.isScrollDisabled()).toEqual(false);
expect(c.isCaptured()).toEqual(true);
expect(c['requestedStart']).toEqual({});
// Higher priority can not capture because it is already capture
expect(g5.capture()).toEqual(false);
expect(g5.canStart()).toEqual(false);
expect(g5.start()).toEqual(false);
expect(c['requestedStart']).toEqual({});
// Only captured gesture can release
g1.release();
g2.release();
g3.release();
g5.release();
expect(c.isCaptured()).toEqual(true);
// G4 releases
g4.release();
expect(c.isCaptured()).toEqual(false);
// Once it was release, any gesture can capture
expect(g1.start()).toEqual(true);
expect(g1.capture()).toEqual(true);
});
it('should disable scrolling on capture', () => {
let c = new GestureController(null);
let g = c.createGesture({
name: 'goback',
disableScroll: true,
});
let g1 = c.createGesture({ name: 'swipe' });
g.start();
expect(c.isScrollDisabled()).toEqual(false);
g1.capture();
g.capture();
expect(c.isScrollDisabled()).toEqual(false);
g1.release();
expect(c.isScrollDisabled()).toEqual(false);
g.capture();
expect(c.isScrollDisabled()).toEqual(true);
g.destroy();
expect(c.isScrollDisabled()).toEqual(false);
});
describe('BlockerDelegate', () => {
it('create one', () => {
let c = new GestureController(null);
let b = c.createBlocker({
disableScroll: true,
disable: ['event1', 'event2', 'event3', 'event4']
});
expect(b['disable']).toEqual(['event1', 'event2', 'event3', 'event4']);
expect(b['disableScroll']).toEqual(true);
expect(b['controller']).toEqual(c);
expect(b['id']).toEqual(1);
let b2 = c.createBlocker({
disable: ['event2', 'event3', 'event4', 'event5']
});
expect(b2['disable']).toEqual(['event2', 'event3', 'event4', 'event5']);
expect(b2['disableScroll']).toEqual(false);
expect(b2['controller']).toEqual(c);
expect(b2['id']).toEqual(2);
expect(c.isDisabled('event1')).toBeFalsy();
expect(c.isDisabled('event2')).toBeFalsy();
expect(c.isDisabled('event3')).toBeFalsy();
expect(c.isDisabled('event4')).toBeFalsy();
expect(c.isDisabled('event5')).toBeFalsy();
b.block();
b.block();
expect(c.isDisabled('event1')).toBeTruthy();
expect(c.isDisabled('event2')).toBeTruthy();
expect(c.isDisabled('event3')).toBeTruthy();
expect(c.isDisabled('event4')).toBeTruthy();
expect(c.isDisabled('event5')).toBeFalsy();
b2.block();
b2.block();
b2.block();
expect(c.isDisabled('event1')).toBeTruthy();
expect(c.isDisabled('event2')).toBeTruthy();
expect(c.isDisabled('event3')).toBeTruthy();
expect(c.isDisabled('event4')).toBeTruthy();
expect(c.isDisabled('event5')).toBeTruthy();
b.unblock();
expect(c.isDisabled('event1')).toBeFalsy();
expect(c.isDisabled('event2')).toBeTruthy();
expect(c.isDisabled('event3')).toBeTruthy();
expect(c.isDisabled('event4')).toBeTruthy();
expect(c.isDisabled('event5')).toBeTruthy();
b2.destroy();
expect(b2['controller']).toBeNull();
expect(c.isDisabled('event1')).toBeFalsy();
expect(c.isDisabled('event2')).toBeFalsy();
expect(c.isDisabled('event3')).toBeFalsy();
expect(c.isDisabled('event4')).toBeFalsy();
expect(c.isDisabled('event5')).toBeFalsy();
});
it('should disable some events', () => {
let c = new GestureController(null);
let goback = c.createGesture({ name: 'goback' });
expect(goback.canStart()).toEqual(true);
let g2 = c.createGesture({ name: 'goback2' });
expect(g2.canStart()).toEqual(true);
let g3 = c.createBlocker({
disable: ['range', 'goback', 'something']
});
let g4 = c.createBlocker({
disable: ['range']
});
g3.block();
g4.block();
// goback is disabled
expect(c.isDisabled('range')).toEqual(true);
expect(c.isDisabled('goback')).toEqual(true);
expect(c.isDisabled('something')).toEqual(true);
expect(c.isDisabled('goback2')).toEqual(false);
expect(goback.canStart()).toEqual(false);
expect(goback.start()).toEqual(false);
expect(goback.capture()).toEqual(false);
// Once g3 is destroyed, goback and something should be enabled
g3.destroy();
expect(c.isDisabled('range')).toEqual(true);
expect(c.isDisabled('goback')).toEqual(false);
expect(c.isDisabled('something')).toEqual(false);
// Once g4 is destroyed, range is also enabled
g4.unblock();
expect(c.isDisabled('range')).toEqual(false);
});
});
});

View File

@@ -0,0 +1,205 @@
import { PanRecognizer } from '../recognizers';
import { Simulate } from '../simulator';
describe('recognizers', () => {
it('should not fire if it did not start', () => {
let p = new PanRecognizer('x', 2, 2);
expect(p.pan()).toEqual(0);
Simulate.from(0, 0).to(99, 0).run((coord: Coordinates) => {
expect(p.detect(coord)).toEqual(false);
});
});
it('should reset', () => {
let p = new PanRecognizer('x', 2, 2);
p.start({ x: 0, y: 0 });
expect(p.pan()).toEqual(0);
Simulate.from(0, 0).to(10, 0).run((coord: Coordinates) => {
p.detect(coord);
});
expect(p.pan()).toEqual(1);
p.start({ x: 0, y: 0 });
expect(p.pan()).toEqual(0);
Simulate.from(0, 0).to(-10, 0).run((coord: Coordinates) => {
p.detect(coord);
});
expect(p.pan()).toEqual(-1);
});
it('should fire with large threshold', () => {
let detected = false;
let p = new PanRecognizer('x', 100, 40);
p.start({ x: 0, y: 0 });
Simulate
.from(0, 0).to(99, 0)
// Since threshold is 100, it should not fire yet
.run((coord: Coordinates) => expect(p.detect(coord)).toEqual(false))
// Now it should fire
.delta(2, 0)
.run((coord: Coordinates) => {
if (p.detect(coord)) {
// it should detect a horizontal pan
expect(p.pan()).toEqual(1);
detected = true;
}
})
// It should not detect again
.delta(20, 0)
.to(0, 0)
.to(102, 0)
.run((coord: Coordinates) => expect(p.detect(coord)).toEqual(false));
expect(detected).toEqual(true);
});
it('should detect swipe left', () => {
let p = new PanRecognizer('x', 20, 20);
p.start({ x: 0, y: 0 });
Simulate
.from(0, 0).deltaPolar(19, 21).delta(-30, 0)
.run((coord: Coordinates) => p.detect(coord));
expect(p.pan()).toEqual(1);
p.start({ x: 0, y: 0 });
Simulate
.from(0, 0).deltaPolar(-19, 21).delta(-30, 0)
.run((coord: Coordinates) => p.detect(coord));
expect(p.pan()).toEqual(1);
});
it('should detect swipe right', () => {
let p = new PanRecognizer('x', 20, 20);
p.start({ x: 0, y: 0 });
Simulate
.from(0, 0).deltaPolar(180 - 19, 21).delta(30, 0)
.run((coord: Coordinates) => p.detect(coord));
expect(p.pan()).toEqual(-1);
p.start({ x: 0, y: 0 });
Simulate
.from(0, 0).deltaPolar(180 + 19, 21).delta(30, 0)
.run((coord: Coordinates) => p.detect(coord));
expect(p.pan()).toEqual(-1);
});
it('should NOT detect swipe left', () => {
let p = new PanRecognizer('x', 20, 20);
p.start({ x: 0, y: 0 });
Simulate
.from(0, 0).deltaPolar(21, 21).delta(-30, 0)
.run((coord: Coordinates) => p.detect(coord));
expect(p.pan()).toEqual(0);
p.start({ x: 0, y: 0 });
Simulate
.from(0, 0).deltaPolar(-21, 21).delta(-30, 0)
.run((coord: Coordinates) => p.detect(coord));
expect(p.pan()).toEqual(0);
});
it('should NOT detect swipe right', () => {
let p = new PanRecognizer('x', 20, 20);
p.start({ x: 0, y: 0 });
Simulate
.from(0, 0).deltaPolar(180 - 21, 21).delta(30, 0)
.run((coord: Coordinates) => p.detect(coord));
expect(p.pan()).toEqual(0);
p.start({ x: 0, y: 0 });
Simulate
.from(0, 0).deltaPolar(180 + 21, 21).delta(30, 0)
.run((coord: Coordinates) => p.detect(coord));
expect(p.pan()).toEqual(0);
});
it('should detect swipe top', () => {
let p = new PanRecognizer('y', 20, 20);
p.start({ x: 0, y: 0 });
Simulate
.from(0, 0).deltaPolar(90 - 19, 21).delta(-30, 0)
.run((coord: Coordinates) => p.detect(coord));
expect(p.pan()).toEqual(1);
p.start({ x: 0, y: 0 });
Simulate
.from(0, 0).deltaPolar(90 + 19, 21).delta(-30, 0)
.run((coord: Coordinates) => p.detect(coord));
expect(p.pan()).toEqual(1);
});
it('should detect swipe bottom', () => {
let p = new PanRecognizer('y', 20, 20);
p.start({ x: 0, y: 0 });
Simulate
.from(0, 0).deltaPolar(-90 + 19, 21).delta(30, 0)
.run((coord: Coordinates) => p.detect(coord));
expect(p.pan()).toEqual(-1);
p.start({ x: 0, y: 0 });
Simulate
.from(0, 0).deltaPolar(-90 - 19, 21).delta(30, 0)
.run((coord: Coordinates) => p.detect(coord));
expect(p.pan()).toEqual(-1);
});
it('should NOT detect swipe top', () => {
let p = new PanRecognizer('y', 20, 20);
p.start({ x: 0, y: 0 });
Simulate
.from(0, 0).deltaPolar(90 - 21, 21).delta(-30, 0)
.run((coord: Coordinates) => p.detect(coord));
expect(p.pan()).toEqual(0);
p.start({ x: 0, y: 0 });
Simulate
.from(0, 0).deltaPolar(90 + 21, 21).delta(-30, 0)
.run((coord: Coordinates) => p.detect(coord));
expect(p.pan()).toEqual(0);
});
it('should NOT detect swipe bottom', () => {
let p = new PanRecognizer('y', 20, 20);
p.start({ x: 0, y: 0 });
Simulate
.from(0, 0).deltaPolar(-90 + 21, 21).delta(30, 0)
.run((coord: Coordinates) => p.detect(coord));
expect(p.pan()).toEqual(0);
p.start({ x: 0, y: 0 });
Simulate
.from(0, 0).deltaPolar(-90 - 21, 21).delta(30, 0)
.run((coord: Coordinates) => p.detect(coord));
expect(p.pan()).toEqual(0);
});
it('should NOT confuse between pan Y and X', () => {
let p = new PanRecognizer('x', 20, 20);
p.start({ x: 0, y: 0 });
Simulate
.from(0, 0).deltaPolar(90, 21).delta(30, 0)
.run((coord: Coordinates) => p.detect(coord));
expect(p.pan()).toEqual(0);
});
it('should NOT confuse between pan X and Y', () => {
let p = new PanRecognizer('y', 20, 20);
p.start({ x: 0, y: 0 });
Simulate
.from(0, 0).delta(30, 0)
.run((coord: Coordinates) => p.detect(coord));
expect(p.pan()).toEqual(0);
});
});

View File

@@ -0,0 +1,57 @@
import { PointerEvents, PointerEventsConfig } from './pointer-events';
import { Platform, EventListenerOptions } from '../platform/platform';
/**
* @hidden
*/
export class UIEventManager {
private evts: Function[] = [];
constructor(public plt: Platform) {}
pointerEvents(config: PointerEventsConfig): PointerEvents {
if (!config.element || !config.pointerDown) {
console.error('PointerEvents config is invalid');
return;
}
const eventListnerOpts: EventListenerOptions = {
capture: config.capture,
passive: config.passive,
zone: config.zone
};
const pointerEvents = new PointerEvents(
this.plt,
config.element,
config.pointerDown,
config.pointerMove,
config.pointerUp,
eventListnerOpts);
const removeFunc = () => pointerEvents.destroy();
this.evts.push(removeFunc);
return pointerEvents;
}
listen(ele: any, eventName: string, callback: any, opts: EventListenerOptions): Function {
if (ele) {
var removeFunc = this.plt.registerListener(ele, eventName, callback, opts);
this.evts.push(removeFunc);
return removeFunc;
}
}
unlistenAll() {
this.evts.forEach(unRegEvent => {
unRegEvent();
});
this.evts.length = 0;
}
destroy() {
this.unlistenAll();
this.evts = null;
}
}