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 };