mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-15 17:42:15 +08:00
175 lines
4.2 KiB
TypeScript
175 lines
4.2 KiB
TypeScript
|
|
import { now, pointerCoord } from './helpers';
|
|
|
|
export function startTapClick(doc: Document) {
|
|
let lastTouch = -MOUSE_WAIT * 10;
|
|
let lastActivated = 0;
|
|
let cancelled = false;
|
|
|
|
let activatableEle: HTMLElement | undefined;
|
|
let activeDefer: any;
|
|
|
|
const clearDefers = new WeakMap<HTMLElement, any>();
|
|
|
|
function onBodyClick(ev: Event) {
|
|
if (cancelled) {
|
|
ev.preventDefault();
|
|
ev.stopPropagation();
|
|
}
|
|
}
|
|
|
|
// Touch Events
|
|
function onTouchStart(ev: TouchEvent) {
|
|
lastTouch = now(ev);
|
|
pointerDown(ev);
|
|
}
|
|
|
|
function onTouchEnd(ev: TouchEvent) {
|
|
lastTouch = now(ev);
|
|
pointerUp(ev);
|
|
}
|
|
|
|
function onMouseDown(ev: MouseEvent) {
|
|
const t = now(ev) - MOUSE_WAIT;
|
|
if (lastTouch < t) {
|
|
pointerDown(ev);
|
|
}
|
|
}
|
|
|
|
function onMouseUp(ev: MouseEvent) {
|
|
const t = now(ev) - MOUSE_WAIT;
|
|
if (lastTouch < t) {
|
|
pointerUp(ev);
|
|
}
|
|
}
|
|
|
|
function cancelActive() {
|
|
clearTimeout(activeDefer);
|
|
if (activatableEle) {
|
|
removeActivated(false);
|
|
activatableEle = undefined;
|
|
}
|
|
cancelled = true;
|
|
}
|
|
|
|
function pointerDown(ev: any) {
|
|
if (activatableEle) {
|
|
return;
|
|
}
|
|
cancelled = false;
|
|
setActivatedElement(getActivatableTarget(ev), ev);
|
|
}
|
|
|
|
function pointerUp(ev: UIEvent) {
|
|
setActivatedElement(undefined, ev);
|
|
if (cancelled && ev.cancelable) {
|
|
ev.preventDefault();
|
|
}
|
|
}
|
|
|
|
function setActivatedElement(el: HTMLElement | undefined, ev: UIEvent) {
|
|
// do nothing
|
|
if (el && el === activatableEle) {
|
|
return;
|
|
}
|
|
clearTimeout(activeDefer);
|
|
activeDefer = undefined;
|
|
|
|
const { x, y } = pointerCoord(ev);
|
|
|
|
// unactivate selected
|
|
if (activatableEle) {
|
|
if (clearDefers.has(activatableEle)) {
|
|
throw new Error('internal error');
|
|
}
|
|
if (!activatableEle.classList.contains(ACTIVATED)) {
|
|
addActivated(activatableEle, x, y);
|
|
}
|
|
removeActivated(true);
|
|
}
|
|
|
|
// activate
|
|
if (el) {
|
|
const deferId = clearDefers.get(el);
|
|
if (deferId) {
|
|
clearTimeout(deferId);
|
|
clearDefers.delete(el);
|
|
}
|
|
|
|
el.classList.remove(ACTIVATED);
|
|
activeDefer = setTimeout(() => {
|
|
addActivated(el, x, y);
|
|
activeDefer = undefined;
|
|
}, ADD_ACTIVATED_DEFERS);
|
|
}
|
|
activatableEle = el;
|
|
}
|
|
|
|
function addActivated(el: HTMLElement, x: number, y: number) {
|
|
lastActivated = Date.now();
|
|
el.classList.add(ACTIVATED);
|
|
|
|
const rippleEffect = getRippleEffect(el);
|
|
if (rippleEffect && rippleEffect.addRipple) {
|
|
rippleEffect.addRipple(x, y);
|
|
}
|
|
}
|
|
|
|
function removeActivated(smooth: boolean) {
|
|
const active = activatableEle;
|
|
if (!active) {
|
|
return;
|
|
}
|
|
const time = CLEAR_STATE_DEFERS - Date.now() + lastActivated;
|
|
if (smooth && time > 0) {
|
|
const deferId = setTimeout(() => {
|
|
active.classList.remove(ACTIVATED);
|
|
clearDefers.delete(active);
|
|
}, CLEAR_STATE_DEFERS);
|
|
clearDefers.set(active, deferId);
|
|
} else {
|
|
active.classList.remove(ACTIVATED);
|
|
}
|
|
}
|
|
|
|
doc.body.addEventListener('click', onBodyClick, true);
|
|
doc.body.addEventListener('ionScrollStart', cancelActive);
|
|
doc.body.addEventListener('ionGestureCaptured', cancelActive);
|
|
|
|
doc.addEventListener('touchstart', onTouchStart, true);
|
|
doc.addEventListener('touchcancel', onTouchEnd, true);
|
|
doc.addEventListener('touchend', onTouchEnd, true);
|
|
|
|
doc.addEventListener('mousedown', onMouseDown, true);
|
|
doc.addEventListener('mouseup', onMouseUp, true);
|
|
}
|
|
|
|
function getActivatableTarget(ev: any): any {
|
|
if (ev.composedPath) {
|
|
const path = ev.composedPath() as HTMLElement[];
|
|
for (let i = 0; i < path.length - 2; i++) {
|
|
const el = path[i];
|
|
if (el.hasAttribute && el.hasAttribute('ion-activable')) {
|
|
return el;
|
|
}
|
|
}
|
|
} else {
|
|
return ev.target.closest('[ion-activable]');
|
|
}
|
|
}
|
|
|
|
function getRippleEffect(el: HTMLElement) {
|
|
if (el.shadowRoot) {
|
|
const ripple = el.shadowRoot.querySelector('ion-ripple-effect');
|
|
if (ripple) {
|
|
return ripple;
|
|
}
|
|
}
|
|
return el.querySelector('ion-ripple-effect');
|
|
}
|
|
|
|
const ACTIVATED = 'activated';
|
|
const ADD_ACTIVATED_DEFERS = 200;
|
|
const CLEAR_STATE_DEFERS = 200;
|
|
const MOUSE_WAIT = 2500;
|