{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "useLongPress", "title": "useLongPress", "description": "useLongPress hook", "registryDependencies": [ "https://shadcn-ahooks.vercel.app/r/useLatest.json", "https://shadcn-ahooks.vercel.app/r/utils.json" ], "files": [ { "path": "packages/hooks/src/useLongPress/index.ts", "content": "import { useRef } from 'react';\nimport useLatest from '../useLatest';\nimport type { BasicTarget } from '../utils/domTarget';\nimport { getTargetElement } from '../utils/domTarget';\nimport useEffectWithTarget from '../utils/useEffectWithTarget';\n\ntype EventType = MouseEvent | TouchEvent;\nexport interface Options {\n delay?: number;\n moveThreshold?: { x?: number; y?: number };\n onClick?: (event: EventType) => void;\n onLongPressEnd?: (event: EventType) => void;\n}\n\nfunction useLongPress(\n onLongPress: (event: EventType) => void,\n target: BasicTarget,\n { delay = 300, moveThreshold, onClick, onLongPressEnd }: Options = {},\n) {\n const onLongPressRef = useLatest(onLongPress);\n const onClickRef = useLatest(onClick);\n const onLongPressEndRef = useLatest(onLongPressEnd);\n\n const timerRef = useRef>(undefined);\n\n const isTriggeredRef = useRef(false);\n const pervPositionRef = useRef({ x: 0, y: 0 });\n const mousePressed = useRef(false);\n const touchPressed = useRef(false);\n const hasMoveThreshold = !!(\n (moveThreshold?.x && moveThreshold.x > 0) ||\n (moveThreshold?.y && moveThreshold.y > 0)\n );\n\n useEffectWithTarget(\n () => {\n const targetElement = getTargetElement(target);\n if (!targetElement?.addEventListener) {\n return;\n }\n\n const overThreshold = (event: EventType) => {\n const { clientX, clientY } = getClientPosition(event);\n const offsetX = Math.abs(clientX - pervPositionRef.current.x);\n const offsetY = Math.abs(clientY - pervPositionRef.current.y);\n\n return !!(\n (moveThreshold?.x && offsetX > moveThreshold.x) ||\n (moveThreshold?.y && offsetY > moveThreshold.y)\n );\n };\n\n function getClientPosition(event: EventType) {\n if ('TouchEvent' in window && event instanceof TouchEvent) {\n return {\n clientX: event.touches[0].clientX,\n clientY: event.touches[0].clientY,\n };\n }\n if (event instanceof MouseEvent) {\n return {\n clientX: event.clientX,\n clientY: event.clientY,\n };\n }\n\n return { clientX: 0, clientY: 0 };\n }\n\n const createTimer = (event: EventType) => {\n timerRef.current = setTimeout(() => {\n onLongPressRef.current(event);\n isTriggeredRef.current = true;\n }, delay);\n };\n\n const onTouchStart = (event: TouchEvent) => {\n if (touchPressed.current) {\n return;\n }\n touchPressed.current = true;\n\n if (hasMoveThreshold) {\n const { clientX, clientY } = getClientPosition(event);\n pervPositionRef.current.x = clientX;\n pervPositionRef.current.y = clientY;\n }\n createTimer(event);\n };\n\n const onMouseDown = (event: MouseEvent) => {\n if ((event as any)?.sourceCapabilities?.firesTouchEvents) {\n return;\n }\n\n mousePressed.current = true;\n\n if (hasMoveThreshold) {\n pervPositionRef.current.x = event.clientX;\n pervPositionRef.current.y = event.clientY;\n }\n createTimer(event);\n };\n\n const onMove = (event: EventType) => {\n if (timerRef.current && overThreshold(event)) {\n clearTimeout(timerRef.current);\n timerRef.current = undefined;\n }\n };\n\n const onTouchEnd = (event: TouchEvent) => {\n if (!touchPressed.current) {\n return;\n }\n touchPressed.current = false;\n\n if (timerRef.current) {\n clearTimeout(timerRef.current);\n timerRef.current = undefined;\n }\n\n if (isTriggeredRef.current) {\n onLongPressEndRef.current?.(event);\n } else if (onClickRef.current) {\n onClickRef.current(event);\n }\n isTriggeredRef.current = false;\n };\n\n const onMouseUp = (event: MouseEvent) => {\n if ((event as any)?.sourceCapabilities?.firesTouchEvents) {\n return;\n }\n if (!mousePressed.current) {\n return;\n }\n mousePressed.current = false;\n\n if (timerRef.current) {\n clearTimeout(timerRef.current);\n timerRef.current = undefined;\n }\n\n if (isTriggeredRef.current) {\n onLongPressEndRef.current?.(event);\n } else if (onClickRef.current) {\n onClickRef.current(event);\n }\n isTriggeredRef.current = false;\n };\n\n const onMouseLeave = (event: MouseEvent) => {\n if (!mousePressed.current) {\n return;\n }\n mousePressed.current = false;\n\n if (timerRef.current) {\n clearTimeout(timerRef.current);\n timerRef.current = undefined;\n }\n if (isTriggeredRef.current) {\n onLongPressEndRef.current?.(event);\n isTriggeredRef.current = false;\n }\n };\n\n targetElement.addEventListener('mousedown', onMouseDown as EventListener);\n targetElement.addEventListener('mouseup', onMouseUp as EventListener);\n targetElement.addEventListener('mouseleave', onMouseLeave as EventListener);\n targetElement.addEventListener('touchstart', onTouchStart as EventListener);\n targetElement.addEventListener('touchend', onTouchEnd as EventListener);\n\n if (hasMoveThreshold) {\n targetElement.addEventListener('mousemove', onMove as EventListener);\n targetElement.addEventListener('touchmove', onMove as EventListener);\n }\n\n return () => {\n if (timerRef.current) {\n clearTimeout(timerRef.current);\n isTriggeredRef.current = false;\n }\n\n targetElement.removeEventListener('mousedown', onMouseDown as EventListener);\n targetElement.removeEventListener('mouseup', onMouseUp as EventListener);\n targetElement.removeEventListener('mouseleave', onMouseLeave as EventListener);\n targetElement.removeEventListener('touchstart', onTouchStart as EventListener);\n targetElement.removeEventListener('touchend', onTouchEnd as EventListener);\n\n if (hasMoveThreshold) {\n targetElement.removeEventListener('mousemove', onMove as EventListener);\n targetElement.removeEventListener('touchmove', onMove as EventListener);\n }\n };\n },\n [],\n target,\n );\n}\n\nexport default useLongPress;\n", "type": "registry:file", "target": "src/hooks/ahooks/useLongPress/index.ts" } ], "type": "registry:hook" }