mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-22 05:21:52 +08:00
fix(gesture/tapclick): ios support
This commit is contained in:
31
packages/core/src/components.d.ts
vendored
31
packages/core/src/components.d.ts
vendored
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -5,7 +5,20 @@
|
||||
<!-- Auto Generated Below -->
|
||||
|
||||
|
||||
## Events
|
||||
|
||||
#### ionGestureCaptured
|
||||
|
||||
|
||||
## Methods
|
||||
|
||||
#### create()
|
||||
|
||||
|
||||
#### createBlocker()
|
||||
|
||||
|
||||
|
||||
----------------------------------------------
|
||||
|
||||
*Built by [StencilJS](https://stenciljs.com/)*
|
||||
*Built with [StencilJS](https://stenciljs.com/)*
|
||||
|
@ -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;
|
||||
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -22,11 +22,6 @@
|
||||
|
||||
|
||||
|
||||
#### scrollEvents
|
||||
|
||||
boolean
|
||||
|
||||
|
||||
## Attributes
|
||||
|
||||
#### onion-scroll
|
||||
@ -44,11 +39,6 @@ boolean
|
||||
|
||||
|
||||
|
||||
#### scroll-events
|
||||
|
||||
boolean
|
||||
|
||||
|
||||
## Events
|
||||
|
||||
#### ionScroll
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
6
packages/core/src/index.d.ts
vendored
6
packages/core/src/index.d.ts
vendored
@ -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';
|
||||
|
@ -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'] },
|
||||
|
Reference in New Issue
Block a user