preact-missing-hooks 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,30 @@
1
+ name: Test Hooks
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - name: Checkout
15
+ uses: actions/checkout@v3
16
+
17
+ - name: Setup Node.js
18
+ uses: actions/setup-node@v3
19
+ with:
20
+ node-version: 20
21
+ cache: 'npm'
22
+
23
+ - name: Install deps
24
+ run: npm i
25
+
26
+ - name: Type Check
27
+ run: npm run type-check
28
+
29
+ - name: Run Tests
30
+ run: npm run test
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Prakhar Dubey
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the “Software”), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/Readme.md CHANGED
@@ -1,15 +1,29 @@
1
1
  # Preact Missing Hooks
2
2
 
3
- A lightweight, extendable collection of missing React-like hooks for Preact — plus fresh, powerful new ones designed specifically for modern Preact apps..
3
+ <p align="left">
4
+ <a href="https://www.npmjs.com/package/preact-missing-hooks">
5
+ <img src="https://img.shields.io/npm/v/preact-missing-hooks?color=crimson&label=npm%20version" alt="npm version" />
6
+ </a>
7
+ <a href="https://www.npmjs.com/package/preact-missing-hooks">
8
+ <img src="https://img.shields.io/npm/dm/preact-missing-hooks?label=monthly%20downloads" alt="npm downloads" />
9
+ </a>
10
+
11
+ <a href="https://github.com/prakhardubey2002/preact-missing-hooks/actions/workflows/test-hooks.yml">
12
+ <img src="https://img.shields.io/github/actions/workflow/status/prakhardubey2002/preact-missing-hooks/test-hooks.yml?branch=main&label=build%20status" alt="Build Status" />
13
+ </a>
14
+ </p>
15
+
16
+ A lightweight, extendable collection of React-like hooks for Preact, including utilities for transitions, DOM mutation observation, and global event buses.
4
17
 
5
18
  ---
6
19
 
7
20
  ## ✨ Features
8
21
 
9
- * **🔄 `useTransition`**: Mimics React's `useTransition` for deferring state updates.
22
+ * **🔄 `useTransition`** Defers state updates to yield a smoother UI experience.
23
+ * **🔍 `useMutationObserver`** — Reactively observes DOM changes with a familiar hook API.
24
+ * **📡 `useEventBus`** — A simple publish/subscribe system, eliminating props drilling or overuse of context.
10
25
  * ✅ Fully TypeScript compatible
11
26
  * 📦 Bundled with Microbundle
12
- * 🔌 Easily extensible — more hooks can be added in future
13
27
  * ⚡ Zero dependencies (except `preact`)
14
28
 
15
29
  ---
@@ -28,66 +42,104 @@ npm install preact
28
42
 
29
43
  ---
30
44
 
31
- ## 🔧 Usage
45
+ ## 🔧 Usage Examples
46
+
47
+ ### `useTransition`
32
48
 
33
49
  ```tsx
34
50
  import { useTransition } from 'preact-missing-hooks';
35
51
 
36
- const Example = () => {
37
- const [isPending, startTransition] = useTransition();
52
+ function ExampleTransition() {
53
+ const [startTransition, isPending] = useTransition();
38
54
 
39
55
  const handleClick = () => {
40
56
  startTransition(() => {
41
- // Expensive update here
57
+ // perform an expensive update here
42
58
  });
43
59
  };
44
60
 
45
61
  return (
46
62
  <button onClick={handleClick} disabled={isPending}>
47
- {isPending ? 'Loading...' : 'Click me'}
63
+ {isPending ? 'Loading...' : 'Click Me'}
48
64
  </button>
49
65
  );
50
- };
66
+ }
51
67
  ```
52
68
 
53
69
  ---
54
70
 
55
- ## 🔍 API: `useTransition()`
71
+ ### `useMutationObserver`
56
72
 
57
- Returns a tuple:
73
+ ```tsx
74
+ import { useRef } from 'preact/hooks';
75
+ import { useMutationObserver } from 'preact-missing-hooks';
76
+
77
+ function ExampleMutation() {
78
+ const ref = useRef<HTMLDivElement>(null);
58
79
 
59
- * `startTransition(fn: () => void)` — schedules a low-priority update
60
- * `isPending: boolean` — `true` while the transition is in progress
80
+ useMutationObserver(ref, (mutations) => {
81
+ console.log('Detected mutations:', mutations);
82
+ }, { childList: true, subtree: true });
83
+
84
+ return <div ref={ref}>Observe this content</div>;
85
+ }
86
+ ```
61
87
 
62
88
  ---
63
89
 
64
- ## 🛠 Built With
90
+ ### `useEventBus`
65
91
 
