motion-dom
Version:
1 lines • 18.5 kB
Source Map (JSON)
{"version":3,"file":"index.mjs","sources":["../../../../src/gestures/press/index.ts"],"sourcesContent":["import { secondsToMilliseconds } from \"motion-utils\"\nimport { cancelFrame, frame } from \"../../frameloop/frame\"\nimport { time } from \"../../frameloop/sync-time\"\nimport { bindToScope } from \"../../scope/active\"\nimport { isHTMLElement } from \"../../utils/is-html-element\"\nimport { ElementOrSelector } from \"../../utils/resolve-elements\"\nimport { isDragActive } from \"../drag/state/is-active\"\nimport { EventOptions } from \"../types\"\nimport { isNodeOrChild } from \"../utils/is-node-or-child\"\nimport { isPrimaryPointer } from \"../utils/is-primary-pointer\"\nimport { setupGesture } from \"../utils/setup\"\nimport { OnPressStartEvent, PressHandlers, PressOptions } from \"./types\"\nimport { isElementKeyboardAccessible } from \"./utils/is-keyboard-accessible\"\nimport { enableKeyboardPress } from \"./utils/keyboard\"\nimport { isPressing } from \"./utils/state\"\n\n/**\n * Filter out events that are not primary pointer events, or are triggering\n * while a Motion gesture is active.\n */\nfunction isValidPressEvent(event: PointerEvent) {\n return isPrimaryPointer(event) && !isDragActive()\n}\n\nconst claimedPointerDownEvents = new WeakSet<Event>()\n\n/**\n * Max distance in px between consecutive presses of a `count` sequence.\n * Android's `ViewConfiguration` double tap slop is 100dp.\n */\nconst sequenceSlop = 100\n\n/**\n * Max distance in px a pointer can move during a held press (`hold`\n * pending, or `repeat`). Android's `ViewConfiguration` touch slop is 8dp.\n */\nconst holdSlop = 8\n\nconst exceeds = (a: PointerEvent, b: PointerEvent, max: number) =>\n Math.hypot(a.clientX - b.clientX, a.clientY - b.clientY) > max\n\nconst preventDefault = (event: Event) => event.preventDefault()\n\n/**\n * A `count` sequence waiting for its next press.\n */\ninterface PressSequence {\n count: number\n event: PointerEvent\n handlers: PressHandlers\n expire: (force?: unknown) => true | void\n}\n\nexport interface PointerEventOptions extends EventOptions, PressOptions {\n useGlobalTarget?: boolean\n stopPropagation?: boolean\n}\n\n/**\n * Create a press gesture.\n *\n * Press is different to `\"pointerdown\"`, `\"pointerup\"` in that it\n * automatically filters out secondary pointer events like right\n * click and multitouch.\n *\n * It also adds accessibility support for keyboards, where\n * an element with a press gesture will receive focus and\n * trigger on Enter `\"keydown\"` and `\"keyup\"` events.\n *\n * This is different to a browser's `\"click\"` event, which does\n * respond to keyboards but only for the `\"click\"` itself, rather\n * than the press start and end/cancel. The element also needs\n * to be focusable for this to work, whereas a press gesture will\n * make an element focusable by default.\n *\n * `onPressStart` fires on the first pointer or key down, and can return\n * a function to call when the press ends, or an object of handlers for\n * this press:\n *\n * ```js\n * press(button, () => ({\n * onHold: (progress) => {},\n * onComplete: (event, { success }) => {},\n * }), { hold: 0.5 })\n * ```\n *\n * @public\n */\nexport function press(\n targetOrSelector: ElementOrSelector,\n onPressStart: OnPressStartEvent,\n options: PointerEventOptions = {}\n): VoidFunction {\n onPressStart = bindToScope(onPressStart)\n\n const [targets, eventOptions, cancelEvents] = setupGesture(\n targetOrSelector,\n options\n )\n\n const sequences = new WeakMap<Element, PressSequence>()\n\n const startPress = (startEvent: PointerEvent) => {\n const target = startEvent.currentTarget as Element\n\n if (!isValidPressEvent(startEvent)) return\n if (claimedPointerDownEvents.has(startEvent)) return\n\n if (options.stopPropagation) {\n claimedPointerDownEvents.add(startEvent)\n }\n\n /**\n * Options are read per press so they can be updated between presses.\n */\n const {\n count: presses = 1,\n countTimeout = 0.3,\n hold = 0,\n repeat,\n } = options\n const downTime = time.now()\n const holdTime = secondsToMilliseconds(hold)\n const timeout = secondsToMilliseconds(countTimeout)\n let previous = sequences.get(target)\n\n /**\n * A press during a `count` sequence continues it. If it's too late\n * or too far away, the sequence is cancelled and this press starts\n * a new one.\n */\n if (\n previous?.expire(exceeds(previous.event, startEvent, sequenceSlop))\n ) {\n previous = undefined\n } else if (previous) {\n cancelFrame(previous.expire)\n sequences.delete(target)\n }\n\n const count = previous ? previous.count + 1 : 1\n const isFinal = count >= presses\n const isHeld = isFinal && (hold || repeat)\n let isActive = !hold\n let repeats = 0\n let handlers: PressHandlers\n\n isPressing.add(target)\n\n if (previous) {\n handlers = previous.handlers\n } else {\n const result = onPressStart(target, startEvent)\n handlers =\n typeof result === \"function\"\n ? { onComplete: result }\n : result || {}\n }\n\n isFinal && isActive && handlers.onActive?.()\n\n /**\n * While held, `onHold` reports progress from 0 to 1 over `hold`,\n * then over each `repeat`. Each cycle reports exactly 1 at its end,\n * then 0 on the next frame.\n */\n const repeatTime = secondsToMilliseconds(repeat!)\n let length = holdTime || repeatTime\n let cycleEnd = downTime + length\n let isCycleStart = true\n\n const tick = () => {\n if (eventOptions.signal!.aborted) return cancelFrame(tick)\n\n let progress = Math.min(1 - (cycleEnd - time.now()) / length, 1)\n\n /**\n * The frame after a boundary reports 0, so each cycle visibly\n * restarts. Boundaries are timed from `downTime`, so this\n * doesn't delay the next one. If a long frame has already\n * passed the next boundary, it reports 1 instead.\n */\n if (isCycleStart && progress < 1) progress = 0\n isCycleStart = false\n handlers.onHold?.(progress)\n\n if (progress < 1) return\n\n if (isActive) {\n handlers.onRepeat?.(++repeats)\n } else {\n isActive = true\n handlers.onActive?.()\n }\n\n if (repeat) {\n isCycleStart = true\n cycleEnd += length = repeatTime\n } else {\n cancelFrame(tick)\n }\n }\n\n /**\n * End listeners run in the capture phase so a descendant calling\n * stopPropagation() in its own pointerup handler can't prevent the\n * press gesture from ending. This also keeps the gesture-end\n * ordering consistent with the drag gesture. See #2794.\n */\n const endEventOptions = { ...eventOptions, capture: true }\n\n const onPointerEnd = (endEvent: PointerEvent, success: boolean) => {\n /**\n * Another touch lifting (for instance after a pinch) doesn't\n * end the press.\n */\n if (!isPrimaryPointer(endEvent)) return\n\n window.removeEventListener(\n \"pointerup\",\n onPointerUp,\n endEventOptions\n )\n window.removeEventListener(\n \"pointercancel\",\n onPointerCancel,\n endEventOptions\n )\n window.removeEventListener(\n \"pointermove\",\n onPointerMove,\n endEventOptions\n )\n target.removeEventListener(\"contextmenu\", preventDefault)\n cancelFrame(tick)\n isPressing.delete(target)\n\n /**\n * A press that ends while a drag is active is a cancel.\n */\n if (isDragActive()) success = false\n\n if (success && !isFinal) {\n /**\n * Wait for the next press of the sequence, and cancel the\n * gesture if it doesn't arrive in time.\n */\n const end = time.now()\n\n /**\n * Cancels the sequence if it's `force`d, or the interval\n * has passed. Runs every frame, where `force` is frame\n * data, so it's checked with `=== true`.\n */\n const expire = (force?: unknown) => {\n const isAborted = eventOptions.signal!.aborted\n if (\n isAborted ||\n force === true ||\n time.now() - end > timeout\n ) {\n cancelFrame(expire)\n sequences.delete(target)\n isAborted ||\n handlers.onComplete?.(endEvent, { success: false })\n return true\n }\n }\n sequences.set(target, {\n count,\n event: startEvent,\n handlers,\n expire,\n })\n frame.update(expire, true)\n } else {\n /**\n * Releasing before `hold` is reached is a cancel.\n */\n handlers.onComplete?.(endEvent, {\n success: success && isActive,\n })\n }\n }\n\n const onPointerUp = (upEvent: PointerEvent) => {\n onPointerEnd(\n upEvent,\n (target as any) === window ||\n (target as any) === document ||\n options.useGlobalTarget ||\n isNodeOrChild(target, upEvent.target as Element)\n )\n }\n\n const onPointerCancel = (cancelEvent: PointerEvent) => {\n onPointerEnd(cancelEvent, false)\n }\n\n /**\n * Moving during a pending long press, or a repeating press, cancels\n * it, so scrolling on touch devices doesn't trigger it. A drag\n * starting during a `count` sequence cancels it.\n */\n const onPointerMove = (moveEvent: PointerEvent) => {\n if (\n isPrimaryPointer(moveEvent) &&\n (((repeat || !isActive) &&\n exceeds(startEvent, moveEvent, holdSlop)) ||\n (presses > 1 && isDragActive()))\n ) {\n onPointerCancel(moveEvent)\n }\n }\n\n window.addEventListener(\"pointerup\", onPointerUp, endEventOptions)\n window.addEventListener(\n \"pointercancel\",\n onPointerCancel,\n endEventOptions\n )\n\n if (isHeld || presses > 1) {\n window.addEventListener(\n \"pointermove\",\n onPointerMove,\n endEventOptions\n )\n }\n\n if (isHeld) {\n frame.update(tick, true)\n /**\n * Touch devices open a context menu or callout on long press,\n * which would cancel the gesture.\n */\n target.addEventListener(\"contextmenu\", preventDefault, {\n ...eventOptions,\n passive: false,\n })\n }\n }\n\n targets.forEach((target: EventTarget) => {\n const pointerDownTarget = options.useGlobalTarget ? window : target\n pointerDownTarget.addEventListener(\n \"pointerdown\",\n startPress as EventListener,\n eventOptions\n )\n\n if (isHTMLElement(target)) {\n target.addEventListener(\n \"focus\",\n () => enableKeyboardPress(target, eventOptions),\n eventOptions\n )\n\n if (\n !isElementKeyboardAccessible(target) &&\n !target.hasAttribute(\"tabindex\")\n ) {\n target.tabIndex = 0\n }\n }\n })\n\n return cancelEvents\n}\n"],"names":[],"mappings":"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"}