fix(gesture/tapclick): ios support

This commit is contained in:
Manu Mtz.-Almeida
2018-02-20 12:38:36 +01:00
parent 83062221a3
commit 9fded75502
12 changed files with 139 additions and 98 deletions

View File

@ -1000,6 +1000,36 @@ declare global {
}
import {
GestureController as IonGestureController
} from './components/gesture-controller/gesture-controller';
declare global {
interface HTMLIonGestureControllerElement extends IonGestureController, HTMLStencilElement {
}
var HTMLIonGestureControllerElement: {
prototype: HTMLIonGestureControllerElement;
new (): HTMLIonGestureControllerElement;
};
interface HTMLElementTagNameMap {
"ion-gesture-controller": HTMLIonGestureControllerElement;
}
interface ElementTagNameMap {
"ion-gesture-controller": HTMLIonGestureControllerElement;
}
namespace JSX {
interface IntrinsicElements {
"ion-gesture-controller": JSXElements.IonGestureControllerAttributes;
}
}
namespace JSXElements {
export interface IonGestureControllerAttributes extends HTMLAttributes {
}
}
}
import {
Gesture as IonGesture
} from './components/gesture/gesture';
@ -2660,7 +2690,6 @@ declare global {
onionScroll?: ScrollCallback;
onionScrollEnd?: ScrollCallback;
onionScrollStart?: ScrollCallback;
scrollEvents?: boolean;
}
}
}

View File

