# useLongPress

## Usage

Detects a single-finger press-and-hold and reports where it happened, so touch users can reach affordances that a pointer gets from right-click. iOS Safari never synthesizes a `contextmenu` event on long-press, which makes this the only touch route into a cursor-positioned surface such as `ContextMenu`. The press cancels on movement, lift, multi-touch or unmount.

The hook attaches no listeners itself. Two ways to bind it: spread the returned `handlers` onto the element (`<div v-on="longPress.handlers">`), or drive it yourself with `start(point)` on `pointerdown`, `moveTo(point)` on `pointermove`, and `cancel` on `pointerup` and `pointercancel`. `ContextMenu` wires the second form on its trigger, and skips mouse pointers so a right-click keeps its own path.

```ts
import { useLongPress } from '@astryx-vue/core'
```

```vue
<script setup lang="ts">
import { useLongPress } from '@astryx-vue/core'
import { ref } from 'vue'

const isDisabled = ref(false)

const longPress = useLongPress({
  disabled: isDisabled,
  onLongPress: (point) => { openMenuAt(point.x, point.y) },
})
</script>

<template>
  <button type="button" v-on="longPress.handlers">
    Hold me
  </button>
</template>
```

## Best practices

| Guidance | Practices |
| --- | --- |
| Do | Use the reported point to position the surface you open, so it appears under the finger. |
| Do | Pair it with the pointer `contextmenu` handler rather than replacing it; this hook only covers touch. |
| Don't | Use it for the primary action of a control; press-and-hold is undiscoverable as the only way to do something. |

## Parameters

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `options` | `LongPressOptions` | — | Configuration object. Required. |
| `options.onLongPress` | `(point: { x: number, y: number }) => void` | — | Fired with the press start point once the press is held for `delayMs`. Required. |
| `options.disabled` | `MaybeRefOrGetter<boolean>` | `false` | When true the detector is inert, so a disabled trigger can flip the flag without re-registering anything. Read on each press, so a ref or a getter works as well as a plain boolean. |
| `options.isDisabled` | `MaybeRefOrGetter<boolean>` | — | The same switch under its other name; either one being true keeps the detector inert. |
| `options.delayMs` | `number` | `500` | Hold duration before the press fires, in milliseconds. |
| `options.moveCancelPx` | `number` | `10` | Movement past this distance on either axis cancels the press, treating it as a scroll or drag. |

## Returns

| Name | Type | Description |
| --- | --- | --- |
| `handlers` | `{ onTouchStart, onTouchMove, onTouchEnd, onTouchCancel }` | The four touch listeners to spread onto the target element, e.g. `<button v-on="press.handlers">`. A second finger, a lift, or a cancel ends the press; a move past `moveCancelPx` does too. |
| `start` | `(point: { x: number, y: number }) => void` | Begins detection at a point. Call it from `pointerdown`. It clears any pending press first, then re-arms the timer. |
| `moveTo` | `(point: { x: number, y: number }) => boolean` | Reports the pointer's new position. Returns true while the press is still inside the tolerance; past it the press is cancelled and it returns false. Returns false as well when no press is pending. |
| `cancel` | `() => void` | Drops any pending press. Bind it to `pointerup` and `pointercancel`. Also runs when the component unmounts. |
