mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-18 03:00:58 +08:00
fix(datetime): fix gesture
This commit is contained in:
@ -58,7 +58,7 @@ export class ItemSliding {
|
||||
|
||||
this.updateOptions();
|
||||
|
||||
this.gesture = (await import('../../utils/gesture/gesture')).create({
|
||||
this.gesture = (await import('../../utils/gesture/gesture')).createGesture({
|
||||
el: this.el,
|
||||
queue: this.queue,
|
||||
gestureName: 'item-swipe',
|
||||
@ -69,7 +69,7 @@ export class ItemSliding {
|
||||
onMove: this.onDragMove.bind(this),
|
||||
onEnd: this.onDragEnd.bind(this),
|
||||
});
|
||||
this.gesture.disabled = false;
|
||||
this.gesture.setDisabled(false);
|
||||
}
|
||||
|
||||
componentDidUnload() {
|
||||
|
@ -175,7 +175,7 @@ export class Menu {
|
||||
this.menuCtrl!._register(this);
|
||||
this.ionMenuChange.emit({ disabled: !isEnabled, open: this._isOpen });
|
||||
|
||||
this.gesture = (await import('../../utils/gesture/gesture')).create({
|
||||
this.gesture = (await import('../../utils/gesture/gesture')).createGesture({
|
||||
el: this.doc,
|
||||
queue: this.queue,
|
||||
gestureName: 'menu-swipe',
|
||||
@ -439,7 +439,7 @@ export class Menu {
|
||||
private updateState() {
|
||||
const isActive = this.isActive();
|
||||
if (this.gesture) {
|
||||
this.gesture.disabled = !isActive || !this.swipeEnabled;
|
||||
this.gesture.setDisabled(!isActive || !this.swipeEnabled);
|
||||
}
|
||||
|
||||
// Close menu inmediately
|
||||
|
@ -41,7 +41,7 @@ export class Nav implements NavOutlet {
|
||||
@Watch('swipeBackEnabled')
|
||||
swipeBackEnabledChanged() {
|
||||
if (this.gesture) {
|
||||
this.gesture.disabled = !this.swipeBackEnabled;
|
||||
this.gesture.setDisabled(!this.swipeBackEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
@ -110,7 +110,7 @@ export class Nav implements NavOutlet {
|
||||
async componentDidLoad() {
|
||||
this.rootChanged();
|
||||
|
||||
this.gesture = (await import('../../utils/gesture/gesture')).create({
|
||||
this.gesture = (await import('../../utils/gesture/gesture')).createGesture({
|
||||
el: this.win.document.body,
|
||||
queue: this.queue,
|
||||
gestureName: 'goback-swipe',
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Component, Element, Prop, QueueApi } from '@stencil/core';
|
||||
|
||||
import { Gesture, GestureDetail, Mode, PickerColumn, PickerColumnOption } from '../../interface';
|
||||
import { Gesture, GestureDetail, Mode, PickerColumn } from '../../interface';
|
||||
import { hapticSelectionChanged } from '../../utils';
|
||||
import { clamp } from '../../utils/helpers';
|
||||
import { createThemedClasses } from '../../utils/theme';
|
||||
@ -18,13 +18,12 @@ export class PickerColumnCmp {
|
||||
private minY!: number;
|
||||
private maxY!: number;
|
||||
private optHeight = 0;
|
||||
private pos: number[] = [];
|
||||
private rotateFactor = 0;
|
||||
private scaleFactor = 1;
|
||||
private startY?: number;
|
||||
private velocity = 0;
|
||||
private y = 0;
|
||||
private gesture?: Gesture;
|
||||
private rafId: any;
|
||||
|
||||
@Element() el!: HTMLElement;
|
||||
|
||||
@ -54,27 +53,17 @@ export class PickerColumnCmp {
|
||||
|
||||
this.refresh();
|
||||
|
||||
this.gesture = (await import('../../utils/gesture/gesture')).create({
|
||||
this.gesture = (await import('../../utils/gesture/gesture')).createGesture({
|
||||
el: this.el,
|
||||
queue: this.queue,
|
||||
gestureName: 'picker-swipe',
|
||||
gesturePriority: 10,
|
||||
threshold: 0,
|
||||
canStart: this.canStart.bind(this),
|
||||
onStart: this.onDragStart.bind(this),
|
||||
onMove: this.onDragMove.bind(this),
|
||||
onEnd: this.onDragEnd.bind(this),
|
||||
});
|
||||
this.gesture.disabled = false;
|
||||
}
|
||||
|
||||
private optClick(ev: Event, index: number) {
|
||||
if (!this.velocity) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
this.setSelected(index, 150);
|
||||
}
|
||||
this.gesture.setDisabled(false);
|
||||
}
|
||||
|
||||
private setSelected(selectedIndex: number, duration: number) {
|
||||
@ -85,6 +74,7 @@ export class PickerColumnCmp {
|
||||
this.velocity = 0;
|
||||
|
||||
// set what y position we're at
|
||||
cancelAnimationFrame(this.rafId);
|
||||
this.update(y, duration, true, true);
|
||||
}
|
||||
|
||||
@ -92,16 +82,8 @@ export class PickerColumnCmp {
|
||||
// ensure we've got a good round number :)
|
||||
y = Math.round(y);
|
||||
|
||||
let i: number;
|
||||
let button: any;
|
||||
let opt: PickerColumnOption;
|
||||
let optOffset: number;
|
||||
let visible: boolean;
|
||||
let translateY = 0;
|
||||
let translateZ = 0;
|
||||
let rotateX: number;
|
||||
let transform: string;
|
||||
let selected: boolean;
|
||||
|
||||
const parent = this.el.querySelector('.picker-opts')!;
|
||||
const children = parent.children;
|
||||
@ -111,15 +93,15 @@ export class PickerColumnCmp {
|
||||
const durationStr = (duration === 0) ? null : duration + 'ms';
|
||||
const scaleStr = `scale(${this.scaleFactor})`;
|
||||
|
||||
for (i = 0; i < length; i++) {
|
||||
button = children[i];
|
||||
opt = this.col.options[i];
|
||||
optOffset = (i * this.optHeight) + y;
|
||||
visible = true;
|
||||
transform = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
const button = children[i] as HTMLElement;
|
||||
const opt = this.col.options[i];
|
||||
const optOffset = (i * this.optHeight) + y;
|
||||
let visible = true;
|
||||
let transform = '';
|
||||
|
||||
if (this.rotateFactor !== 0) {
|
||||
rotateX = optOffset * this.rotateFactor;
|
||||
const rotateX = optOffset * this.rotateFactor;
|
||||
if (Math.abs(rotateX) > 90) {
|
||||
visible = false;
|
||||
} else {
|
||||
@ -135,7 +117,7 @@ export class PickerColumnCmp {
|
||||
}
|
||||
}
|
||||
|
||||
selected = selectedIndex === i;
|
||||
const selected = selectedIndex === i;
|
||||
if (visible) {
|
||||
transform += `translate3d(0px,${translateY}px,${translateZ}px) `;
|
||||
if (this.scaleFactor !== 1 && !selected) {
|
||||
@ -204,7 +186,7 @@ export class PickerColumnCmp {
|
||||
? Math.max(this.velocity, 1)
|
||||
: Math.min(this.velocity, -1);
|
||||
|
||||
y = Math.round(this.y - this.velocity);
|
||||
y = Math.round(this.y + this.velocity);
|
||||
|
||||
if (y > this.minY) {
|
||||
// whoops, it's trying to scroll up farther than the options we have!
|
||||
@ -223,7 +205,7 @@ export class PickerColumnCmp {
|
||||
|
||||
if (notLockedIn) {
|
||||
// isn't locked in yet, keep decelerating until it is
|
||||
this.queue.read(() => this.decelerate());
|
||||
this.rafId = requestAnimationFrame(() => this.decelerate());
|
||||
}
|
||||
|
||||
} else if (this.y % this.optHeight !== 0) {
|
||||
@ -246,11 +228,8 @@ export class PickerColumnCmp {
|
||||
}
|
||||
|
||||
// TODO should this check disabled?
|
||||
private canStart() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private onDragStart(detail: GestureDetail): boolean {
|
||||
private onDragStart(detail: GestureDetail) {
|
||||
// We have to prevent default in order to block scrolling under the picker
|
||||
// but we DO NOT have to stop propagation, since we still want
|
||||
// some "click" events to capture
|
||||
@ -259,14 +238,8 @@ export class PickerColumnCmp {
|
||||
detail.event.stopPropagation();
|
||||
}
|
||||
|
||||
// remember where the pointer started from
|
||||
this.startY = detail.startY;
|
||||
|
||||
// reset everything
|
||||
this.velocity = 0;
|
||||
this.pos.length = 0;
|
||||
this.pos.push(this.startY, Date.now());
|
||||
|
||||
cancelAnimationFrame(this.rafId);
|
||||
const options = this.col.options;
|
||||
let minY = (options.length - 1);
|
||||
let maxY = 0;
|
||||
@ -279,7 +252,6 @@ export class PickerColumnCmp {
|
||||
|
||||
this.minY = (minY * this.optHeight * -1);
|
||||
this.maxY = (maxY * this.optHeight * -1);
|
||||
return true;
|
||||
}
|
||||
|
||||
private onDragMove(detail: GestureDetail) {
|
||||
@ -288,15 +260,8 @@ export class PickerColumnCmp {
|
||||
detail.event.stopPropagation();
|
||||
}
|
||||
|
||||
const currentY = detail.currentY;
|
||||
this.pos.push(currentY, Date.now());
|
||||
|
||||
if (this.startY === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// update the scroll position relative to pointer start position
|
||||
let y = this.y + (currentY - this.startY);
|
||||
let y = this.y + detail.deltaY;
|
||||
|
||||
if (y > this.minY) {
|
||||
// scrolling up higher than scroll area
|
||||
@ -321,12 +286,6 @@ export class PickerColumnCmp {
|
||||
}
|
||||
|
||||
private onDragEnd(detail: GestureDetail) {
|
||||
if (this.startY === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.velocity = 0;
|
||||
|
||||
if (this.bounceFrom > 0) {
|
||||
// bounce back up
|
||||
this.update(this.minY, 100, true, true);
|
||||
@ -337,36 +296,20 @@ export class PickerColumnCmp {
|
||||
return;
|
||||
}
|
||||
|
||||
const endY = detail.currentY;
|
||||
this.velocity = clamp(-MAX_PICKER_SPEED, detail.velocityY * 17, MAX_PICKER_SPEED);
|
||||
if (this.velocity === 0 && detail.deltaY === 0) {
|
||||
const opt = (detail.event.target as Element).closest('.picker-opt');
|
||||
if (opt && opt.hasAttribute('opt-index')) {
|
||||
this.setSelected(parseInt(opt.getAttribute('opt-index')!, 10), 150);
|
||||
}
|
||||
|
||||
this.pos.push(endY, Date.now());
|
||||
|
||||
const endPos = (this.pos.length - 1);
|
||||
let startPos = endPos;
|
||||
const timeRange = (Date.now() - 100);
|
||||
|
||||
// move pointer to position measured 100ms ago
|
||||
for (let i = endPos; i > 0 && this.pos[i] > timeRange; i -= 2) {
|
||||
startPos = i;
|
||||
} else {
|
||||
if (Math.abs(detail.deltaY) > 3) {
|
||||
const y = this.y + detail.deltaY;
|
||||
this.update(y, 0, true, true);
|
||||
}
|
||||
this.decelerate();
|
||||
}
|
||||
|
||||
if (startPos !== endPos) {
|
||||
// compute relative movement between these two points
|
||||
const timeOffset = (this.pos[endPos] - this.pos[startPos]);
|
||||
const movedTop = (this.pos[startPos - 1] - this.pos[endPos - 1]);
|
||||
|
||||
// based on XXms compute the movement to apply for each render step
|
||||
const velocity = ((movedTop / timeOffset) * FRAME_MS);
|
||||
this.velocity = clamp(-MAX_PICKER_SPEED, velocity, MAX_PICKER_SPEED);
|
||||
}
|
||||
|
||||
if (Math.abs(endY - this.startY) > 3) {
|
||||
const y = this.y + (endY - this.startY);
|
||||
this.update(y, 0, true, true);
|
||||
}
|
||||
|
||||
this.startY = undefined;
|
||||
this.decelerate();
|
||||
}
|
||||
|
||||
private refresh() {
|
||||
@ -406,45 +349,35 @@ export class PickerColumnCmp {
|
||||
const col = this.col;
|
||||
|
||||
const options = col.options.map(o => {
|
||||
if (typeof o === 'string') {
|
||||
o = { text: o };
|
||||
}
|
||||
return o;
|
||||
return (typeof o === 'string')
|
||||
? { text: o }
|
||||
: o;
|
||||
})
|
||||
.filter(o => o !== null);
|
||||
|
||||
const results: JSX.Element[] = [];
|
||||
|
||||
if (col.prefix) {
|
||||
results.push(
|
||||
const Button = 'button' as any;
|
||||
return [
|
||||
col.prefix && (
|
||||
<div class="picker-prefix" style={{ width: col.prefixWidth! }}>
|
||||
{col.prefix}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
results.push(
|
||||
),
|
||||
<div class="picker-opts" style={{ maxWidth: col.optionsWidth! }}>
|
||||
{options.map((o, index) =>
|
||||
<button
|
||||
<Button
|
||||
class={{ 'picker-opt': true, 'picker-opt-disabled': !!o.disabled }}
|
||||
disable-activated
|
||||
onClick={event => this.optClick(event, index)}>
|
||||
opt-index={index}>
|
||||
{o.text}
|
||||
</button>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
if (col.suffix) {
|
||||
results.push(
|
||||
</div>,
|
||||
col.suffix && (
|
||||
<div class="picker-suffix" style={{ width: col.suffixWidth! }}>
|
||||
{col.suffix}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return results;
|
||||
)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,7 +98,7 @@ export class Range implements BaseInput {
|
||||
@Watch('disabled')
|
||||
protected disabledChanged() {
|
||||
if (this.gesture) {
|
||||
this.gesture.disabled = this.disabled;
|
||||
this.gesture.setDisabled(this.disabled);
|
||||
}
|
||||
this.emitStyle();
|
||||
}
|
||||
@ -145,7 +145,7 @@ export class Range implements BaseInput {
|
||||
}
|
||||
|
||||
async componentDidLoad() {
|
||||
this.gesture = (await import('../../utils/gesture/gesture')).create({
|
||||
this.gesture = (await import('../../utils/gesture/gesture')).createGesture({
|
||||
el: this.rangeSlider!,
|
||||
queue: this.queue,
|
||||
gestureName: 'range',
|
||||
@ -155,7 +155,7 @@ export class Range implements BaseInput {
|
||||
onMove: this.onDragMove.bind(this),
|
||||
onEnd: this.onDragEnd.bind(this),
|
||||
});
|
||||
this.gesture.disabled = this.disabled;
|
||||
this.gesture.setDisabled(this.disabled);
|
||||
}
|
||||
|
||||
@Listen('ionIncrease')
|
||||
|
@ -67,7 +67,7 @@ export class Refresher {
|
||||
@Watch('disabled')
|
||||
disabledChanged() {
|
||||
if (this.gesture) {
|
||||
this.gesture.disabled = this.disabled;
|
||||
this.gesture.setDisabled(this.disabled);
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,7 +102,7 @@ export class Refresher {
|
||||
console.error('ion-refresher did not attach, make sure the parent is an ion-content.');
|
||||
}
|
||||
|
||||
this.gesture = (await import('../../utils/gesture/gesture')).create({
|
||||
this.gesture = (await import('../../utils/gesture/gesture')).createGesture({
|
||||
el: this.el.closest('ion-content') as any,
|
||||
queue: this.queue,
|
||||
gestureName: 'refresher',
|
||||
|
@ -40,7 +40,7 @@ export class ReorderGroup {
|
||||
@Watch('disabled')
|
||||
disabledChanged() {
|
||||
if (this.gesture) {
|
||||
this.gesture.disabled = this.disabled;
|
||||
this.gesture.setDisabled(this.disabled);
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,7 +51,7 @@ export class ReorderGroup {
|
||||
this.scrollEl = contentEl.getScrollElement();
|
||||
}
|
||||
|
||||
this.gesture = (await import('../../utils/gesture/gesture')).create({
|
||||
this.gesture = (await import('../../utils/gesture/gesture')).createGesture({
|
||||
el: this.doc.body,
|
||||
queue: this.queue,
|
||||
gestureName: 'reorder',
|
||||
|
@ -94,7 +94,7 @@ export class Toggle implements CheckboxInput {
|
||||
'interactive-disabled': this.disabled,
|
||||
});
|
||||
if (this.gesture) {
|
||||
this.gesture.disabled = this.disabled;
|
||||
this.gesture.setDisabled(this.disabled);
|
||||
}
|
||||
}
|
||||
|
||||
@ -112,7 +112,7 @@ export class Toggle implements CheckboxInput {
|
||||
}
|
||||
}
|
||||
|
||||
this.gesture = (await import('../../utils/gesture/gesture')).create({
|
||||
this.gesture = (await import('../../utils/gesture/gesture')).createGesture({
|
||||
el: this.el,
|
||||
queue: this.queue,
|
||||
gestureName: 'toggle',
|
||||
|
@ -2,7 +2,7 @@ import { QueueApi } from '@stencil/core';
|
||||
|
||||
import { PanRecognizer } from './recognizers';
|
||||
|
||||
import { GestureDelegate, gestureController } from './gesture-controller';
|
||||
import { gestureController } from './gesture-controller';
|
||||
import { PointerEvents } from './pointer-events';
|
||||
|
||||
export interface GestureDetail {
|
||||
@ -23,6 +23,11 @@ export interface GestureDetail {
|
||||
|
||||
export type GestureCallback = (detail?: GestureDetail) => boolean | void;
|
||||
|
||||
export interface Gesture {
|
||||
setDisabled(disabled: boolean): void;
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
export interface GestureConfig {
|
||||
el: Node;
|
||||
disableScroll?: boolean;
|
||||
@ -43,100 +48,70 @@ export interface GestureConfig {
|
||||
notCaptured?: GestureCallback;
|
||||
}
|
||||
|
||||
export function create(config: GestureConfig) {
|
||||
return new Gesture(config);
|
||||
}
|
||||
export function createGesture(config: GestureConfig): Gesture {
|
||||
const finalConfig = {
|
||||
disableScroll: false,
|
||||
direction: 'x',
|
||||
gesturePriority: 0,
|
||||
passive: true,
|
||||
maxAngle: 40,
|
||||
threshold: 10,
|
||||
|
||||
export class Gesture {
|
||||
...config
|
||||
};
|
||||
|
||||
private detail: GestureDetail;
|
||||
private positions: number[] = [];
|
||||
private gesture: GestureDelegate;
|
||||
private pan: PanRecognizer;
|
||||
private hasCapturedPan = false;
|
||||
private hasStartedPan = false;
|
||||
private hasFiredStart = true;
|
||||
private isMoveQueued = false;
|
||||
private pointerEvents: PointerEvents;
|
||||
let hasCapturedPan = false;
|
||||
let hasStartedPan = false;
|
||||
let hasFiredStart = true;
|
||||
let isMoveQueued = false;
|
||||
|
||||
private canStart?: GestureCallback;
|
||||
private onWillStart?: (_: GestureDetail) => Promise<void>;
|
||||
private onStart?: GestureCallback;
|
||||
private onMove?: GestureCallback;
|
||||
private onEnd?: GestureCallback;
|
||||
private notCaptured?: GestureCallback;
|
||||
private threshold: number;
|
||||
private queue: QueueApi;
|
||||
const canStart = finalConfig.canStart;
|
||||
const onWillStart = finalConfig.onWillStart;
|
||||
const onStart = finalConfig.onStart;
|
||||
const onEnd = finalConfig.onEnd;
|
||||
const notCaptured = finalConfig.notCaptured;
|
||||
const onMove = finalConfig.onMove;
|
||||
const threshold = finalConfig.threshold;
|
||||
const queue = finalConfig.queue;
|
||||
|
||||
constructor(config: GestureConfig) {
|
||||
const finalConfig = {
|
||||
disableScroll: false,
|
||||
direction: 'x',
|
||||
gesturePriority: 0,
|
||||
passive: true,
|
||||
maxAngle: 40,
|
||||
threshold: 10,
|
||||
const detail = {
|
||||
type: 'pan',
|
||||
startX: 0,
|
||||
startY: 0,
|
||||
startTimeStamp: 0,
|
||||
currentX: 0,
|
||||
currentY: 0,
|
||||
velocityX: 0,
|
||||
velocityY: 0,
|
||||
deltaX: 0,
|
||||
deltaY: 0,
|
||||
timeStamp: 0,
|
||||
event: undefined as any,
|
||||
data: undefined
|
||||
};
|
||||
|
||||
...config
|
||||
};
|
||||
const pointerEvents = new PointerEvents(
|
||||
finalConfig.el,
|
||||
pointerDown,
|
||||
pointerMove,
|
||||
pointerUp,
|
||||
{
|
||||
capture: false,
|
||||
}
|
||||
);
|
||||
|
||||
this.canStart = finalConfig.canStart;
|
||||
this.onWillStart = finalConfig.onWillStart;
|
||||
this.onStart = finalConfig.onStart;
|
||||
this.onEnd = finalConfig.onEnd;
|
||||
this.onMove = finalConfig.onMove;
|
||||
this.threshold = finalConfig.threshold;
|
||||
this.queue = finalConfig.queue;
|
||||
const pan = new PanRecognizer(finalConfig.direction, finalConfig.threshold, finalConfig.maxAngle);
|
||||
const gesture = gestureController.createGesture({
|
||||
name: config.gestureName,
|
||||
priority: config.gesturePriority,
|
||||
disableScroll: config.disableScroll
|
||||
});
|
||||
|
||||
this.detail = {
|
||||
type: 'pan',
|
||||
startX: 0,
|
||||
startY: 0,
|
||||
startTimeStamp: 0,
|
||||
currentX: 0,
|
||||
currentY: 0,
|
||||
velocityX: 0,
|
||||
velocityY: 0,
|
||||
deltaX: 0,
|
||||
deltaY: 0,
|
||||
timeStamp: 0,
|
||||
event: undefined as any,
|
||||
data: undefined
|
||||
};
|
||||
|
||||
this.pointerEvents = new PointerEvents(
|
||||
finalConfig.el,
|
||||
this.pointerDown.bind(this),
|
||||
this.pointerMove.bind(this),
|
||||
this.pointerUp.bind(this),
|
||||
{
|
||||
capture: false,
|
||||
}
|
||||
);
|
||||
|
||||
this.pan = new PanRecognizer(finalConfig.direction, finalConfig.threshold, finalConfig.maxAngle);
|
||||
this.gesture = gestureController.createGesture({
|
||||
name: config.gestureName,
|
||||
priority: config.gesturePriority,
|
||||
disableScroll: config.disableScroll
|
||||
});
|
||||
}
|
||||
|
||||
set disabled(disabled: boolean) {
|
||||
this.pointerEvents.disabled = disabled;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.gesture.destroy();
|
||||
this.pointerEvents.destroy();
|
||||
}
|
||||
|
||||
private pointerDown(ev: UIEvent): boolean {
|
||||
function pointerDown(ev: UIEvent): boolean {
|
||||
const timeStamp = now(ev);
|
||||
if (this.hasStartedPan || !this.hasFiredStart) {
|
||||
if (hasStartedPan || !hasFiredStart) {
|
||||
return false;
|
||||
}
|
||||
const detail = this.detail;
|
||||
|
||||
updateDetail(ev, detail);
|
||||
detail.startX = detail.currentX;
|
||||
@ -144,109 +119,90 @@ export class Gesture {
|
||||
detail.startTimeStamp = detail.timeStamp = timeStamp;
|
||||
detail.velocityX = detail.velocityY = detail.deltaX = detail.deltaY = 0;
|
||||
detail.event = ev;
|
||||
this.positions.length = 0;
|
||||
|
||||
// Check if gesture can start
|
||||
if (this.canStart && this.canStart(detail) === false) {
|
||||
if (canStart && canStart(detail) === false) {
|
||||
return false;
|
||||
}
|
||||
// Release fallback
|
||||
this.gesture.release();
|
||||
gesture.release();
|
||||
|
||||
// Start gesture
|
||||
if (!this.gesture.start()) {
|
||||
if (!gesture.start()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.positions.push(detail.currentX, detail.currentY, timeStamp);
|
||||
this.hasStartedPan = true;
|
||||
if (this.threshold === 0) {
|
||||
return this.tryToCapturePan();
|
||||
hasStartedPan = true;
|
||||
if (threshold === 0) {
|
||||
return tryToCapturePan();
|
||||
}
|
||||
this.pan.start(detail.startX, detail.startY);
|
||||
pan.start(detail.startX, detail.startY);
|
||||
return true;
|
||||
}
|
||||
|
||||
private pointerMove(ev: UIEvent) {
|
||||
function pointerMove(ev: UIEvent) {
|
||||
// fast path, if gesture is currently captured
|
||||
// do minimun job to get user-land even dispatched
|
||||
if (this.hasCapturedPan) {
|
||||
if (!this.isMoveQueued && this.hasFiredStart) {
|
||||
this.isMoveQueued = true;
|
||||
this.calcGestureData(ev);
|
||||
this.queue.write(this.fireOnMove.bind(this));
|
||||
if (hasCapturedPan) {
|
||||
if (!isMoveQueued && hasFiredStart) {
|
||||
isMoveQueued = true;
|
||||
calcGestureData(ev);
|
||||
queue.write(fireOnMove);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// gesture is currently being detected
|
||||
const detail = this.detail;
|
||||
this.calcGestureData(ev);
|
||||
if (this.pan.detect(detail.currentX, detail.currentY)) {
|
||||
if (this.pan.isGesture()) {
|
||||
if (!this.tryToCapturePan()) {
|
||||
this.abortGesture();
|
||||
calcGestureData(ev);
|
||||
if (pan.detect(detail.currentX, detail.currentY)) {
|
||||
if (pan.isGesture()) {
|
||||
if (!tryToCapturePan()) {
|
||||
abortGesture();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fireOnMove() {
|
||||
function fireOnMove() {
|
||||
// Since fireOnMove is called inside a RAF, onEnd() might be called,
|
||||
// we must double check hasCapturedPan
|
||||
if (!this.hasCapturedPan) {
|
||||
if (!hasCapturedPan) {
|
||||
return;
|
||||
}
|
||||
const detail = this.detail;
|
||||
this.isMoveQueued = false;
|
||||
if (this.onMove) {
|
||||
this.onMove(detail);
|
||||
isMoveQueued = false;
|
||||
if (onMove) {
|
||||
onMove(detail);
|
||||
}
|
||||
}
|
||||
|
||||
private calcGestureData(ev: UIEvent) {
|
||||
const detail = this.detail;
|
||||
function calcGestureData(ev: UIEvent) {
|
||||
const prevX = detail.currentX;
|
||||
const prevY = detail.currentY;
|
||||
const prevT = detail.timeStamp;
|
||||
|
||||
updateDetail(ev, detail);
|
||||
|
||||
const currentX = detail.currentX;
|
||||
const currentY = detail.currentY;
|
||||
const timestamp = detail.timeStamp = now(ev);
|
||||
const timeDelta = timestamp - prevT;
|
||||
if (timeDelta > 0 && timeDelta < 100) {
|
||||
const velocityX = (currentX - prevX) / timeDelta;
|
||||
const velocityY = (currentY - prevY) / timeDelta;
|
||||
detail.velocityX = velocityX * 0.7 + detail.velocityX * 0.3;
|
||||
detail.velocityY = velocityY * 0.7 + detail.velocityY * 0.3;
|
||||
}
|
||||
detail.deltaX = currentX - detail.startX;
|
||||
detail.deltaY = currentY - detail.startY;
|
||||
detail.event = ev;
|
||||
|
||||
const timeRange = timestamp - 100;
|
||||
const positions = this.positions;
|
||||
let startPos = positions.length - 1;
|
||||
|
||||
// move pointer to position measured 100ms ago
|
||||
while (startPos > 0 && positions[startPos] > timeRange) {
|
||||
startPos -= 3;
|
||||
}
|
||||
|
||||
if (startPos > 1) {
|
||||
// compute relative movement between these two points
|
||||
const frequency = 1 / (positions[startPos] - timestamp);
|
||||
const movedY = positions[startPos - 1] - currentY;
|
||||
const movedX = positions[startPos - 2] - currentX;
|
||||
|
||||
// based on XXms compute the movement to apply for each render step
|
||||
// velocity = space/time = s*(1/t) = s*frequency
|
||||
detail.velocityX = movedX * frequency;
|
||||
detail.velocityY = movedY * frequency;
|
||||
} else {
|
||||
detail.velocityX = 0;
|
||||
detail.velocityY = 0;
|
||||
}
|
||||
positions.push(currentX, currentY, timestamp);
|
||||
}
|
||||
|
||||
private tryToCapturePan(): boolean {
|
||||
if (this.gesture && !this.gesture.capture()) {
|
||||
function tryToCapturePan(): boolean {
|
||||
if (gesture && !gesture.capture()) {
|
||||
return false;
|
||||
}
|
||||
this.hasCapturedPan = true;
|
||||
this.hasFiredStart = false;
|
||||
hasCapturedPan = true;
|
||||
hasFiredStart = false;
|
||||
|
||||
// reset start position since the real user-land event starts here
|
||||
// If the pan detector threshold is big, not reseting the start position
|
||||
@ -254,71 +210,77 @@ export class Gesture {
|
||||
// the array of positions used to calculate the gesture velocity does not
|
||||
// need to be cleaned, more points in the positions array always results in a
|
||||
// more acurate value of the velocity.
|
||||
const detail = this.detail;
|
||||
detail.startX = detail.currentX;
|
||||
detail.startY = detail.currentY;
|
||||
detail.startTimeStamp = detail.timeStamp;
|
||||
|
||||
if (this.onWillStart) {
|
||||
this.onWillStart(this.detail).then(this.fireOnStart.bind(this));
|
||||
if (onWillStart) {
|
||||
onWillStart(detail).then(fireOnStart);
|
||||
} else {
|
||||
this.fireOnStart();
|
||||
fireOnStart();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private fireOnStart() {
|
||||
if (this.onStart) {
|
||||
this.onStart(this.detail);
|
||||
function fireOnStart() {
|
||||
if (onStart) {
|
||||
onStart(detail);
|
||||
}
|
||||
this.hasFiredStart = true;
|
||||
hasFiredStart = true;
|
||||
}
|
||||
|
||||
private abortGesture() {
|
||||
this.reset();
|
||||
this.pointerEvents.stop();
|
||||
if (this.notCaptured) {
|
||||
this.notCaptured(this.detail);
|
||||
function abortGesture() {
|
||||
reset();
|
||||
pointerEvents.stop();
|
||||
if (notCaptured) {
|
||||
notCaptured(detail);
|
||||
}
|
||||
}
|
||||
|
||||
private reset() {
|
||||
this.hasCapturedPan = false;
|
||||
this.hasStartedPan = false;
|
||||
this.isMoveQueued = false;
|
||||
this.hasFiredStart = true;
|
||||
if (this.gesture) {
|
||||
this.gesture.release();
|
||||
}
|
||||
function reset() {
|
||||
hasCapturedPan = false;
|
||||
hasStartedPan = false;
|
||||
isMoveQueued = false;
|
||||
hasFiredStart = true;
|
||||
|
||||
gesture.release();
|
||||
}
|
||||
|
||||
// END *************************
|
||||
|
||||
private pointerUp(ev: UIEvent) {
|
||||
const hasCaptured = this.hasCapturedPan;
|
||||
const hasFiredStart = this.hasFiredStart;
|
||||
this.reset();
|
||||
function pointerUp(ev: UIEvent) {
|
||||
const tmpHasCaptured = hasCapturedPan;
|
||||
const tmpHasFiredStart = hasFiredStart;
|
||||
reset();
|
||||
|
||||
if (!hasFiredStart) {
|
||||
if (!tmpHasFiredStart) {
|
||||
return;
|
||||
}
|
||||
this.calcGestureData(ev);
|
||||
|
||||
const detail = this.detail;
|
||||
calcGestureData(ev);
|
||||
|
||||
// Try to capture press
|
||||
if (hasCaptured) {
|
||||
if (this.onEnd) {
|
||||
this.onEnd(detail);
|
||||
if (tmpHasCaptured) {
|
||||
if (onEnd) {
|
||||
onEnd(detail);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Not captured any event
|
||||
if (this.notCaptured) {
|
||||
this.notCaptured(detail);
|
||||
if (notCaptured) {
|
||||
notCaptured(detail);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
setDisabled(disabled: boolean) {
|
||||
pointerEvents.disabled = disabled;
|
||||
},
|
||||
destroy() {
|
||||
gesture.destroy();
|
||||
pointerEvents.destroy();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function updateDetail(ev: any, detail: GestureDetail) {
|
||||
|
Reference in New Issue
Block a user