@ -1,3 +1,9 @@
import { Component, Method, EventEmitter, Event } from "@stencil/core";
@Component({
tag: 'ion-gesture-controller'
})
export class GestureController {
private gestureId = 0;
@ -6,21 +12,21 @@ export class GestureController {
private disabledScroll = new Set<number>();
private capturedId: number|null = null;
createGesture(gestureName: string, gesturePriority: number, disableScroll: boolean): GestureDelegate {
return new GestureDelegate(this, this.newID(), gestureName, gesturePriority, disableScroll);
@Event() ionGestureCaptured: EventEmitter<string>;
@Method()
create(config: GestureConfig): Promise<GestureDelegate> {
return Promise.resolve(new GestureDelegate(this, this.newID(), config.name, config.priority, config.disableScroll));
}
createBlocker(opts: BlockerOptions = {}): BlockerDelegate {
@Method()
createBlocker(opts: BlockerConfig = {}): BlockerDelegate {
return new BlockerDelegate(this.newID(), this,
opts.disable,
!!opts.disableScroll
);
}
newID(): number {
return this.gestureId++;
}
start(gestureName: string, id: number, priority: number): boolean {
if (!this.canStart(gestureName)) {
this.requestedStart.delete(id);
@ -43,6 +49,7 @@ export class GestureController {
if (maxPriority === priority) {
this.capturedId = id;
this.requestedStart.clear();
this.ionGestureCaptured.emit(gestureName);
return true;
}
requestedStart.delete(id);
@ -121,6 +128,9 @@ export class GestureController {
return false;
}
private newID(): number {
return this.gestureId++;
}
}
@ -180,14 +190,11 @@ export class GestureDelegate {
this.release();
this.ctrl = null;
}
}
export class BlockerDelegate {
blocked = false;
private ctrl: GestureController|null;
constructor(
@ -212,7 +219,6 @@ export class BlockerDelegate {
if (this.disableScroll) {
this.ctrl.disableScroll(this.blockerDelegateId);
}
this.blocked = true;
}
unblock() {
@ -227,7 +233,6 @@ export class BlockerDelegate {
if (this.disableScroll) {
this.ctrl.enableScroll(this.blockerDelegateId);
}
this.blocked = false;
}
destroy() {
@ -237,13 +242,20 @@ export class BlockerDelegate {
}
export interface BlockerOptions {
disableScroll?: boolean;
disable?: string[];
export interface GestureConfig {
name: string;
priority: number;
disableScroll: boolean;
}
export const BLOCK_ALL: BlockerOptions = {
export interface BlockerConfig {
disable?: string[];
disableScroll?: boolean;
}
export const BLOCK_ALL: BlockerConfig = {
disable: ['menu-swipe', 'goback-swipe'],
disableScroll: true
};

View File

@ -5,7 +5,20 @@
<!-- Auto Generated Below -->
## Events
#### ionGestureCaptured
## Methods
#### create()
#### createBlocker()
----------------------------------------------
*Built by [StencilJS](https://stenciljs.com/)*
*Built with [StencilJS](https://stenciljs.com/)*

View File

@ -1,11 +1,9 @@
import { Component, Element, Event, EventEmitter, EventListenerEnable, Listen, Prop, Watch } from '@stencil/core';
import { ElementRef, applyStyles, assert, getElementReference, now, updateDetail } from '../../utils/helpers';
import { BLOCK_ALL, BlockerDelegate, GestureController, GestureDelegate } from '../gesture-controller/gesture-controller';
import { Component, Event, EventEmitter, EventListenerEnable, Listen, Prop, Watch } from '@stencil/core';
import { ElementRef, assert, now, updateDetail } from '../../utils/helpers';
import { BlockerDelegate, GestureDelegate, BlockerConfig, BLOCK_ALL } from '../gesture-controller/gesture-controller';
import { DomController } from '../../index';
import { PanRecognizer } from './recognizers';
declare const Ionic: { gesture: GestureController };
@Component({
tag: 'ion-gesture'
@ -14,7 +12,6 @@ export class Gesture {
private detail: GestureDetail = {};
private positions: number[] = [];
private ctrl: GestureController;
private gesture: GestureDelegate;
private lastTouch = 0;
private pan: PanRecognizer;
@ -25,8 +22,7 @@ export class Gesture {
private isMoveQueued = false;
private blocker: BlockerDelegate;
@Element() private el: HTMLElement;
@Prop({ connect: 'ion-gesture-controller' }) gestureCtrl: HTMLIonGestureControllerElement;
@Prop({ context: 'dom' }) dom: DomController;
@Prop({ context: 'enableListener' }) enableListener: EventListenerEnable;
@ -76,13 +72,18 @@ export class Gesture {
*/
@Event() ionPress: EventEmitter;
componentWillLoad() {
return this.gestureCtrl.create({
name: this.gestureName,
priority: this.gesturePriority,
disableScroll: this.disableScroll
}).then((gesture) => this.gesture = gesture);
}
componentDidLoad() {
// in this case, we already know the GestureController and Gesture are already
// apart of the same bundle, so it's safe to load it this way
// only create one instance of GestureController, and reuse the same one later
this.ctrl = Ionic.gesture = Ionic.gesture || new GestureController();
this.gesture = this.ctrl.createGesture(this.gestureName, this.gesturePriority, this.disableScroll);
const types = this.type.replace(/\s/g, '').toLowerCase().split(',');
if (types.indexOf('pan') > -1) {
@ -91,15 +92,9 @@ export class Gesture {
this.hasPress = (types.indexOf('press') > -1);
this.disabledChanged(this.disabled);
if (this.pan || this.hasPress) {
this.dom.write(() => {
applyStyles(getElementReference(this.el, this.attachTo), GESTURE_INLINE_STYLES);
});
}
if (this.autoBlockAll) {
this.blocker = this.ctrl.createBlocker(BLOCK_ALL);
this.blocker.block();
this.setBlocker(BLOCK_ALL).then(b => b.block());
}
}
@ -116,12 +111,19 @@ export class Gesture {
@Watch('block')
protected blockChanged(block: string) {
this.setBlocker({ disable: block.split(',')});
}
private setBlocker(config: BlockerConfig) {
if (this.blocker) {
this.blocker.destroy();
}
if (block) {
this.blocker = this.ctrl.createBlocker({ disable: block.split(',')});
if (config) {
return this.gestureCtrl.componentOnReady()
.then(ctrl => ctrl.createBlocker(config))
.then(blocker => this.blocker = blocker);
}
return Promise.resolve(null);
}
// DOWN *************************
@ -459,19 +461,12 @@ export class Gesture {
this.blocker = null;
}
this.gesture && this.gesture.destroy();
this.ctrl = this.gesture = this.pan = this.detail = this.detail.event = null;
this.gesture = this.pan = this.detail = this.detail.event = null;
}
}
const GESTURE_INLINE_STYLES = {
'touch-action': 'none',
'user-select': 'none',
'-webkit-user-drag': 'none',
'-webkit-tap-highlight-color': 'rgba(0,0,0,0)'
};
const MOUSE_WAIT = 2500;

View File

@ -221,9 +221,6 @@ export class InfiniteScroll {
private enableScrollEvents(shouldListen: boolean) {
if (this.scrollEl) {
if (shouldListen) {
this.scrollEl.scrollEvents = true;
}
this.enableListener(this, 'ionScroll', shouldListen, this.scrollEl);
}
}

View File

@ -22,11 +22,6 @@
#### scrollEvents
boolean
## Attributes
#### onion-scroll
@ -44,11 +39,6 @@ boolean
#### scroll-events
boolean
## Events
#### ionScroll

View File

@ -1,8 +1,6 @@
import { Component, Element, Event, EventEmitter, EventListenerEnable, Listen, Method, Prop, Watch } from '@stencil/core';
import { Component, Element, Event, EventEmitter, EventListenerEnable, Listen, Method, Prop } from '@stencil/core';
import { Config, DomController, GestureDetail } from '../../index';
import { GestureController, GestureDelegate } from '../gesture-controller/gesture-controller';
declare const Ionic: { gesture: GestureController };
import { GestureDelegate } from '../gesture-controller/gesture-controller';
@Component({
@ -20,6 +18,7 @@ export class Scroll {
@Element() private el: HTMLElement;
@Prop({ connect: 'ion-gesture-controller'}) gestureCtrl: HTMLIonGestureControllerElement;
@Prop({ context: 'config'}) config: Config;
@Prop({ context: 'enableListener'}) enableListener: EventListenerEnable;
@Prop({ context: 'dom' }) dom: DomController;
@ -44,22 +43,19 @@ export class Scroll {
*/
@Event() ionScrollEnd: EventEmitter;
@Prop() scrollEvents = false;
@Watch('scrollEvents')
scrollChanged(enabled: boolean) {
this.enableListener(this, 'scroll', enabled);
componentWillLoad() {
return this.gestureCtrl.create({
name: 'scroll',
priority: 100,
disableScroll: false,
}).then(gesture => this.gesture = gesture);
}
componentDidLoad() {
if (this.isServer) {
return;
}
const gestureCtrl = Ionic.gesture = Ionic.gesture || new GestureController();
this.gesture = gestureCtrl.createGesture('scroll', 100, false);
this.app = this.el.closest('ion-app') as HTMLIonAppElement;
this.scrollChanged(this.scrollEvents);
}
componentDidUnload() {
@ -69,7 +65,7 @@ export class Scroll {
// Native Scroll *************************
@Listen('scroll', { passive: true, enabled: false })
@Listen('scroll', { passive: true })
onNativeScroll() {
if (!this.queued) {
this.queued = true;
@ -219,6 +215,7 @@ export class Scroll {
if (this.onionScrollStart) {
this.onionScrollStart(detail);
}
this.gesture.capture();
this.ionScrollStart.emit(detail);
}
detail.deltaY = (detail.scrollTop - detail.startY);
@ -277,6 +274,8 @@ export class Scroll {
detail.timeStamp = timeStamp;
// emit that the scroll has ended
this.gesture.release();
if (this.onionScrollEnd) {
this.onionScrollEnd(detail);
}

View File

@ -1,8 +1,5 @@
import { Component, Element, EventListenerEnable, Listen, Prop } from '@stencil/core';
import { now, pointerCoordX, pointerCoordY } from '../../utils/helpers';
import { GestureController } from '../gesture-controller/gesture-controller';
declare const Ionic: { gesture: GestureController };
@Component({
@ -13,8 +10,7 @@ export class TapClick {
private app: HTMLIonAppElement;
private lastTouch = -MOUSE_WAIT*10;
private lastActivated = 0;
private gestureCtrl: GestureController;
private cancelled = false;
private activatableEle: HTMLElement | null;
private activeDefer: any;
@ -30,40 +26,37 @@ export class TapClick {
if (this.isServer) {
return;
}
this.gestureCtrl = Ionic.gesture = Ionic.gesture || new GestureController();
this.app = this.el.closest('ion-app') as HTMLIonAppElement;
}
@Listen('document:click', {passive: false, capture: true})
@Listen('body:click', {passive: false, capture: true})
onBodyClick(ev: Event) {
if (this.shouldCancel()) {
debugger;
if (this.cancelled || this.shouldCancel()) {
ev.preventDefault();
ev.stopPropagation();
}
}
// Touch Events
@Listen('document:touchstart', { passive: true })
@Listen('document:touchstart', { passive: true, capture: true })
onTouchStart(ev: TouchEvent) {
this.lastTouch = now(ev);
this.pointerDown(ev);
}
@Listen('document:touchcancel', { passive: true })
@Listen('document:touchcancel', { passive: true, capture: true })
onTouchCancel(ev: TouchEvent) {
this.lastTouch = now(ev);
this.pointerUp(ev);
}
@Listen('document:touchend', { passive: true })
@Listen('document:touchend', { passive: false, capture: true })
onTouchEnd(ev: TouchEvent) {
this.lastTouch = now(ev);
this.pointerUp(ev);
}
@Listen('document:mousedown', { passive: true })
@Listen('document:mousedown', { passive: true, capture: true })
onMouseDown(ev: MouseEvent) {
const t = now(ev) - MOUSE_WAIT;
if (this.lastTouch < t) {
@ -71,7 +64,7 @@ export class TapClick {
}
}
@Listen('document:mouseup', { passive: true })
@Listen('document:mouseup', { passive: false, capture: true })
onMouseUp(ev: TouchEvent) {
const t = now(ev) - MOUSE_WAIT;
if (this.lastTouch < t) {
@ -80,25 +73,32 @@ export class TapClick {
}
@Listen('body:ionScrollStart')
scrollStarted() {
@Listen('body:ionGestureCaptured')
cancelActive() {
clearTimeout(this.activeDefer);
if (this.activatableEle) {
this.removeActivated(false);
this.activatableEle = null;
}
this.cancelled = true;
}
private pointerDown(ev: any) {
if (this.activatableEle) {
return;
}
if (!this.shouldCancel()) {
this.cancelled = this.shouldCancel();
if (!this.cancelled) {
this.setActivatedElement(getActivatableTarget(ev.target), ev);
}
}
private pointerUp(ev: UIEvent) {
this.setActivatedElement(null, ev);
if (this.cancelled) {
ev.preventDefault();
}
}
private setActivatedElement(el: HTMLElement | null, ev: UIEvent) {
@ -175,10 +175,6 @@ export class TapClick {
console.debug('click prevent: appDisabled');
return true;
}
if (this.gestureCtrl.isCaptured()) {
console.debug('click prevent: tap-click (gesture is captured)');
return true;
}
return false;
}
}

View File

@ -7,6 +7,9 @@ ion-toggle {
display: inline-block;
contain: content;
touch-action: none;
user-select: none;
}
ion-toggle ion-gesture {
@ -52,8 +55,10 @@ ion-toggle input {
background: transparent;
cursor: pointer;
border: 0;
pointer-events: none;
// touch-action: pan-x;
-webkit-appearance: none;
-moz-appearance: none;

View File

@ -16,13 +16,13 @@ import { debounce } from '../../utils/helpers';
}
})
export class Toggle implements CheckboxInput {
private didLoad: boolean;
private gestureConfig: any;
private inputId: string;
private nativeInput: HTMLInputElement;
private pivotX: number;
@State() activated = false;
@State() keyFocus: boolean;
@ -88,6 +88,7 @@ export class Toggle implements CheckboxInput {
'onMove': this.onDragMove.bind(this),
'onEnd': this.onDragEnd.bind(this),
'gestureName': 'toggle',
'passive': false,
'gesturePriority': 30,
'type': 'pan',
'direction': 'x',
@ -141,6 +142,10 @@ export class Toggle implements CheckboxInput {
private onDragStart(detail: GestureDetail) {
this.pivotX = detail.currentX;
this.activated = true;
// touch-action does not work in iOS
detail.event.preventDefault();
return true;
}
private onDragMove(detail: GestureDetail) {

View File

@ -36,9 +36,9 @@ export { PanRecognizer } from './components/gesture/recognizers';
export {
BLOCK_ALL,
BlockerDelegate,
BlockerOptions,
GestureController,
GestureDelegate
GestureDelegate,
BlockerConfig,
GestureConfig,
} from './components/gesture-controller/gesture-controller';
export { Grid } from './components/grid/grid';
export { Header } from './components/header/header';

View File

@ -20,7 +20,7 @@ exports.config = {
{ components: ['ion-datetime', 'ion-picker', 'ion-picker-column', 'ion-picker-controller'] },
{ components: ['ion-events'] },
{ components: ['ion-fab', 'ion-fab-button', 'ion-fab-list'] },
{ components: ['ion-gesture'] },
{ components: ['ion-gesture', 'ion-gesture-controller'] },
{ components: ['ion-grid', 'ion-row', 'ion-col'] },
{ components: ['ion-item', 'ion-item-divider', 'ion-item-group', 'ion-label', 'ion-list', 'ion-list-header', 'ion-skeleton-text'] },
{ components: ['ion-item-sliding', 'ion-item-options', 'ion-item-option'] },