122 lines
5.4 KiB
JavaScript
122 lines
5.4 KiB
JavaScript
import { extractEventInfo } from '../events/event-info.mjs';
|
|
import { addDomEvent } from '../events/add-dom-event.mjs';
|
|
import { addPointerEvent } from '../events/add-pointer-event.mjs';
|
|
import { Feature } from '../motion/features/Feature.mjs';
|
|
import { pipe } from '../utils/pipe.mjs';
|
|
import { isDragActive } from './drag/utils/lock.mjs';
|
|
import { isNodeOrChild } from './utils/is-node-or-child.mjs';
|
|
import { noop } from '../utils/noop.mjs';
|
|
import { frame } from '../frameloop/frame.mjs';
|
|
|
|
function fireSyntheticPointerEvent(name, handler) {
|
|
if (!handler)
|
|
return;
|
|
const syntheticPointerEvent = new PointerEvent("pointer" + name);
|
|
handler(syntheticPointerEvent, extractEventInfo(syntheticPointerEvent));
|
|
}
|
|
class PressGesture extends Feature {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.removeStartListeners = noop;
|
|
this.removeEndListeners = noop;
|
|
this.removeAccessibleListeners = noop;
|
|
this.startPointerPress = (startEvent, startInfo) => {
|
|
if (this.isPressing)
|
|
return;
|
|
this.removeEndListeners();
|
|
const props = this.node.getProps();
|
|
const endPointerPress = (endEvent, endInfo) => {
|
|
if (!this.checkPressEnd())
|
|
return;
|
|
const { onTap, onTapCancel, globalTapTarget } = this.node.getProps();
|
|
frame.update(() => {
|
|
/**
|
|
* We only count this as a tap gesture if the event.target is the same
|
|
* as, or a child of, this component's element
|
|
*/
|
|
!globalTapTarget &&
|
|
!isNodeOrChild(this.node.current, endEvent.target)
|
|
? onTapCancel && onTapCancel(endEvent, endInfo)
|
|
: onTap && onTap(endEvent, endInfo);
|
|
});
|
|
};
|
|
const removePointerUpListener = addPointerEvent(window, "pointerup", endPointerPress, { passive: !(props.onTap || props["onPointerUp"]) });
|
|
const removePointerCancelListener = addPointerEvent(window, "pointercancel", (cancelEvent, cancelInfo) => this.cancelPress(cancelEvent, cancelInfo), { passive: !(props.onTapCancel || props["onPointerCancel"]) });
|
|
this.removeEndListeners = pipe(removePointerUpListener, removePointerCancelListener);
|
|
this.startPress(startEvent, startInfo);
|
|
};
|
|
this.startAccessiblePress = () => {
|
|
const handleKeydown = (keydownEvent) => {
|
|
if (keydownEvent.key !== "Enter" || this.isPressing)
|
|
return;
|
|
const handleKeyup = (keyupEvent) => {
|
|
if (keyupEvent.key !== "Enter" || !this.checkPressEnd())
|
|
return;
|
|
fireSyntheticPointerEvent("up", (event, info) => {
|
|
const { onTap } = this.node.getProps();
|
|
if (onTap) {
|
|
frame.update(() => onTap(event, info));
|
|
}
|
|
});
|
|
};
|
|
this.removeEndListeners();
|
|
this.removeEndListeners = addDomEvent(this.node.current, "keyup", handleKeyup);
|
|
fireSyntheticPointerEvent("down", (event, info) => {
|
|
this.startPress(event, info);
|
|
});
|
|
};
|
|
const removeKeydownListener = addDomEvent(this.node.current, "keydown", handleKeydown);
|
|
const handleBlur = () => {
|
|
if (!this.isPressing)
|
|
return;
|
|
fireSyntheticPointerEvent("cancel", (cancelEvent, cancelInfo) => this.cancelPress(cancelEvent, cancelInfo));
|
|
};
|
|
const removeBlurListener = addDomEvent(this.node.current, "blur", handleBlur);
|
|
this.removeAccessibleListeners = pipe(removeKeydownListener, removeBlurListener);
|
|
};
|
|
}
|
|
startPress(event, info) {
|
|
this.isPressing = true;
|
|
const { onTapStart, whileTap } = this.node.getProps();
|
|
/**
|
|
* Ensure we trigger animations before firing event callback
|
|
*/
|
|
if (whileTap && this.node.animationState) {
|
|
this.node.animationState.setActive("whileTap", true);
|
|
}
|
|
if (onTapStart) {
|
|
frame.update(() => onTapStart(event, info));
|
|
}
|
|
}
|
|
checkPressEnd() {
|
|
this.removeEndListeners();
|
|
this.isPressing = false;
|
|
const props = this.node.getProps();
|
|
if (props.whileTap && this.node.animationState) {
|
|
this.node.animationState.setActive("whileTap", false);
|
|
}
|
|
return !isDragActive();
|
|
}
|
|
cancelPress(event, info) {
|
|
if (!this.checkPressEnd())
|
|
return;
|
|
const { onTapCancel } = this.node.getProps();
|
|
if (onTapCancel) {
|
|
frame.update(() => onTapCancel(event, info));
|
|
}
|
|
}
|
|
mount() {
|
|
const props = this.node.getProps();
|
|
const removePointerListener = addPointerEvent(props.globalTapTarget ? window : this.node.current, "pointerdown", this.startPointerPress, { passive: !(props.onTapStart || props["onPointerStart"]) });
|
|
const removeFocusListener = addDomEvent(this.node.current, "focus", this.startAccessiblePress);
|
|
this.removeStartListeners = pipe(removePointerListener, removeFocusListener);
|
|
}
|
|
unmount() {
|
|
this.removeStartListeners();
|
|
this.removeEndListeners();
|
|
this.removeAccessibleListeners();
|
|
}
|
|
}
|
|
|
|
export { PressGesture };
|