66
- * [Preact](https://preactjs.com)
67
- * [Microbundle](https://github.com/developit/microbundle)
68
- * [TypeScript](https://www.typescriptlang.org/)
92
+ ```tsx
93
+ // types.ts
94
+ export type Events = {
95
+ notify: (message: string) => void;
96
+ };
69
97
 
70
- ---
98
+ // Sender.tsx
99
+ import { useEventBus } from 'preact-missing-hooks';
100
+ import type { Events } from './types';
101
+
102
+ function Sender() {
103
+ const { emit } = useEventBus<Events>();
104
+ return <button onClick={() => emit('notify', 'Hello World!')}>Send</button>;
105
+ }
106
+
107
+ // Receiver.tsx
108
+ import { useEventBus } from 'preact-missing-hooks';
109
+ import { useState, useEffect } from 'preact/hooks';
110
+ import type { Events } from './types';
111
+
112
+ function Receiver() {
113
+ const [msg, setMsg] = useState<string>('');
114
+ const { on } = useEventBus<Events>();
71
115
 
72
- ## 🧩 Future Hooks (Planned)
116
+ useEffect(() => {
117
+ const unsubscribe = on('notify', setMsg);
118
+ return unsubscribe;
119
+ }, []);
120
+
121
+ return <div>Message: {msg}</div>;
122
+ }
123
+ ```
73
124
 
74
- * `useMutationObserver`: For observing changes in DOM mutations.
75
125
  ---
76
126
 
77
127
 
78
- ## 📝 License
128
+ ## 🛠 Built With
79
129
 
80
- ISC © [Prakhar Dubey](https://github.com/prakhardubey2002)
130
+ * [Preact](https://preactjs.com)
131
+ * [Microbundle](https://github.com/developit/microbundle)
132
+ * [TypeScript](https://www.typescriptlang.org)
133
+ * [Vitest](https://vitest.dev) for testing
81
134
 
82
135
  ---
83
136
 
84
- ## 📬 Contributing
137
+ ## 📝 License
85
138
 
86
- Feel free to open issues or submit PRs to suggest or contribute additional hooks!
139
+ MIT © [Prakhar Dubey](https://github.com/prakhardubey2002)
87
140
 
88
141
  ---
89
142
 
90
- ## 📎 Related
143
+ ## 📬 Contributing
91
144
 
92
- * [React useTransition Docs](https://react.dev/reference/react/useTransition)
93
- * [Preact Documentation](https://preactjs.com/guide/v10/getting-started/)
145
+ Contributions are welcome! Please open issues or submit PRs with new hooks or improvements.
package/dist/index.d.ts CHANGED
@@ -1 +1,3 @@
1
1
  export * from './useTransition';
2
+ export * from './useMutationObserver';
3
+ export * from './useEventBus';
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- var e=require("preact/hooks");exports.useTransition=function(){var r=e.useState(!1),n=r[0],t=r[1];return[e.useCallback(function(e){t(!0),Promise.resolve().then(function(){e(),t(!1)})},[]),n]};
1
+ var e=require("preact/hooks"),n=new Map;exports.useEventBus=function(){return{emit:e.useCallback(function(e){var t=arguments,r=n.get(e);r&&r.forEach(function(e){return e.apply(void 0,[].slice.call(t,1))})},[]),on:e.useCallback(function(e,t){var r=n.get(e);return r||(r=new Set,n.set(e,r)),r.add(t),function(){r.delete(t),0===r.size&&n.delete(e)}},[])}},exports.useMutationObserver=function(n,t,r){e.useEffect(function(){var e=n.current;if(e){var u=new MutationObserver(t);return u.observe(e,r),function(){return u.disconnect()}}},[n,t,r])},exports.useTransition=function(){var n=e.useState(!1),t=n[0],r=n[1];return[e.useCallback(function(e){r(!0),Promise.resolve().then(function(){e(),r(!1)})},[]),t]};
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/useTransition.ts"],"sourcesContent":["import { useState, useCallback } from 'preact/hooks';\r\n\r\n/**\r\n * Mimics React's useTransition hook in Preact.\r\n * @returns [startTransition, isPending]\r\n */\r\nexport function useTransition(): [startTransition: (callback: () => void) => void, isPending: boolean] {\r\n const [isPending, setIsPending] = useState(false);\r\n\r\n const startTransition = useCallback((callback: () => void) => {\r\n setIsPending(true);\r\n Promise.resolve().then(() => {\r\n callback();\r\n setIsPending(false);\r\n });\r\n }, []);\r\n\r\n return [startTransition, isPending];\r\n}\r\n"],"names":["_useState","useState","isPending","setIsPending","useCallback","callback","Promise","resolve","then"],"mappings":"oDAMgB,WACd,IAAAA,EAAkCC,EAAQA,UAAC,GAApCC,EAASF,KAAEG,EAAYH,EAAA,GAU9B,MAAO,CARiBI,EAAWA,YAAC,SAACC,GACnCF,GAAa,GACbG,QAAQC,UAAUC,KAAK,WACrBH,IACAF,GAAa,EACf,EACF,EAAG,IAEsBD,EAC3B"}
1
+ {"version":3,"file":"index.js","sources":["../src/useEventBus.ts","../src/useMutationObserver.ts","../src/useTransition.ts"],"sourcesContent":["import { useCallback, useEffect } from 'preact/hooks';\r\n\r\ntype EventMap = Record<string, (...args: any[]) => void>;\r\n\r\nconst listeners = new Map<string, Set<(...args: any[]) => void>>();\r\n\r\n/**\r\n * A Preact hook to publish and subscribe to custom events across components.\r\n * @returns An object with `emit` and `on` methods.\r\n */\r\nexport function useEventBus<T extends EventMap>() {\r\n const emit = useCallback(<K extends keyof T>(event: K, ...args: Parameters<T[K]>) => {\r\n const handlers = listeners.get(event as string);\r\n if (handlers) {\r\n handlers.forEach((handler) => handler(...args));\r\n }\r\n }, []);\r\n\r\n const on = useCallback(<K extends keyof T>(event: K, handler: T[K]) => {\r\n let handlers = listeners.get(event as string);\r\n if (!handlers) {\r\n handlers = new Set();\r\n listeners.set(event as string, handlers);\r\n }\r\n handlers.add(handler);\r\n\r\n return () => {\r\n handlers!.delete(handler);\r\n if (handlers!.size === 0) {\r\n listeners.delete(event as string);\r\n }\r\n };\r\n }, []);\r\n\r\n return { emit, on };\r\n}\r\n","import { RefObject } from 'preact'\r\nimport { useEffect } from 'preact/hooks'\r\n\r\nexport type UseMutationObserverOptions = MutationObserverInit\r\n\r\n/**\r\n * A Preact hook to observe DOM mutations using MutationObserver.\r\n * @param target - The element to observe.\r\n * @param callback - Function to call on mutation.\r\n * @param options - MutationObserver options.\r\n */\r\nexport function useMutationObserver(\r\n targetRef: RefObject<HTMLElement | null>,\r\n callback: MutationCallback,\r\n options: MutationObserverInit\r\n) {\r\n useEffect(() => {\r\n const node = targetRef.current\r\n if (!node) return\r\n\r\n const observer = new MutationObserver(callback)\r\n observer.observe(node, options)\r\n\r\n return () => observer.disconnect()\r\n }, [targetRef, callback, options])\r\n}\r\n","import { useState, useCallback } from 'preact/hooks';\r\n\r\n/**\r\n * Mimics React's useTransition hook in Preact.\r\n * @returns [startTransition, isPending]\r\n */\r\nexport function useTransition(): [startTransition: (callback: () => void) => void, isPending: boolean] {\r\n const [isPending, setIsPending] = useState(false);\r\n\r\n const startTransition = useCallback((callback: () => void) => {\r\n setIsPending(true);\r\n Promise.resolve().then(() => {\r\n callback();\r\n setIsPending(false);\r\n });\r\n }, []);\r\n\r\n return [startTransition, isPending];\r\n}\r\n"],"names":["listeners","Map","emit","useCallback","event","_arguments","arguments","handlers","get","forEach","handler","apply","slice","call","on","Set","set","add","size","targetRef","callback","options","useEffect","node","current","observer","MutationObserver","observe","disconnect","_useState","useState","isPending","setIsPending","Promise","resolve","then"],"mappings":"8BAIMA,EAAY,IAAIC,wBAMN,WAwBd,MAAO,CAAEC,KAvBIC,EAAAA,YAAY,SAAoBC,GAAuCC,IAAAA,EAAAC,UAC5EC,EAAWP,EAAUQ,IAAIJ,GAC3BG,GACFA,EAASE,QAAQ,SAACC,UAAYA,EAAOC,WAAA,EAAA,GAAAC,MAAAC,KAAAR,EAAQ,GAAC,EAElD,EAAG,IAkBYS,GAhBJX,EAAWA,YAAC,SAAoBC,EAAUM,GACnD,IAAIH,EAAWP,EAAUQ,IAAIJ,GAO7B,OANKG,IACHA,EAAW,IAAIQ,IACff,EAAUgB,IAAIZ,EAAiBG,IAEjCA,EAASU,IAAIP,GAED,WACVH,EAAS,OAAQG,GACM,IAAnBH,EAAUW,MACZlB,EAAS,OAAQI,EAErB,CACF,EAAG,IAGL,8BCxBgB,SACde,EACAC,EACAC,GAEAC,EAAAA,UAAU,WACR,IAAMC,EAAOJ,EAAUK,QACvB,GAAKD,EAAL,CAEA,IAAME,EAAW,IAAIC,iBAAiBN,GAGtC,OAFAK,EAASE,QAAQJ,EAAMF,GAEV,WAAA,OAAAI,EAASG,YAAY,CAHlC,CAIF,EAAG,CAACT,EAAWC,EAAUC,GAC3B,wBCnBgB,WACd,IAAAQ,EAAkCC,EAAQA,UAAC,GAApCC,EAASF,KAAEG,EAAYH,EAAA,GAU9B,MAAO,CARiB1B,EAAWA,YAAC,SAACiB,GACnCY,GAAa,GACbC,QAAQC,UAAUC,KAAK,WACrBf,IACAY,GAAa,EACf,EACF,EAAG,IAEsBD,EAC3B"}
@@ -1,2 +1,2 @@
1
- import{useState as o,useCallback as r}from"preact/hooks";function n(){var n=o(!1),t=n[0],e=n[1];return[r(function(o){e(!0),Promise.resolve().then(function(){o(),e(!1)})},[]),t]}export{n as useTransition};
1
+ import{useState as n,useCallback as e,useEffect as t}from"preact/hooks";function r(){var t=n(!1),r=t[0],o=t[1];return[e(function(n){o(!0),Promise.resolve().then(function(){n(),o(!1)})},[]),r]}function o(n,e,r){t(function(){var t=n.current;if(t){var o=new MutationObserver(e);return o.observe(t,r),function(){return o.disconnect()}}},[n,e,r])}var i=new Map;function u(){var n=e(function(n){var e=arguments,t=i.get(n);t&&t.forEach(function(n){return n.apply(void 0,[].slice.call(e,1))})},[]);return{emit:n,on:e(function(n,e){var t=i.get(n);return t||(t=new Set,i.set(n,t)),t.add(e),function(){t.delete(e),0===t.size&&i.delete(n)}},[])}}export{u as useEventBus,o as useMutationObserver,r as useTransition};
2
2
  //# sourceMappingURL=index.module.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.module.js","sources":["../src/useTransition.ts"],"sourcesContent":["import { useState, useCallback } from 'preact/hooks';\r\n\r\n/**\r\n * Mimics React's useTransition hook in Preact.\r\n * @returns [startTransition, isPending]\r\n */\r\nexport function useTransition(): [startTransition: (callback: () => void) => void, isPending: boolean] {\r\n const [isPending, setIsPending] = useState(false);\r\n\r\n const startTransition = useCallback((callback: () => void) => {\r\n setIsPending(true);\r\n Promise.resolve().then(() => {\r\n callback();\r\n setIsPending(false);\r\n });\r\n }, []);\r\n\r\n return [startTransition, isPending];\r\n}\r\n"],"names":["useTransition","_useState","useState","isPending","setIsPending","useCallback","callback","Promise","resolve","then"],"mappings":"yDAMgB,SAAAA,IACd,IAAAC,EAAkCC,GAAS,GAApCC,EAASF,KAAEG,EAAYH,EAAA,GAU9B,MAAO,CARiBI,EAAY,SAACC,GACnCF,GAAa,GACbG,QAAQC,UAAUC,KAAK,WACrBH,IACAF,GAAa,EACf,EACF,EAAG,IAEsBD,EAC3B"}
1
+ {"version":3,"file":"index.module.js","sources":["../src/useTransition.ts","../src/useMutationObserver.ts","../src/useEventBus.ts"],"sourcesContent":["import { useState, useCallback } from 'preact/hooks';\r\n\r\n/**\r\n * Mimics React's useTransition hook in Preact.\r\n * @returns [startTransition, isPending]\r\n */\r\nexport function useTransition(): [startTransition: (callback: () => void) => void, isPending: boolean] {\r\n const [isPending, setIsPending] = useState(false);\r\n\r\n const startTransition = useCallback((callback: () => void) => {\r\n setIsPending(true);\r\n Promise.resolve().then(() => {\r\n callback();\r\n setIsPending(false);\r\n });\r\n }, []);\r\n\r\n return [startTransition, isPending];\r\n}\r\n","import { RefObject } from 'preact'\r\nimport { useEffect } from 'preact/hooks'\r\n\r\nexport type UseMutationObserverOptions = MutationObserverInit\r\n\r\n/**\r\n * A Preact hook to observe DOM mutations using MutationObserver.\r\n * @param target - The element to observe.\r\n * @param callback - Function to call on mutation.\r\n * @param options - MutationObserver options.\r\n */\r\nexport function useMutationObserver(\r\n targetRef: RefObject<HTMLElement | null>,\r\n callback: MutationCallback,\r\n options: MutationObserverInit\r\n) {\r\n useEffect(() => {\r\n const node = targetRef.current\r\n if (!node) return\r\n\r\n const observer = new MutationObserver(callback)\r\n observer.observe(node, options)\r\n\r\n return () => observer.disconnect()\r\n }, [targetRef, callback, options])\r\n}\r\n","import { useCallback, useEffect } from 'preact/hooks';\r\n\r\ntype EventMap = Record<string, (...args: any[]) => void>;\r\n\r\nconst listeners = new Map<string, Set<(...args: any[]) => void>>();\r\n\r\n/**\r\n * A Preact hook to publish and subscribe to custom events across components.\r\n * @returns An object with `emit` and `on` methods.\r\n */\r\nexport function useEventBus<T extends EventMap>() {\r\n const emit = useCallback(<K extends keyof T>(event: K, ...args: Parameters<T[K]>) => {\r\n const handlers = listeners.get(event as string);\r\n if (handlers) {\r\n handlers.forEach((handler) => handler(...args));\r\n }\r\n }, []);\r\n\r\n const on = useCallback(<K extends keyof T>(event: K, handler: T[K]) => {\r\n let handlers = listeners.get(event as string);\r\n if (!handlers) {\r\n handlers = new Set();\r\n listeners.set(event as string, handlers);\r\n }\r\n handlers.add(handler);\r\n\r\n return () => {\r\n handlers!.delete(handler);\r\n if (handlers!.size === 0) {\r\n listeners.delete(event as string);\r\n }\r\n };\r\n }, []);\r\n\r\n return { emit, on };\r\n}\r\n"],"names":["useTransition","_useState","useState","isPending","setIsPending","useCallback","callback","Promise","resolve","then","useMutationObserver","targetRef","options","useEffect","node","current","observer","MutationObserver","observe","disconnect","listeners","Map","useEventBus","emit","event","_arguments","arguments","handlers","get","forEach","handler","apply","slice","call","on","Set","set","add","size"],"mappings":"wEAMgB,SAAAA,IACd,IAAAC,EAAkCC,GAAS,GAApCC,EAASF,KAAEG,EAAYH,EAAA,GAU9B,MAAO,CARiBI,EAAY,SAACC,GACnCF,GAAa,GACbG,QAAQC,UAAUC,KAAK,WACrBH,IACAF,GAAa,EACf,EACF,EAAG,IAEsBD,EAC3B,CCPgB,SAAAO,EACdC,EACAL,EACAM,GAEAC,EAAU,WACR,IAAMC,EAAOH,EAAUI,QACvB,GAAKD,EAAL,CAEA,IAAME,EAAW,IAAIC,iBAAiBX,GAGtC,OAFAU,EAASE,QAAQJ,EAAMF,GAEV,WAAA,OAAAI,EAASG,YAAY,CAHlC,CAIF,EAAG,CAACR,EAAWL,EAAUM,GAC3B,CCrBA,IAAMQ,EAAY,IAAIC,IAMN,SAAAC,IACd,IAAMC,EAAOlB,EAAY,SAAoBmB,GAAuCC,IAAAA,EAAAC,UAC5EC,EAAWP,EAAUQ,IAAIJ,GAC3BG,GACFA,EAASE,QAAQ,SAACC,UAAYA,EAAOC,WAAA,EAAA,GAAAC,MAAAC,KAAAR,EAAQ,GAAC,EAElD,EAAG,IAkBH,MAAO,CAAEF,KAAAA,EAAMW,GAhBJ7B,EAAY,SAAoBmB,EAAUM,GACnD,IAAIH,EAAWP,EAAUQ,IAAIJ,GAO7B,OANKG,IACHA,EAAW,IAAIQ,IACff,EAAUgB,IAAIZ,EAAiBG,IAEjCA,EAASU,IAAIP,GAED,WACVH,EAAS,OAAQG,GACM,IAAnBH,EAAUW,MACZlB,EAAS,OAAQI,EAErB,CACF,EAAG,IAGL"}
package/dist/index.umd.js CHANGED
@@ -1,2 +1,2 @@
1
- !function(e,o){"object"==typeof exports&&"undefined"!=typeof module?o(exports,require("preact/hooks")):"function"==typeof define&&define.amd?define(["exports","preact/hooks"],o):o((e||self).preactMissingHooks={},e.hooks)}(this,function(e,o){e.useTransition=function(){var e=o.useState(!1),n=e[0],t=e[1];return[o.useCallback(function(e){t(!0),Promise.resolve().then(function(){e(),t(!1)})},[]),n]}});
1
+ !function(e,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports,require("preact/hooks")):"function"==typeof define&&define.amd?define(["exports","preact/hooks"],n):n((e||self).preactMissingHooks={},e.hooks)}(this,function(e,n){var t=new Map;e.useEventBus=function(){var e=n.useCallback(function(e){var n=arguments,o=t.get(e);o&&o.forEach(function(e){return e.apply(void 0,[].slice.call(n,1))})},[]);return{emit:e,on:n.useCallback(function(e,n){var o=t.get(e);return o||(o=new Set,t.set(e,o)),o.add(n),function(){o.delete(n),0===o.size&&t.delete(e)}},[])}},e.useMutationObserver=function(e,t,o){n.useEffect(function(){var n=e.current;if(n){var r=new MutationObserver(t);return r.observe(n,o),function(){return r.disconnect()}}},[e,t,o])},e.useTransition=function(){var e=n.useState(!1),t=e[0],o=e[1];return[n.useCallback(function(e){o(!0),Promise.resolve().then(function(){e(),o(!1)})},[]),t]}});
2
2
  //# sourceMappingURL=index.umd.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.umd.js","sources":["../src/useTransition.ts"],"sourcesContent":["import { useState, useCallback } from 'preact/hooks';\r\n\r\n/**\r\n * Mimics React's useTransition hook in Preact.\r\n * @returns [startTransition, isPending]\r\n */\r\nexport function useTransition(): [startTransition: (callback: () => void) => void, isPending: boolean] {\r\n const [isPending, setIsPending] = useState(false);\r\n\r\n const startTransition = useCallback((callback: () => void) => {\r\n setIsPending(true);\r\n Promise.resolve().then(() => {\r\n callback();\r\n setIsPending(false);\r\n });\r\n }, []);\r\n\r\n return [startTransition, isPending];\r\n}\r\n"],"names":["_useState","useState","isPending","setIsPending","useCallback","callback","Promise","resolve","then"],"mappings":"6SAMgB,WACd,IAAAA,EAAkCC,EAAQA,UAAC,GAApCC,EAASF,KAAEG,EAAYH,EAAA,GAU9B,MAAO,CARiBI,EAAWA,YAAC,SAACC,GACnCF,GAAa,GACbG,QAAQC,UAAUC,KAAK,WACrBH,IACAF,GAAa,EACf,EACF,EAAG,IAEsBD,EAC3B"}
1
+ {"version":3,"file":"index.umd.js","sources":["../src/useEventBus.ts","../src/useMutationObserver.ts","../src/useTransition.ts"],"sourcesContent":["import { useCallback, useEffect } from 'preact/hooks';\r\n\r\ntype EventMap = Record<string, (...args: any[]) => void>;\r\n\r\nconst listeners = new Map<string, Set<(...args: any[]) => void>>();\r\n\r\n/**\r\n * A Preact hook to publish and subscribe to custom events across components.\r\n * @returns An object with `emit` and `on` methods.\r\n */\r\nexport function useEventBus<T extends EventMap>() {\r\n const emit = useCallback(<K extends keyof T>(event: K, ...args: Parameters<T[K]>) => {\r\n const handlers = listeners.get(event as string);\r\n if (handlers) {\r\n handlers.forEach((handler) => handler(...args));\r\n }\r\n }, []);\r\n\r\n const on = useCallback(<K extends keyof T>(event: K, handler: T[K]) => {\r\n let handlers = listeners.get(event as string);\r\n if (!handlers) {\r\n handlers = new Set();\r\n listeners.set(event as string, handlers);\r\n }\r\n handlers.add(handler);\r\n\r\n return () => {\r\n handlers!.delete(handler);\r\n if (handlers!.size === 0) {\r\n listeners.delete(event as string);\r\n }\r\n };\r\n }, []);\r\n\r\n return { emit, on };\r\n}\r\n","import { RefObject } from 'preact'\r\nimport { useEffect } from 'preact/hooks'\r\n\r\nexport type UseMutationObserverOptions = MutationObserverInit\r\n\r\n/**\r\n * A Preact hook to observe DOM mutations using MutationObserver.\r\n * @param target - The element to observe.\r\n * @param callback - Function to call on mutation.\r\n * @param options - MutationObserver options.\r\n */\r\nexport function useMutationObserver(\r\n targetRef: RefObject<HTMLElement | null>,\r\n callback: MutationCallback,\r\n options: MutationObserverInit\r\n) {\r\n useEffect(() => {\r\n const node = targetRef.current\r\n if (!node) return\r\n\r\n const observer = new MutationObserver(callback)\r\n observer.observe(node, options)\r\n\r\n return () => observer.disconnect()\r\n }, [targetRef, callback, options])\r\n}\r\n","import { useState, useCallback } from 'preact/hooks';\r\n\r\n/**\r\n * Mimics React's useTransition hook in Preact.\r\n * @returns [startTransition, isPending]\r\n */\r\nexport function useTransition(): [startTransition: (callback: () => void) => void, isPending: boolean] {\r\n const [isPending, setIsPending] = useState(false);\r\n\r\n const startTransition = useCallback((callback: () => void) => {\r\n setIsPending(true);\r\n Promise.resolve().then(() => {\r\n callback();\r\n setIsPending(false);\r\n });\r\n }, []);\r\n\r\n return [startTransition, isPending];\r\n}\r\n"],"names":["listeners","Map","emit","useCallback","event","_arguments","arguments","handlers","get","forEach","handler","apply","slice","call","on","Set","set","add","size","targetRef","callback","options","useEffect","node","current","observer","MutationObserver","observe","disconnect","_useState","useState","isPending","setIsPending","Promise","resolve","then"],"mappings":"6RAIA,IAAMA,EAAY,IAAIC,kBAMN,WACd,IAAMC,EAAOC,EAAAA,YAAY,SAAoBC,GAAuCC,IAAAA,EAAAC,UAC5EC,EAAWP,EAAUQ,IAAIJ,GAC3BG,GACFA,EAASE,QAAQ,SAACC,UAAYA,EAAOC,WAAA,EAAA,GAAAC,MAAAC,KAAAR,EAAQ,GAAC,EAElD,EAAG,IAkBH,MAAO,CAAEH,KAAAA,EAAMY,GAhBJX,EAAWA,YAAC,SAAoBC,EAAUM,GACnD,IAAIH,EAAWP,EAAUQ,IAAIJ,GAO7B,OANKG,IACHA,EAAW,IAAIQ,IACff,EAAUgB,IAAIZ,EAAiBG,IAEjCA,EAASU,IAAIP,GAED,WACVH,EAAS,OAAQG,GACM,IAAnBH,EAAUW,MACZlB,EAAS,OAAQI,EAErB,CACF,EAAG,IAGL,wBCxBgB,SACde,EACAC,EACAC,GAEAC,EAAAA,UAAU,WACR,IAAMC,EAAOJ,EAAUK,QACvB,GAAKD,EAAL,CAEA,IAAME,EAAW,IAAIC,iBAAiBN,GAGtC,OAFAK,EAASE,QAAQJ,EAAMF,GAEV,WAAA,OAAAI,EAASG,YAAY,CAHlC,CAIF,EAAG,CAACT,EAAWC,EAAUC,GAC3B,kBCnBgB,WACd,IAAAQ,EAAkCC,EAAQA,UAAC,GAApCC,EAASF,KAAEG,EAAYH,EAAA,GAU9B,MAAO,CARiB1B,EAAWA,YAAC,SAACiB,GACnCY,GAAa,GACbC,QAAQC,UAAUC,KAAK,WACrBf,IACAY,GAAa,EACf,EACF,EAAG,IAEsBD,EAC3B"}
@@ -0,0 +1,10 @@
1
+ type EventMap = Record<string, (...args: any[]) => void>;
2
+ /**
3
+ * A Preact hook to publish and subscribe to custom events across components.
4
+ * @returns An object with `emit` and `on` methods.
5
+ */
6
+ export declare function useEventBus<T extends EventMap>(): {
7
+ emit: <K extends keyof T>(event: K, ...args: Parameters<T[K]>) => void;
8
+ on: <K extends keyof T>(event: K, handler: T[K]) => () => void;
9
+ };
10
+ export {};
@@ -0,0 +1,9 @@
1
+ import { RefObject } from 'preact';
2
+ export type UseMutationObserverOptions = MutationObserverInit;
3
+ /**
4
+ * A Preact hook to observe DOM mutations using MutationObserver.
5
+ * @param target - The element to observe.
6
+ * @param callback - Function to call on mutation.
7
+ * @param options - MutationObserver options.
8
+ */
9
+ export declare function useMutationObserver(targetRef: RefObject<HTMLElement | null>, callback: MutationCallback, options: MutationObserverInit): void;
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "preact-missing-hooks",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "A lightweight, extendable collection of missing React-like hooks for Preact — plus fresh, powerful new ones designed specifically for modern Preact apps.",
5
5
  "author": "Prakhar Dubey",
6
- "license": "ISC",
6
+ "license": "MIT",
7
7
  "main": "dist/index.js",
8
8
  "module": "dist/index.module.js",
9
9
  "types": "dist/index.d.ts",
@@ -24,7 +24,8 @@
24
24
  "build": "microbundle",
25
25
  "dev": "microbundle watch",
26
26
  "prepublishOnly": "npm run build",
27
- "test": "echo \"Error: no test specified\" && exit 1"
27
+ "test": "vitest run",
28
+ "type-check": "tsc --noEmit"
28
29
  },
29
30
  "keywords": [
30
31
  "preact",
@@ -35,12 +36,21 @@
35
36
  "typescript",
36
37
  "preact-hooks"
37
38
  ],
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "https://github.com/prakhardubey2002/Preact-Missing-Hooks"
42
+ },
38
43
  "dependencies": {
39
- "preact": "^10.26.7"
44
+ "preact": ">=10.0.0"
40
45
  },
41
46
  "devDependencies": {
47
+ "@testing-library/jest-dom": "^6.6.3",
48
+ "@testing-library/preact": "^3.2.4",
49
+ "@types/jest": "^29.5.14",
50
+ "jsdom": "^26.1.0",
42
51
  "microbundle": "^0.15.1",
43
- "typescript": "^5.8.3"
52
+ "typescript": "^5.8.3",
53
+ "vitest": "^3.1.4"
44
54
  },
45
55
  "peerDependencies": {
46
56
  "preact": ">=10.0.0"
package/src/index.ts CHANGED
@@ -1 +1,3 @@
1
- export * from './useTransition';
1
+ export * from './useTransition'
2
+ export * from './useMutationObserver'
3
+ export * from './useEventBus'
@@ -0,0 +1,36 @@
1
+ import { useCallback, useEffect } from 'preact/hooks';
2
+
3
+ type EventMap = Record<string, (...args: any[]) => void>;
4
+
5
+ const listeners = new Map<string, Set<(...args: any[]) => void>>();
6
+
7
+ /**
8
+ * A Preact hook to publish and subscribe to custom events across components.
9
+ * @returns An object with `emit` and `on` methods.
10
+ */
11
+ export function useEventBus<T extends EventMap>() {
12
+ const emit = useCallback(<K extends keyof T>(event: K, ...args: Parameters<T[K]>) => {
13
+ const handlers = listeners.get(event as string);
14
+ if (handlers) {
15
+ handlers.forEach((handler) => handler(...args));
16
+ }
17
+ }, []);
18
+
19
+ const on = useCallback(<K extends keyof T>(event: K, handler: T[K]) => {
20
+ let handlers = listeners.get(event as string);
21
+ if (!handlers) {
22
+ handlers = new Set();
23
+ listeners.set(event as string, handlers);
24
+ }
25
+ handlers.add(handler);
26
+
27
+ return () => {
28
+ handlers!.delete(handler);
29
+ if (handlers!.size === 0) {
30
+ listeners.delete(event as string);
31
+ }
32
+ };
33
+ }, []);
34
+
35
+ return { emit, on };
36
+ }
@@ -0,0 +1,26 @@
1
+ import { RefObject } from 'preact'
2
+ import { useEffect } from 'preact/hooks'
3
+
4
+ export type UseMutationObserverOptions = MutationObserverInit
5
+
6
+ /**
7
+ * A Preact hook to observe DOM mutations using MutationObserver.
8
+ * @param target - The element to observe.
9
+ * @param callback - Function to call on mutation.
10
+ * @param options - MutationObserver options.
11
+ */
12
+ export function useMutationObserver(
13
+ targetRef: RefObject<HTMLElement | null>,
14
+ callback: MutationCallback,
15
+ options: MutationObserverInit
16
+ ) {
17
+ useEffect(() => {
18
+ const node = targetRef.current
19
+ if (!node) return
20
+
21
+ const observer = new MutationObserver(callback)
22
+ observer.observe(node, options)
23
+
24
+ return () => observer.disconnect()
25
+ }, [targetRef, callback, options])
26
+ }
@@ -0,0 +1,41 @@
1
+ /** @jsx h */
2
+ import { h } from 'preact'
3
+ import { render, fireEvent, screen } from '@testing-library/preact'
4
+ import { useEventBus } from '../src/useEventBus'
5
+ import { useEffect, useState } from 'preact/hooks'
6
+
7
+ type Events = {
8
+ notify: (msg: string) => void
9
+ }
10
+
11
+ function Sender() {
12
+ const { emit } = useEventBus<Events>()
13
+ return <button onClick={() => emit('notify', 'hello')}>Send</button>
14
+ }
15
+
16
+ function Receiver() {
17
+ const { on } = useEventBus<Events>()
18
+ const [message, setMessage] = useState('')
19
+
20
+ useEffect(() => {
21
+ const unsub = on('notify', setMessage)
22
+ return unsub
23
+ }, [on])
24
+
25
+ return <div>{message}</div>
26
+ }
27
+
28
+ test('useEventBus sends and receives events', () => {
29
+ render(
30
+ <div>
31
+ <Sender />
32
+ <Receiver />
33
+ </div>
34
+ )
35
+
36
+ fireEvent.click(screen.getByText('Send'))
37
+
38
+ const node = screen.getByText('hello')
39
+ expect(node).not.toBeNull()
40
+ expect(node.textContent).toBe('hello')
41
+ })
@@ -0,0 +1,35 @@
1
+ /** @jsx h */
2
+ import { h } from 'preact'
3
+ import { render } from '@testing-library/preact'
4
+ import { useEffect, useRef } from 'preact/hooks'
5
+ import { useMutationObserver } from '../src/useMutationObserver'
6
+ import { vi } from 'vitest'
7
+ import { waitFor } from '@testing-library/preact'
8
+
9
+ describe('useMutationObserver', () => {
10
+ it('triggers callback on DOM mutation', async () => {
11
+ const callback = vi.fn()
12
+
13
+ function TestComponent() {
14
+ const ref = useRef<HTMLDivElement>(null)
15
+
16
+ useMutationObserver(ref, callback, { childList: true })
17
+
18
+ useEffect(() => {
19
+ if (ref.current) {
20
+ const span = document.createElement('span')
21
+ span.textContent = 'New Child'
22
+ ref.current.appendChild(span)
23
+ }
24
+ }, [])
25
+
26
+ return <div ref={ref}></div>
27
+ }
28
+
29
+ render(<TestComponent />)
30
+
31
+ await waitFor(() => {
32
+ expect(callback).toHaveBeenCalled()
33
+ })
34
+ })
35
+ })
@@ -0,0 +1,25 @@
1
+ /** @jsx h */
2
+ import { render, fireEvent, screen } from '@testing-library/preact'
3
+ import '@testing-library/jest-dom'
4
+ import { useTransition } from '../src/useTransition'
5
+ import { h } from 'preact'
6
+
7
+ function TestComponent() {
8
+ const [startTransition, isPending] = useTransition()
9
+
10
+ return (
11
+ <div>
12
+ <button onClick={() => startTransition(() => {})}>Trigger</button>
13
+ <span>{isPending ? 'Pending' : 'Idle'}</span>
14
+ </div>
15
+ )
16
+ }
17
+
18
+ test('useTransition updates isPending', async () => {
19
+ render(<TestComponent />)
20
+ fireEvent.click(screen.getByText('Trigger'))
21
+
22
+ expect(screen.getByText(/Pending|Idle/)).toBeInTheDocument()
23
+
24
+ await Promise.resolve()
25
+ })
package/vite.config.ts ADDED
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ environment: 'jsdom',
7
+ setupFiles: [],
8
+ },
9
+ });