mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-11-08 23:58:13 +08:00
perf(app): tap-click is a ES
we can finally remove the slot!
This commit is contained in:
@ -44,7 +44,6 @@ ion-app.is-device {
|
||||
// Misc
|
||||
// --------------------------------------------------
|
||||
|
||||
ion-tap-click,
|
||||
ion-route,
|
||||
ion-route-redirect,
|
||||
ion-router,
|
||||
|
||||
@ -13,6 +13,8 @@ import { createThemedClasses } from '../../utils/theme';
|
||||
})
|
||||
export class App {
|
||||
|
||||
private isDevice = false;
|
||||
|
||||
mode!: Mode;
|
||||
|
||||
@Element() el!: HTMLElement;
|
||||
@ -21,13 +23,17 @@ export class App {
|
||||
@Prop({ context: 'config' }) config!: Config;
|
||||
@Prop({ context: 'queue' }) queue!: QueueApi;
|
||||
|
||||
componentWillLoad() {
|
||||
this.isDevice = this.config.getBoolean('isDevice', isDevice(this.win));
|
||||
}
|
||||
|
||||
componentDidLoad() {
|
||||
importTapClick(this.win, this.isDevice);
|
||||
importInputShims(this.win, this.config);
|
||||
importStatusTap(this.win, this.config, this.queue);
|
||||
importStatusTap(this.win, this.isDevice, this.queue);
|
||||
}
|
||||
|
||||
hostData() {
|
||||
const device = this.config.getBoolean('isDevice', isDevice(this.win));
|
||||
const hybrid = isHybrid(this.win);
|
||||
const statusbarPadding = this.config.get('statusbarPadding', hybrid);
|
||||
|
||||
@ -35,25 +41,23 @@ export class App {
|
||||
class: {
|
||||
...createThemedClasses(this.mode, 'app'),
|
||||
|
||||
'is-device': device,
|
||||
'is-device': this.isDevice,
|
||||
'is-hydrid': hybrid,
|
||||
'statusbar-padding': statusbarPadding
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return [
|
||||
<ion-tap-click></ion-tap-click>,
|
||||
<slot></slot>
|
||||
];
|
||||
async function importStatusTap(win: Window, device: boolean, queue: QueueApi) {
|
||||
if (device) {
|
||||
(await import('../../utils/status-tap')).startStatusTap(win, queue);
|
||||
}
|
||||
}
|
||||
|
||||
async function importStatusTap(win: Window, config: Config, queue: QueueApi) {
|
||||
const device = config.getBoolean('isDevice', isDevice(win));
|
||||
async function importTapClick(win: Window, device: boolean) {
|
||||
if (device) {
|
||||
(await import('../../utils/status-tap')).startStatusTap(win, queue);
|
||||
(await import('../../utils/tap-click')).startTapClick(win.document);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
# ion-tap-click
|
||||
|
||||
TabClick is an internal component with no public API.
|
||||
|
||||
|
||||
<!-- Auto Generated Below -->
|
||||
|
||||
|
||||
|
||||
----------------------------------------------
|
||||
|
||||
*Built with [StencilJS](https://stenciljs.com/)*
|
||||
@ -1,164 +0,0 @@
|
||||
import { Component, Element, EventListenerEnable, Listen, Prop } from '@stencil/core';
|
||||
|
||||
import { now, pointerCoord } from '../../utils/helpers';
|
||||
|
||||
/** @hidden */
|
||||
@Component({
|
||||
tag: 'ion-tap-click',
|
||||
})
|
||||
export class TapClick {
|
||||
|
||||
private lastTouch = -MOUSE_WAIT * 10;
|
||||
private lastActivated = 0;
|
||||
private cancelled = false;
|
||||
|
||||
private activatableEle?: HTMLElement;
|
||||
private activeDefer: any;
|
||||
|
||||
private clearDefers = new WeakMap<HTMLElement, any>();
|
||||
|
||||
@Prop({ context: 'isServer' }) isServer!: boolean;
|
||||
@Prop({ context: 'enableListener' }) enableListener!: EventListenerEnable;
|
||||
|
||||
@Element() el!: HTMLElement;
|
||||
|
||||
@Listen('body:click', { passive: false, capture: true })
|
||||
onBodyClick(ev: Event) {
|
||||
if (this.cancelled) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
// Touch Events
|
||||
@Listen('document:touchstart', { passive: true, capture: true })
|
||||
onTouchStart(ev: TouchEvent) {
|
||||
this.lastTouch = now(ev);
|
||||
this.pointerDown(ev);
|
||||
}
|
||||
|
||||
@Listen('document:touchcancel', { passive: false, capture: true })
|
||||
@Listen('document:touchend', { passive: false, capture: true })
|
||||
onTouchEnd(ev: TouchEvent) {
|
||||
this.lastTouch = now(ev);
|
||||
this.pointerUp(ev);
|
||||
}
|
||||
|
||||
@Listen('document:mousedown', { passive: true, capture: true })
|
||||
onMouseDown(ev: MouseEvent) {
|
||||
const t = now(ev) - MOUSE_WAIT;
|
||||
if (this.lastTouch < t) {
|
||||
this.pointerDown(ev);
|
||||
}
|
||||
}
|
||||
|
||||
@Listen('document:mouseup', { passive: false, capture: true })
|
||||
onMouseUp(ev: TouchEvent) {
|
||||
const t = now(ev) - MOUSE_WAIT;
|
||||
if (this.lastTouch < t) {
|
||||
this.pointerUp(ev);
|
||||
}
|
||||
}
|
||||
|
||||
@Listen('body:ionScrollStart')
|
||||
@Listen('body:ionGestureCaptured')
|
||||
cancelActive() {
|
||||
clearTimeout(this.activeDefer);
|
||||
if (this.activatableEle) {
|
||||
this.removeActivated(false);
|
||||
this.activatableEle = undefined;
|
||||
}
|
||||
this.cancelled = true;
|
||||
}
|
||||
|
||||
private pointerDown(ev: any) {
|
||||
if (this.activatableEle) {
|
||||
return;
|
||||
}
|
||||
this.cancelled = false;
|
||||
this.setActivatedElement(getActivatableTarget(ev.target), ev);
|
||||
}
|
||||
|
||||
private pointerUp(ev: UIEvent) {
|
||||
this.setActivatedElement(undefined, ev);
|
||||
if (this.cancelled && ev.cancelable) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
private setActivatedElement(el: HTMLElement | undefined, ev: UIEvent) {
|
||||
// do nothing
|
||||
const activatableEle = this.activatableEle;
|
||||
if (el && el === activatableEle) {
|
||||
return;
|
||||
}
|
||||
clearTimeout(this.activeDefer);
|
||||
this.activeDefer = undefined;
|
||||
|
||||
const { x, y } = pointerCoord(ev);
|
||||
|
||||
// unactivate selected
|
||||
if (activatableEle) {
|
||||
if (this.clearDefers.has(activatableEle)) {
|
||||
throw new Error('internal error');
|
||||
}
|
||||
if (!activatableEle.classList.contains(ACTIVATED)) {
|
||||
this.addActivated(activatableEle, x, y);
|
||||
}
|
||||
this.removeActivated(true);
|
||||
}
|
||||
|
||||
// activate
|
||||
if (el) {
|
||||
const deferId = this.clearDefers.get(el);
|
||||
if (deferId) {
|
||||
clearTimeout(deferId);
|
||||
this.clearDefers.delete(el);
|
||||
}
|
||||
|
||||
el.classList.remove(ACTIVATED);
|
||||
this.activeDefer = setTimeout(() => {
|
||||
this.addActivated(el, x, y);
|
||||
this.activeDefer = undefined;
|
||||
}, ADD_ACTIVATED_DEFERS);
|
||||
}
|
||||
this.activatableEle = el;
|
||||
}
|
||||
|
||||
private addActivated(el: HTMLElement, x: number, y: number) {
|
||||
this.lastActivated = Date.now();
|
||||
el.classList.add(ACTIVATED);
|
||||
|
||||
const event = new CustomEvent('ionActivated', {
|
||||
bubbles: false,
|
||||
detail: { x, y }
|
||||
});
|
||||
el.dispatchEvent(event);
|
||||
}
|
||||
|
||||
private removeActivated(smooth: boolean) {
|
||||
const activatableEle = this.activatableEle;
|
||||
if (!activatableEle) {
|
||||
return;
|
||||
}
|
||||
const time = CLEAR_STATE_DEFERS - Date.now() + this.lastActivated;
|
||||
if (smooth && time > 0) {
|
||||
const deferId = setTimeout(() => {
|
||||
activatableEle.classList.remove(ACTIVATED);
|
||||
this.clearDefers.delete(activatableEle);
|
||||
}, CLEAR_STATE_DEFERS);
|
||||
this.clearDefers.set(activatableEle, deferId);
|
||||
} else {
|
||||
activatableEle.classList.remove(ACTIVATED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getActivatableTarget(el: HTMLElement): any {
|
||||
return el.closest(':not([tappable]) > a, :not([tappable]) > button, [tappable]');
|
||||
}
|
||||
|
||||
const ACTIVATED = 'activated';
|
||||
const ADD_ACTIVATED_DEFERS = 200;
|
||||
const CLEAR_STATE_DEFERS = 200;
|
||||
const MOUSE_WAIT = 2500;
|
||||
@ -1,75 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html dir="ltr">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Button Effect - Basic</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<script src="/dist/ionic.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/css/ionic.min.css">
|
||||
|
||||
<style>
|
||||
.my-block {
|
||||
position: relative;
|
||||
background: blue;
|
||||
color: white;
|
||||
width: 300px;
|
||||
height: 100px;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<ion-app>
|
||||
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Button Effect - Basic</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content padding no-bounce>
|
||||
<p>
|
||||
<ion-button size="small">Small</ion-button>
|
||||
</p>
|
||||
<p>
|
||||
<ion-button size="large">Large</ion-button>
|
||||
</p>
|
||||
<p>
|
||||
<ion-button size="large" fill="outline">Large</ion-button>
|
||||
</p>
|
||||
<p>
|
||||
<ion-button size="large" fill="clear">Large</ion-button>
|
||||
</p>
|
||||
<div class="my-block">
|
||||
<ion-ripple-effect></ion-ripple-effect>
|
||||
This is just a div + effect behind
|
||||
<ion-button onclick="buttonClicked()">Nested button</ion-button>
|
||||
</div>
|
||||
<div class="my-block">
|
||||
This is just a div + effect on top
|
||||
<ion-button onclick="buttonClicked()">Nested button</ion-button>
|
||||
<ion-ripple-effect></ion-ripple-effect>
|
||||
</div>
|
||||
|
||||
<div class="my-block">
|
||||
This is just a div + effect
|
||||
<ion-ripple-effect></ion-ripple-effect>
|
||||
</div>
|
||||
</ion-content>
|
||||
|
||||
</ion-app>
|
||||
<script>
|
||||
function blockClicked() {
|
||||
console.log('block clicked');
|
||||
return true;
|
||||
}
|
||||
function buttonClicked() {
|
||||
console.log('button clicked');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@ -1,82 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html dir="ltr">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Button Effect</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<script src="/dist/ionic.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/css/ionic.min.css">
|
||||
|
||||
<style>
|
||||
.my-block {
|
||||
position: relative;
|
||||
background: blue;
|
||||
color: white;
|
||||
width: 300px;
|
||||
height: 100px;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<ion-app>
|
||||
|
||||
<ion-header translucent>
|
||||
<ion-toolbar>
|
||||
<ion-title>Button Effect</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content padding no-bounce fullscreen>
|
||||
<p>
|
||||
<ion-button size="small">Small</ion-button>
|
||||
</p>
|
||||
<p>
|
||||
<ion-button size="large">Large</ion-button>
|
||||
</p>
|
||||
<p>
|
||||
<ion-button size="large" fill="outline">Large</ion-button>
|
||||
</p>
|
||||
<p>
|
||||
<ion-button size="large" fill="clear">Large</ion-button>
|
||||
</p>
|
||||
<div class="my-block">
|
||||
<ion-ripple-effect></ion-ripple-effect>
|
||||
This is just a div + effect behind
|
||||
<ion-button onclick="buttonClicked()">Nested button</ion-button>
|
||||
</div>
|
||||
<div class="my-block">
|
||||
This is just a div + effect on top
|
||||
<ion-button onclick="buttonClicked()">Nested button</ion-button>
|
||||
<ion-ripple-effect></ion-ripple-effect>
|
||||
</div>
|
||||
|
||||
<div class="my-block">
|
||||
This is just a div + effect
|
||||
<ion-ripple-effect></ion-ripple-effect>
|
||||
</div>
|
||||
</ion-content>
|
||||
|
||||
</ion-app>
|
||||
|
||||
<style>
|
||||
ion-toolbar {
|
||||
--background: white;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
function blockClicked() {
|
||||
console.log('block clicked');
|
||||
return true;
|
||||
}
|
||||
function buttonClicked() {
|
||||
console.log('button clicked');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user