preact-missing-hooks 1.0.0 → 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.
- package/.github/workflows/test-hooks.yml +30 -0
- package/LICENSE +21 -0
- package/Readme.md +73 -39
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.module.js +1 -1
- package/dist/index.module.js.map +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/index.umd.js.map +1 -1
- package/dist/useEventBus.d.ts +10 -0
- package/dist/useMutationObserver.d.ts +9 -0
- package/package.json +16 -6
- package/src/index.ts +3 -1
- package/src/useEventBus.ts +36 -0
- package/src/useMutationObserver.ts +26 -0
- package/tests/useEventBus.test.tsx +41 -0
- package/tests/useMutationObserver.test.tsx +35 -0
- package/tests/useTransition.test.tsx +25 -0
- package/vite.config.ts +9 -0
|
@@ -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
|
-
|
|
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
|
|
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,84 +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
|
-
|
|
37
|
-
const [
|
|
52
|
+
function ExampleTransition() {
|
|
53
|
+
const [startTransition, isPending] = useTransition();
|
|
38
54
|
|
|
39
55
|
const handleClick = () => {
|
|
40
56
|
startTransition(() => {
|
|
41
|
-
//
|
|
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
|
|
63
|
+
{isPending ? 'Loading...' : 'Click Me'}
|
|
48
64
|
</button>
|
|
49
65
|
);
|
|
50
|
-
}
|
|
66
|
+
}
|
|
51
67
|
```
|
|
52
68
|
|
|
53
69
|
---
|
|
54
70
|
|
|
55
|
-
|
|
71
|
+
### `useMutationObserver`
|
|
56
72
|
|
|
57
|
-
|
|
73
|
+
```tsx
|
|
74
|
+
import { useRef } from 'preact/hooks';
|
|
75
|
+
import { useMutationObserver } from 'preact-missing-hooks';
|
|
58
76
|
|
|
59
|
-
|
|
60
|
-
|
|
77
|
+
function ExampleMutation() {
|
|
78
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
61
79
|
|
|
62
|
-
|
|
80
|
+
useMutationObserver(ref, (mutations) => {
|
|
81
|
+
console.log('Detected mutations:', mutations);
|
|
82
|
+
}, { childList: true, subtree: true });
|
|
63
83
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
* [Microbundle](https://github.com/developit/microbundle)
|
|
68
|
-
* [TypeScript](https://www.typescriptlang.org/)
|
|
84
|
+
return <div ref={ref}>Observe this content</div>;
|
|
85
|
+
}
|
|
86
|
+
```
|
|
69
87
|
|
|
70
88
|
---
|
|
71
89
|
|
|
72
|
-
|
|
90
|
+
### `useEventBus`
|
|
73
91
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
92
|
+
```tsx
|
|
93
|
+
// types.ts
|
|
94
|
+
export type Events = {
|
|
95
|
+
notify: (message: string) => void;
|
|
96
|
+
};
|
|
77
97
|
|
|
78
|
-
|
|
98
|
+
// Sender.tsx
|
|
99
|
+
import { useEventBus } from 'preact-missing-hooks';
|
|
100
|
+
import type { Events } from './types';
|
|
79
101
|
|
|
80
|
-
|
|
102
|
+
function Sender() {
|
|
103
|
+
const { emit } = useEventBus<Events>();
|
|
104
|
+
return <button onClick={() => emit('notify', 'Hello World!')}>Send</button>;
|
|
105
|
+
}
|
|
81
106
|
|
|
82
|
-
|
|
107
|
+
// Receiver.tsx
|
|
108
|
+
import { useEventBus } from 'preact-missing-hooks';
|
|
109
|
+
import { useState, useEffect } from 'preact/hooks';
|
|
110
|
+
import type { Events } from './types';
|
|
83
111
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
112
|
+
function Receiver() {
|
|
113
|
+
const [msg, setMsg] = useState<string>('');
|
|
114
|
+
const { on } = useEventBus<Events>();
|
|
87
115
|
|
|
88
|
-
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
const unsubscribe = on('notify', setMsg);
|
|
118
|
+
return unsubscribe;
|
|
119
|
+
}, []);
|
|
89
120
|
|
|
90
|
-
|
|
91
|
-
|
|
121
|
+
return <div>Message: {msg}</div>;
|
|
122
|
+
}
|
|
92
123
|
```
|
|
93
124
|
|
|
94
125
|
---
|
|
95
126
|
|
|
96
|
-
## 📝 License
|
|
97
127
|
|
|
98
|
-
|
|
128
|
+
## 🛠 Built With
|
|
129
|
+
|
|
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
|
|
99
134
|
|
|
100
135
|
---
|
|
101
136
|
|
|
102
|
-
##
|
|
137
|
+
## 📝 License
|
|
103
138
|
|
|
104
|
-
|
|
139
|
+
MIT © [Prakhar Dubey](https://github.com/prakhardubey2002)
|
|
105
140
|
|
|
106
141
|
---
|
|
107
142
|
|
|
108
|
-
##
|
|
143
|
+
## 📬 Contributing
|
|
109
144
|
|
|
110
|
-
|
|
111
|
-
* [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
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var e=require("preact/hooks");exports.
|
|
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":["
|
|
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"}
|
package/dist/index.module.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{useState as
|
|
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
|
package/dist/index.module.js.map
CHANGED
|
@@ -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":"
|
|
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,
|
|
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
|
package/dist/index.umd.js.map
CHANGED
|
@@ -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":["
|
|
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.
|
|
4
|
-
"description": "A lightweight, extendable collection of missing React-like hooks for Preact",
|
|
3
|
+
"version": "1.0.2",
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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
|
@@ -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
|
+
})
|