{"_id":"@cujuju/solidjs-hold-action","name":"@cujuju/solidjs-hold-action","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@cujuju/solidjs-hold-action","version":"0.1.0","description":"Press-and-hold primitive for SolidJS — useHoldAction hook + HoldIndicator visual. Stages, hover trigger, click suppression.","type":"module","main":"./dist/index.js","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","solid":"./src/index.ts","import":"./dist/index.js"}},"sideEffects":false,"license":"MIT","author":{"name":"Cujuju"},"repository":{"type":"git","url":"git+https://github.com/Cujuju/solidjs-toolkit.git","directory":"packages/hold-action"},"homepage":"https://github.com/Cujuju/solidjs-toolkit/tree/main/packages/hold-action","bugs":{"url":"https://github.com/Cujuju/solidjs-toolkit/issues"},"keywords":["solidjs","hold-to-confirm","press-and-hold","progress","destructive"],"peerDependencies":{"solid-js":">=1.7.0"},"devDependencies":{"jsdom":"^24.0.0","vitest":"^3.2.4"},"publishConfig":{"access":"public"},"scripts":{"test":"vitest run","test:watch":"vitest","build":"vite build"},"_id":"@cujuju/solidjs-hold-action@0.1.0","_integrity":"sha512-rYBrqjeHk5puTbd55xRKDLe0BCUkufFl1xl6Y8ubiPINpGEMTSnnP22OF5bYRLMoFUgWPX7C98PWM3ayqrXd3g==","_resolved":"C:\\Users\\shawn\\AppData\\Local\\Temp\\f47b3029467788307d7d469655433c6a\\cujuju-solidjs-hold-action-0.1.0.tgz","_from":"file:cujuju-solidjs-hold-action-0.1.0.tgz","_nodeVersion":"25.8.2","_npmVersion":"11.12.1","dist":{"integrity":"sha512-rYBrqjeHk5puTbd55xRKDLe0BCUkufFl1xl6Y8ubiPINpGEMTSnnP22OF5bYRLMoFUgWPX7C98PWM3ayqrXd3g==","shasum":"a64ebdf0fea55a0daecc18c43744f7b066f9a4bf","tarball":"https://registry.npmjs.org/@cujuju/solidjs-hold-action/-/solidjs-hold-action-0.1.0.tgz","fileCount":22,"unpackedSize":103356,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIEsGhIa27nhdYirMz90OfnJqLZcklCR1cMAhVIyvLpwIAiEAon/L+XnD9hyACncYo+9jmO8DJqHvEKUPyoJ84jGfWKk="}]},"_npmUser":{"name":"cujuju","email":"cujuju@pm.me"},"directories":{},"maintainers":[{"name":"cujuju","email":"cujuju@pm.me"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/solidjs-hold-action_0.1.0_1777341447842_0.9169201885767546"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-28T01:57:27.766Z","0.1.0":"2026-04-28T01:57:27.974Z","modified":"2026-04-28T01:57:28.146Z"},"maintainers":[{"name":"cujuju","email":"cujuju@pm.me"}],"description":"Press-and-hold primitive for SolidJS — useHoldAction hook + HoldIndicator visual. Stages, hover trigger, click suppression.","homepage":"https://github.com/Cujuju/solidjs-toolkit/tree/main/packages/hold-action","keywords":["solidjs","hold-to-confirm","press-and-hold","progress","destructive"],"repository":{"type":"git","url":"git+https://github.com/Cujuju/solidjs-toolkit.git","directory":"packages/hold-action"},"author":{"name":"Cujuju"},"bugs":{"url":"https://github.com/Cujuju/solidjs-toolkit/issues"},"license":"MIT","readme":"# @cujuju/solidjs-hold-action\n\nPress-and-hold primitive for SolidJS. Pair the `useHoldAction` hook (timing + event handlers + progress signal) with the `<HoldIndicator>` component (SVG ring / rectangle border trace / linear bar) for hold-to-confirm UX.\n\n## Install\n\n```\npnpm add @cujuju/solidjs-hold-action\n```\n\nNo stylesheet — `<HoldIndicator>` uses inline SVG with `currentColor` by default.\n\n## Usage\n\n```tsx\nimport { useHoldAction, HoldIndicator } from '@cujuju/solidjs-hold-action';\n\nfunction DeleteButton() {\n  const hold = useHoldAction({\n    durationMs: 1500,\n    onComplete: () => performDelete(),\n  });\n  return (\n    <button\n      {...hold.handlers}\n      style={{ position: 'relative', color: 'red' }}\n      title=\"Hold to delete\"\n    >\n      <Icon name=\"trash-2\" />\n      <Show when={hold.holding()}>\n        <HoldIndicator progress={hold.progress} shape=\"rect\" />\n      </Show>\n    </button>\n  );\n}\n```\n\n## `useHoldAction(options)`\n\n| Option | Default | Description |\n|---|---|---|\n| `durationMs` | (required) | Duration to complete. Intentionally has no default — pick per context (250ms for confirmations, 1500-2000ms for destructive). |\n| `onComplete` | (required) | Called once when progress reaches 1. |\n| `onProgress(p)` | — | RAF-tick callback with progress 0→1. Use for custom visuals that need per-frame access. |\n| `stages` | — | Array of `{ at: ms, onReach }` intermediate callbacks. Each fires once per hold when its `at` ms is crossed. Not re-fired if the hold cancels. |\n| `trigger` | `'press'` | `'press'` = pointerdown starts, pointerup stops. `'hover'` = pointerenter starts, pointerleave stops. |\n| `enabled` | `() => true` | Accessor; when false, holds do not start. |\n| `onCancel` | — | Called when an in-progress hold is cancelled (pointerup, pointerleave, doc-mouseup, or imperative `cancel()`). Does NOT fire after `onComplete`, and does NOT fire on component cleanup. Use for reverting visual state set during the hold. |\n| `suppressClickAfterComplete` | `true` | Completed hold swallows the subsequent `click` event (prevents double-firing with the parent element's click handler). |\n| `cancelOnLeave` | `true` | Pointer leaving the element cancels. |\n| `cancelOnDocumentMouseUp` | `true` | Mouse up anywhere on the document cancels an in-progress hold (catches release outside the element). |\n\n### Returns\n\n```ts\n{\n  handlers: {\n    onPointerDown, onPointerUp, onPointerEnter, onPointerLeave, onClick\n  },                           // spread onto the target element\n  progress: Accessor<number>,  // 0 → 1\n  holding: Accessor<boolean>,\n  cancel: () => void,\n}\n```\n\n## `<HoldIndicator>`\n\n| Prop | Default | Description |\n|---|---|---|\n| `progress` | (required) | Accessor or plain number 0→1. |\n| `shape` | `'circle'` | `'circle'` / `'rect'` / `'bar'`. |\n| `width`, `height`, `size` | — | Raw sizing. `size` is shorthand for square. |\n| `fillParent` | `true` when no explicit size | Positions absolutely inside a `position: relative` parent with `inset: 0`. |\n| `stroke` | `'currentColor'` | Path color. |\n| `strokeWidth` | `2` | In px. |\n| `strokeLinecap` | `'round'` (circle, bar), `'square'` (rect) | |\n| `easing` | — | Optional `(t: number) => number` curve applied to progress before geometry is computed. Output is clamped to `[0, 1]`, so overshoot easings won't break geometry. Default is linear (no transform). Examples: ease-in `t => t*t`, ease-out `t => 1 - Math.pow(1-t, 3)`. |\n| `radius` | `6` | Corner radius for `shape=\"rect\"`. |\n| `startAngle` | `0` (top) | Circle only — offset in degrees. |\n| `direction` | `'clockwise'` | |\n| `class`, `style` | — | Passthrough. |\n\nThe component measures its parent (or self, when explicit size) via `getBoundingClientRect` + `ResizeObserver` for correct geometry.\n\n## Multi-stage holds\n\n```tsx\nconst hold = useHoldAction({\n  durationMs: 3000,\n  onComplete: () => finalAction(),\n  stages: [\n    { at: 1000, onReach: () => navigator.vibrate?.(10) },\n    { at: 2000, onReach: () => navigator.vibrate?.([20, 20, 20]) },\n  ],\n});\n```\n\n## Hover trigger\n\n```tsx\nconst hold = useHoldAction({\n  durationMs: 800,\n  trigger: 'hover',\n  onComplete: () => openFlyout(),\n});\nreturn (\n  <div onPointerEnter={hold.handlers.onPointerEnter} onPointerLeave={hold.handlers.onPointerLeave}>\n    …\n  </div>\n);\n```\n\n## Suppression details\n\nThe `click` event fires AFTER `pointerup` on mouse interactions. Without suppression, a hold that completes on pointerup would also fire the parent's `onClick` (e.g., opening a popover). With `suppressClickAfterComplete: true` (default), the hold-action's `onClick` handler in the returned `handlers` object will `preventDefault` + `stopPropagation` on the next click event immediately after a completed hold. Tap-without-holding still fires onClick normally.\n\n## Future ideas\n\n- Keyboard support (Space/Enter held) — deferred pending accessibility review for destructive actions\n- Touch-specific duration presets\n- Haptic feedback integration\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-9bbbbafb46c1c079a1d5a232c3b63380"}