{"_id":"@dmytromykhailiuk/preact-signal-utils","_rev":"2-acbd6198b4fb46f66178bda90fa82324","name":"@dmytromykhailiuk/preact-signal-utils","dist-tags":{"latest":"1.0.1"},"versions":{"1.0.0":{"name":"@dmytromykhailiuk/preact-signal-utils","version":"1.0.0","keywords":["preact","preact-signals","signals","hooks","utils","reactive","zero-rerender","resize-observer","intersection-observer","touch","typescript","typed"],"author":{"name":"Dmytro Mykhailiuk","email":"dimamykhayluk@gmail.com"},"license":"MIT","_id":"@dmytromykhailiuk/preact-signal-utils@1.0.0","maintainers":[{"name":"dmytromykhailiuk","email":"dimamykhayluk@gmail.com"}],"homepage":"https://github.com/dmytromykhailiuk/preact-signal-utils#readme","bugs":{"url":"https://github.com/dmytromykhailiuk/preact-signal-utils/issues"},"dist":{"shasum":"77e88cf92f1f0a3f03362af31e7576561e13e1e7","tarball":"https://registry.npmjs.org/@dmytromykhailiuk/preact-signal-utils/-/preact-signal-utils-1.0.0.tgz","fileCount":9,"integrity":"sha512-TOwAZrl/Cy4Q8ofiNXljMHJBm/cRx6wgu6JKkj8GM/tPkgev7xHCduhuHNDUzzxHQOV/sBr+p/awZYscAr4Srw==","signatures":[{"sig":"MEQCIF0TUAWtWGYJq7Ifg33Si6qpJQRTUuYSm2FSR8psMwyuAiB8YZV7iueGkg1+6HQwz7g87EHsjXeMOKiWNs8Op5n11w==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":82823},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./package.json":"./package.json"},"gitHead":"4cfc029718f304ca3046b903c3a74cbc948ec0d4","scripts":{"dev":"tsup --watch","lint":"biome check .","test":"vitest run","build":"tsup","format":"biome format --write .","lint:fix":"biome check --write .","typecheck":"tsc --noEmit","playground":"vite --config vite.playground.config.ts","test:watch":"vitest","prepublishOnly":"npm run build"},"_npmUser":{"name":"dmytromykhailiuk","email":"dimamykhayluk@gmail.com"},"repository":{"url":"git+https://github.com/dmytromykhailiuk/preact-signal-utils.git","type":"git"},"_npmVersion":"11.6.2","description":"Utility hooks for @preact/signals — prop/state bridging (useSignalProp, useLinkedSignal, usePrevSignalValue), lifecycle (useInit, useDestroy, useAfterSignalChangeEffect), DOM helpers (useEvent, useElementSizeSignal, useScrollToItem, useTouchMove) and wait","directories":{},"sideEffects":false,"_nodeVersion":"24.12.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.5","vite":"^5.4.11","jsdom":"^25.0.1","preact":"^10.25.4","vitest":"^2.1.8","typescript":"^5.7.3","@types/node":"^22.10.5","@biomejs/biome":"^1.9.4","@preact/signals":"^2.0.1","@preact/preset-vite":"^2.10.1","@testing-library/preact":"^3.2.4"},"peerDependencies":{"preact":">=10.11.0","@preact/signals":"^2.0.0"},"_npmOperationalInternal":{"tmp":"tmp/preact-signal-utils_1.0.0_1785220625967_0.6216909673953355","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@dmytromykhailiuk/preact-signal-utils","version":"1.0.1","description":"Utility hooks for @preact/signals — prop/state bridging (useSignalProp, useLinkedSignal, usePrevSignalValue), lifecycle (useInit, useDestroy, useAfterSignalChangeEffect), DOM helpers (useEvent, useElementSizeSignal, useScrollToItem, useTouchMove) and wait","type":"module","sideEffects":false,"author":{"name":"Dmytro Mykhailiuk","email":"dimamykhayluk@gmail.com"},"license":"MIT","keywords":["preact","preact-signals","signals","hooks","utils","reactive","zero-rerender","resize-observer","intersection-observer","touch","typescript","typed"],"main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./package.json":"./package.json"},"scripts":{"build":"tsup","dev":"tsup --watch","playground":"vite --config vite.playground.config.ts","typecheck":"tsc --noEmit","test":"vitest run","test:watch":"vitest","lint":"biome check .","lint:fix":"biome check --write .","format":"biome format --write .","prepublishOnly":"npm run build"},"engines":{"node":">=18"},"peerDependencies":{"@preact/signals":"^2.0.0","preact":">=10.11.0"},"devDependencies":{"@biomejs/biome":"^1.9.4","@preact/preset-vite":"^2.10.1","@preact/signals":"^2.0.1","@testing-library/preact":"^3.2.4","@types/node":"^22.10.5","jsdom":"^25.0.1","preact":"^10.25.4","tsup":"^8.3.5","typescript":"^5.7.3","vite":"^5.4.11","vitest":"^2.1.8"},"repository":{"type":"git","url":"git+https://github.com/dmytromykhailiuk/preact-signal-utils.git"},"bugs":{"url":"https://github.com/dmytromykhailiuk/preact-signal-utils/issues"},"homepage":"https://dmytromykhailiuk.github.io/preact-signal-utils/","gitHead":"ebdaaf9edcbcb90aab8af5b953a3b3fd2f9d936b","_id":"@dmytromykhailiuk/preact-signal-utils@1.0.1","_nodeVersion":"24.12.0","_npmVersion":"11.6.2","dist":{"integrity":"sha512-Q0DVDMp/YTyZ/9JA3150UXUIyZDrt34W6xYLEHK/YdokMhoLnkLCxqPc79ut4txnvuX9avydkB53c+3cvkvVTw==","shasum":"7ecfcb7cfbdb7dfeb5189ac0aff8ac413c233b23","tarball":"https://registry.npmjs.org/@dmytromykhailiuk/preact-signal-utils/-/preact-signal-utils-1.0.1.tgz","fileCount":9,"unpackedSize":82816,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCVbo1ZVLTxicBMve1XgrCYpvgHYUNLsTQu9dYwcwz+lwIhANK7Z681MYOipa5ERU1l/2ILo08jALLtsMus3gr9Qeu1"}]},"_npmUser":{"name":"dmytromykhailiuk","email":"dimamykhayluk@gmail.com"},"directories":{},"maintainers":[{"name":"dmytromykhailiuk","email":"dimamykhayluk@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/preact-signal-utils_1.0.1_1786639291031_0.8226751926129687"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-28T06:37:05.789Z","modified":"2026-08-13T16:41:31.367Z","1.0.0":"2026-07-28T06:37:06.115Z","1.0.1":"2026-08-13T16:41:31.203Z"},"bugs":{"url":"https://github.com/dmytromykhailiuk/preact-signal-utils/issues"},"author":{"name":"Dmytro Mykhailiuk","email":"dimamykhayluk@gmail.com"},"license":"MIT","homepage":"https://dmytromykhailiuk.github.io/preact-signal-utils/","keywords":["preact","preact-signals","signals","hooks","utils","reactive","zero-rerender","resize-observer","intersection-observer","touch","typescript","typed"],"repository":{"type":"git","url":"git+https://github.com/dmytromykhailiuk/preact-signal-utils.git"},"description":"Utility hooks for @preact/signals — prop/state bridging (useSignalProp, useLinkedSignal, usePrevSignalValue), lifecycle (useInit, useDestroy, useAfterSignalChangeEffect), DOM helpers (useEvent, useElementSizeSignal, useScrollToItem, useTouchMove) and wait","maintainers":[{"name":"dmytromykhailiuk","email":"dimamykhayluk@gmail.com"}],"readme":"# @dmytromykhailiuk/preact-signal-utils\n\nUtility hooks for `@preact/signals` — prop/state bridging, lifecycle helpers, DOM observers and\ngesture tracking. Signals in, signals out: every hook returns signals you bind directly to JSX,\nso components mount once and never re-render.\n\n> **Full documentation:** open [Docs](https://dmytromykhailiuk.github.io/preact-signal-utils/) in a\n> browser — every hook, with examples, a table of contents and cross-links. This README is the\n> short form.\n\n> ⚡ **The rule that makes it work:** never unwrap `.value` in the render path. Bind the signals\n> these hooks return straight to JSX text and attributes, derive with `useComputed`, render\n> conditionals with `<Show>` and lists with `<For>` (from `@preact/signals/utils`). Unwrap only\n> inside `computed`/`effect` callbacks and event handlers.\n\n## Install\n\n```sh\nnpm i @dmytromykhailiuk/preact-signal-utils\n```\n\nPeer dependencies: `preact >= 10.11` and `@preact/signals ^2.0.0`.\n\n## Hooks at a glance\n\n| Export                       | What it does                                                            |\n| ---------------------------- | ----------------------------------------------------------------------- |\n| `useSignalProp`              | Mirrors a plain prop into a stable signal                               |\n| `useLinkedSignal`            | Writable signal that resets whenever its computation changes            |\n| `usePrevSignalValue`         | The previous value of a signal, as a signal                             |\n| `useAfterSignalChangeEffect` | `useSignalEffect` that skips the initial run                            |\n| `useInit`                    | Runs a callback once after mount (returned function = cleanup)          |\n| `useDestroy`                 | Runs a callback once on unmount (always the latest closure)             |\n| `useEvent`                   | Declarative `addEventListener` on `window` or a signal ref              |\n| `useElementSizeSignal`       | Element `{ width, height }` as a signal, via `ResizeObserver`           |\n| `useScrollToItem`            | \"Jump to latest\" — `IntersectionObserver` + smooth scroll               |\n| `useTouchMove`               | Live drag/swipe deltas (touch **and** mouse) as signals                 |\n| `waitFor`                    | Promise that resolves when a signal predicate becomes `true`            |\n\n## Quick start\n\n```tsx\nimport { useComputed } from \"@preact/signals\";\nimport { useElementSizeSignal, useEvent } from \"@dmytromykhailiuk/preact-signal-utils\";\n\nfunction Panel() {\n  const { ref$, size$ } = useElementSizeSignal<HTMLDivElement>();\n  const label = useComputed(() => `${size$.value.width} × ${size$.value.height}`);\n\n  useEvent(\"keydown\", (event) => {\n    if (event.key === \"Escape\") close();\n  });\n\n  // `label` is bound directly — resizes update the text node, never the component\n  return <div ref={ref$}>{label}</div>;\n}\n```\n\n## Bridging props and state\n\n```tsx\n// a plain prop, usable in the signal world — stable signal, updated in place\nconst value$ = useSignalProp(props.value);\n\n// derived-but-editable state: resets when the source changes\nconst draft$ = useLinkedSignal(() => selectedItem$.value.title);\ndraft$.value = \"edited locally\"; // kept until selectedItem$ changes\n\n// what was in the signal before its latest change (null until it changes)\nconst prevStep$ = usePrevSignalValue(step$);\n```\n\n## Lifecycle & effects\n\n```tsx\nuseInit(() => {\n  const id = startPolling();\n  return () => stopPolling(id); // cleanup on unmount\n});\n\nuseDestroy(() => analytics.flush()); // latest closure, unmount only\n\n// fires on every change of theme$ — but not with the value it mounted with\nuseAfterSignalChangeEffect(theme$, (theme) => applyTheme(theme));\n```\n\n## DOM hooks\n\n```tsx\n// window listener with the usual options; detaches on unmount\nuseEvent(\"resize\", onResize, { passive: true });\n\n// or on an element, via a signal ref — attaches when the element appears\nconst { ref$, size$ } = useElementSizeSignal<HTMLDivElement>();\nuseEvent(\"scroll\", onScroll, { target: ref$ });\n\n// chats/logs: is the anchor item out of view, and how to get back to it\nconst { ref$: anchor$, canScroll$, scroll } = useScrollToItem<HTMLLIElement>();\n\n// swipe-to-dismiss: live deltas while dragging, one callback at the end\nconst { x$ } = useTouchMove(cardRef$, ({ x }) => {\n  if (Math.abs(x) > 120) dismiss();\n});\nconst transform = useComputed(() => `translateX(${x$.value}px)`);\n```\n\n## waitFor\n\n```ts\nimport { waitFor } from \"@dmytromykhailiuk/preact-signal-utils\";\n\nawait waitFor(() => session$.value !== null); // resolves on the exact write\n```\n\nNot a hook — works in components, stores and tests alike. If the predicate is already `true`,\nit resolves immediately without creating an effect; otherwise the effect is disposed as soon as\nit fires.\n\n## TypeScript\n\nEverything is typed end to end: `useEvent(\"keydown\", …)` infers `KeyboardEvent` from\n`WindowEventMap`, sizes are `ReadonlySignal<ElementSize>`, refs are `SignalRef<T | null>`\n(compatible with `useSignalRef` from `@preact/signals/utils` and JSX `ref` props). Exported\ntypes: `SignalRef`, `ElementSize`, `UseEventOptions`, `UseElementSizeSignalResult`,\n`UseScrollToItemResult`, `UseTouchMoveResult`, `TouchMoveEndEvent`.\n\n## License\n\nMIT © Dmytro Mykhailiuk\n","readmeFilename":"README.md"}