use-web-kit 0.1.0

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/README.md ADDED
@@ -0,0 +1,176 @@
1
+ # use-web-kit
2
+
3
+ **A zero-dependency, lightweight (~3KB) collection of React hooks optimized for performance and modern Browser APIs.**
4
+
5
+ `License: MIT` | `TypeScript: Included` | `Size: ~3KB`
6
+
7
+ ---
8
+
9
+ ## Features
10
+
11
+ - Full TypeScript support with exported types
12
+ - Dual ESM and CJS bundles
13
+ - Tree-shakeable exports
14
+ - Zero runtime dependencies
15
+ - Production-ready with comprehensive test coverage
16
+
17
+ ---
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install use-web-kit
23
+ ```
24
+
25
+ ```bash
26
+ yarn add use-web-kit
27
+ ```
28
+
29
+ ```bash
30
+ pnpm add use-web-kit
31
+ ```
32
+
33
+ ---
34
+
35
+ ## API
36
+
37
+ ### useIdleQueue
38
+
39
+ Queues non-urgent tasks to run during browser idle time using `requestIdleCallback` with a robust `setTimeout` fallback for Safari and older browsers.
40
+
41
+ **Signature**
42
+
43
+ ```ts
44
+ const { enqueue, clearQueue, queueLength } = useIdleQueue(options?)
45
+ ```
46
+
47
+ **Options**
48
+
49
+ | Parameter | Type | Default | Description |
50
+ | ------------------ | -------- | ----------- | ------------------------------------------- |
51
+ | `timeout` | `number` | `undefined` | Max wait time (ms) before forcing execution |
52
+ | `fallbackInterval` | `number` | `50` | Interval (ms) for the `setTimeout` fallback |
53
+
54
+ **Usage**
55
+
56
+ ```tsx
57
+ import { useIdleQueue } from "use-web-kit";
58
+
59
+ function Dashboard() {
60
+ const { enqueue, queueLength } = useIdleQueue({ timeout: 2000 });
61
+
62
+ const handleClick = () => {
63
+ enqueue(() => {
64
+ fetch("/api/analytics", {
65
+ method: "POST",
66
+ body: JSON.stringify({ event: "button_click" }),
67
+ });
68
+ });
69
+ };
70
+
71
+ return (
72
+ <div>
73
+ <button onClick={handleClick}>Track Event</button>
74
+ <span>Pending tasks: {queueLength}</span>
75
+ </div>
76
+ );
77
+ }
78
+ ```
79
+
80
+ ---
81
+
82
+ ### useBroadcastState
83
+
84
+ Synchronizes state across different browser tabs and windows in real-time using the Broadcast Channel API. Works as a drop-in replacement for `useState`.
85
+
86
+ **Signature**
87
+
88
+ ```ts
89
+ const [state, setState] = useBroadcastState<T>(channelName, initialValue);
90
+ ```
91
+
92
+ | Parameter | Type | Description |
93
+ | -------------- | -------- | ----------------------------------------------- |
94
+ | `channelName` | `string` | Unique name identifying the shared channel |
95
+ | `initialValue` | `T` | Initial state value (must be JSON-serializable) |
96
+
97
+ **Usage**
98
+
99
+ ```tsx
100
+ import { useBroadcastState } from "use-web-kit";
101
+
102
+ function Counter() {
103
+ const [count, setCount] = useBroadcastState<number>("shared-counter", 0);
104
+
105
+ return (
106
+ <div>
107
+ <p>Count: {count}</p>
108
+ <button onClick={() => setCount((prev) => prev + 1)}>Increment</button>
109
+ <button onClick={() => setCount(0)}>Reset</button>
110
+ </div>
111
+ );
112
+ }
113
+ ```
114
+
115
+ Open this component in multiple tabs to see the count stay in sync.
116
+
117
+ ---
118
+
119
+ ### useAdaptivePolling
120
+
121
+ Polls an endpoint at a specified interval but automatically pauses or throttles the frequency when the browser tab is in the background, saving network and compute resources.
122
+
123
+ **Signature**
124
+
125
+ ```ts
126
+ useAdaptivePolling(callback, options);
127
+ ```
128
+
129
+ **Options**
130
+
131
+ | Parameter | Type | Default | Description |
132
+ | -------------------------- | --------- | ---------- | ------------------------------------------------------------------------------------- |
133
+ | `interval` | `number` | _required_ | Polling interval in milliseconds |
134
+ | `enabled` | `boolean` | `true` | Toggle polling on or off |
135
+ | `pauseOnBackground` | `boolean` | `true` | Fully pause when the tab is hidden |
136
+ | `backgroundSlowdownFactor` | `number` | `5` | Multiplier applied to interval when hidden (used when `pauseOnBackground` is `false`) |
137
+
138
+ **Usage**
139
+
140
+ ```tsx
141
+ import { useAdaptivePolling } from "react-hook-kit";
142
+ import { useState } from "react";
143
+
144
+ function Notifications() {
145
+ const [data, setData] = useState(null);
146
+
147
+ useAdaptivePolling(
148
+ () => {
149
+ fetch("/api/notifications")
150
+ .then((res) => res.json())
151
+ .then(setData);
152
+ },
153
+ {
154
+ interval: 3000,
155
+ pauseOnBackground: false,
156
+ backgroundSlowdownFactor: 4,
157
+ }
158
+ );
159
+
160
+ return <pre>{JSON.stringify(data, null, 2)}</pre>;
161
+ }
162
+ ```
163
+
164
+ When the tab is hidden, the polling interval increases from 3 seconds to 12 seconds. When the user returns, polling resumes at normal speed and fires immediately.
165
+
166
+ ---
167
+
168
+ ## License
169
+
170
+ MIT
171
+
172
+ ---
173
+
174
+ ## Author
175
+
176
+ Tanush
@@ -0,0 +1,23 @@
1
+ type IdleQueueTask = () => void | Promise<void>;
2
+ interface UseIdleQueueOptions {
3
+ timeout?: number;
4
+ fallbackInterval?: number;
5
+ }
6
+ interface UseIdleQueueReturn {
7
+ enqueue: (task: IdleQueueTask) => void;
8
+ clearQueue: () => void;
9
+ queueLength: number;
10
+ }
11
+ declare function useIdleQueue(options?: UseIdleQueueOptions): UseIdleQueueReturn;
12
+
13
+ declare function useBroadcastState<T>(channelName: string, initialValue: T): [T, (value: T | ((prevState: T) => T)) => void];
14
+
15
+ interface UseAdaptivePollingOptions {
16
+ interval: number;
17
+ enabled?: boolean;
18
+ pauseOnBackground?: boolean;
19
+ backgroundSlowdownFactor?: number;
20
+ }
21
+ declare function useAdaptivePolling(callback: () => void, options: UseAdaptivePollingOptions): void;
22
+
23
+ export { type IdleQueueTask, type UseAdaptivePollingOptions, type UseIdleQueueOptions, type UseIdleQueueReturn, useAdaptivePolling, useBroadcastState, useIdleQueue };
@@ -0,0 +1,23 @@
1
+ type IdleQueueTask = () => void | Promise<void>;
2
+ interface UseIdleQueueOptions {
3
+ timeout?: number;
4
+ fallbackInterval?: number;
5
+ }
6
+ interface UseIdleQueueReturn {
7
+ enqueue: (task: IdleQueueTask) => void;
8
+ clearQueue: () => void;
9
+ queueLength: number;
10
+ }
11
+ declare function useIdleQueue(options?: UseIdleQueueOptions): UseIdleQueueReturn;
12
+
13
+ declare function useBroadcastState<T>(channelName: string, initialValue: T): [T, (value: T | ((prevState: T) => T)) => void];
14
+
15
+ interface UseAdaptivePollingOptions {
16
+ interval: number;
17
+ enabled?: boolean;
18
+ pauseOnBackground?: boolean;
19
+ backgroundSlowdownFactor?: number;
20
+ }
21
+ declare function useAdaptivePolling(callback: () => void, options: UseAdaptivePollingOptions): void;
22
+
23
+ export { type IdleQueueTask, type UseAdaptivePollingOptions, type UseIdleQueueOptions, type UseIdleQueueReturn, useAdaptivePolling, useBroadcastState, useIdleQueue };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ 'use strict';var react=require('react');var w=()=>typeof window<"u"&&typeof window.requestIdleCallback=="function";function x(i={}){let{timeout:d,fallbackInterval:r=50}=i,s=react.useRef([]),u=react.useRef(null),o=react.useRef(false),a=react.useRef(false),[f,n]=react.useState(0),e=react.useCallback(l=>{if(w()){let m=d!=null?{timeout:d}:{};u.current=window.requestIdleCallback(l,m);}else u.current=window.setTimeout(l,r);},[d,r]),t=react.useCallback(()=>{u.current!=null&&(w()?window.cancelIdleCallback(u.current):window.clearTimeout(u.current),u.current=null);},[]),c=react.useCallback(()=>{if(a.current||o.current)return;let l=s.current.shift();if(!l){o.current=false;return}o.current=true,n(s.current.length),(async()=>{try{await l();}catch(k){console.error("[useIdleQueue] Task threw an error:",k);}finally{o.current=false,!a.current&&s.current.length>0&&e(c);}})();},[e]),p=react.useCallback(l=>{a.current||(s.current.push(l),n(s.current.length),o.current||e(c));},[e,c]),y=react.useCallback(()=>{s.current=[],n(0),t(),o.current=false;},[t]);return react.useEffect(()=>(a.current=false,()=>{a.current=true,t(),s.current=[];}),[t]),{enqueue:p,clearQueue:y,queueLength:f}}function C(i,d){let[r,s]=react.useState(d),u=react.useRef(r);react.useEffect(()=>{u.current=r;},[r]);let o=react.useRef(null),a=react.useRef(false);react.useEffect(()=>{if(typeof BroadcastChannel>"u")return;let n=new BroadcastChannel(i);return o.current=n,n.onmessage=e=>{try{let t=typeof e.data=="string"?JSON.parse(e.data):e.data,c=JSON.stringify(u.current),p=JSON.stringify(t);c!==p&&(a.current=!0,s(t));}catch(t){console.error("[useBroadcastState] Failed to parse incoming message:",t);}},n.onmessageerror=e=>{console.error("[useBroadcastState] Message deserialization error:",e);},()=>{n.close(),o.current=null;}},[i]);let f=react.useCallback(n=>{let e=typeof n=="function"?n(u.current):n;if(u.current=e,s(e),o.current)try{o.current.postMessage(JSON.stringify(e));}catch(t){console.error("[useBroadcastState] Failed to broadcast state:",t);}},[]);return react.useEffect(()=>{a.current&&(a.current=false);},[r]),[r,f]}function U(i,d){let{interval:r,enabled:s=true,pauseOnBackground:u=true,backgroundSlowdownFactor:o=5}=d,a=react.useRef(i);react.useEffect(()=>{a.current=i;},[i]);let f=react.useRef(0),n=react.useRef(null),e=react.useCallback(()=>{f.current=Date.now(),a.current();},[]),t=react.useCallback(()=>{n.current!=null&&(clearInterval(n.current),n.current=null);},[]),c=react.useCallback(p=>{t(),n.current=setInterval(e,p);},[t,e]);react.useEffect(()=>{if(!s){t();return}let l=typeof document<"u"&&document.visibilityState==="hidden"?u?0:r*o:r;l>0&&c(l);let m=()=>{document.visibilityState==="visible"?(Date.now()-f.current>=r&&e(),c(r)):u?t():c(r*o);};return typeof document<"u"&&document.addEventListener("visibilitychange",m),()=>{t(),typeof document<"u"&&document.removeEventListener("visibilitychange",m);}},[r,s,u,o,c,t,e]);}exports.useAdaptivePolling=U;exports.useBroadcastState=C;exports.useIdleQueue=x;//# sourceMappingURL=index.js.map
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hooks/useIdleQueue.ts","../src/hooks/useBroadcastState.ts","../src/hooks/useAdaptivePolling.ts"],"names":["hasIdleCallback","useIdleQueue","options","timeout","fallbackInterval","queueRef","useRef","handleRef","processingRef","unmountedRef","queueLength","setQueueLength","useState","schedule","useCallback","fn","opts","cancelScheduled","processQueue","task","error","enqueue","clearQueue","useEffect","useBroadcastState","channelName","initialValue","state","setState","stateRef","channelRef","isExternalUpdateRef","channel","event","incoming","current","next","setBroadcastState","value","nextValue","useAdaptivePolling","callback","interval","enabled","pauseOnBackground","backgroundSlowdownFactor","callbackRef","lastFiredRef","intervalIdRef","fire","clearTimer","startTimer","ms","startingInterval","onVisibilityChange"],"mappings":"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","file":"index.js","sourcesContent":["import { useState, useRef, useCallback, useEffect } from 'react';\r\n\r\nexport type IdleQueueTask = () => void | Promise<void>;\r\n\r\nexport interface UseIdleQueueOptions {\r\n timeout?: number;\r\n fallbackInterval?: number;\r\n}\r\n\r\nexport interface UseIdleQueueReturn {\r\n enqueue: (task: IdleQueueTask) => void;\r\n clearQueue: () => void;\r\n queueLength: number;\r\n}\r\n\r\nconst hasIdleCallback = (): boolean =>\r\n typeof window !== 'undefined' &&\r\n typeof window.requestIdleCallback === 'function';\r\n\r\nexport function useIdleQueue(options: UseIdleQueueOptions = {}): UseIdleQueueReturn {\r\n const { timeout, fallbackInterval = 50 } = options;\r\n\r\n const queueRef = useRef<IdleQueueTask[]>([]);\r\n const handleRef = useRef<number | null>(null);\r\n const processingRef = useRef(false);\r\n const unmountedRef = useRef(false);\r\n\r\n const [queueLength, setQueueLength] = useState(0);\r\n\r\n const schedule = useCallback(\r\n (fn: () => void) => {\r\n if (hasIdleCallback()) {\r\n const opts: IdleRequestOptions = timeout != null ? { timeout } : {};\r\n handleRef.current = window.requestIdleCallback(fn, opts);\r\n } else {\r\n handleRef.current = window.setTimeout(fn, fallbackInterval) as unknown as number;\r\n }\r\n },\r\n [timeout, fallbackInterval],\r\n );\r\n\r\n const cancelScheduled = useCallback(() => {\r\n if (handleRef.current == null) return;\r\n\r\n if (hasIdleCallback()) {\r\n window.cancelIdleCallback(handleRef.current);\r\n } else {\r\n window.clearTimeout(handleRef.current);\r\n }\r\n handleRef.current = null;\r\n }, []);\r\n\r\n const processQueue = useCallback(() => {\r\n if (unmountedRef.current || processingRef.current) return;\r\n\r\n const task = queueRef.current.shift();\r\n if (!task) {\r\n processingRef.current = false;\r\n return;\r\n }\r\n\r\n processingRef.current = true;\r\n setQueueLength(queueRef.current.length);\r\n\r\n const execute = async () => {\r\n try {\r\n await task();\r\n } catch (error) {\r\n console.error('[useIdleQueue] Task threw an error:', error);\r\n } finally {\r\n processingRef.current = false;\r\n if (!unmountedRef.current && queueRef.current.length > 0) {\r\n schedule(processQueue);\r\n }\r\n }\r\n };\r\n\r\n void execute();\r\n }, [schedule]);\r\n\r\n const enqueue = useCallback(\r\n (task: IdleQueueTask) => {\r\n if (unmountedRef.current) return;\r\n\r\n queueRef.current.push(task);\r\n setQueueLength(queueRef.current.length);\r\n\r\n if (!processingRef.current) {\r\n schedule(processQueue);\r\n }\r\n },\r\n [schedule, processQueue],\r\n );\r\n\r\n const clearQueue = useCallback(() => {\r\n queueRef.current = [];\r\n setQueueLength(0);\r\n cancelScheduled();\r\n processingRef.current = false;\r\n }, [cancelScheduled]);\r\n\r\n useEffect(() => {\r\n unmountedRef.current = false;\r\n\r\n return () => {\r\n unmountedRef.current = true;\r\n cancelScheduled();\r\n queueRef.current = [];\r\n };\r\n }, [cancelScheduled]);\r\n\r\n return { enqueue, clearQueue, queueLength };\r\n}\r\n","import { useState, useRef, useCallback, useEffect } from 'react';\r\n\r\nexport function useBroadcastState<T>(\r\n channelName: string,\r\n initialValue: T,\r\n): [T, (value: T | ((prevState: T) => T)) => void] {\r\n const [state, setState] = useState<T>(initialValue);\r\n\r\n const stateRef = useRef<T>(state);\r\n useEffect(() => {\r\n stateRef.current = state;\r\n }, [state]);\r\n\r\n const channelRef = useRef<BroadcastChannel | null>(null);\r\n const isExternalUpdateRef = useRef(false);\r\n\r\n useEffect(() => {\r\n if (typeof BroadcastChannel === 'undefined') return;\r\n\r\n const channel = new BroadcastChannel(channelName);\r\n channelRef.current = channel;\r\n\r\n channel.onmessage = (event: MessageEvent) => {\r\n try {\r\n const incoming: T =\r\n typeof event.data === 'string'\r\n ? (JSON.parse(event.data) as T)\r\n : (event.data as T);\r\n\r\n const current = JSON.stringify(stateRef.current);\r\n const next = JSON.stringify(incoming);\r\n\r\n if (current !== next) {\r\n isExternalUpdateRef.current = true;\r\n setState(incoming);\r\n }\r\n } catch (error) {\r\n console.error(\r\n '[useBroadcastState] Failed to parse incoming message:',\r\n error,\r\n );\r\n }\r\n };\r\n\r\n channel.onmessageerror = (event: MessageEvent) => {\r\n console.error(\r\n '[useBroadcastState] Message deserialization error:',\r\n event,\r\n );\r\n };\r\n\r\n return () => {\r\n channel.close();\r\n channelRef.current = null;\r\n };\r\n }, [channelName]);\r\n\r\n const setBroadcastState = useCallback(\r\n (value: T | ((prevState: T) => T)) => {\r\n const nextValue: T =\r\n typeof value === 'function'\r\n ? (value as (prevState: T) => T)(stateRef.current)\r\n : value;\r\n\r\n stateRef.current = nextValue;\r\n setState(nextValue);\r\n\r\n if (channelRef.current) {\r\n try {\r\n channelRef.current.postMessage(JSON.stringify(nextValue));\r\n } catch (error) {\r\n console.error(\r\n '[useBroadcastState] Failed to broadcast state:',\r\n error,\r\n );\r\n }\r\n }\r\n },\r\n [],\r\n );\r\n\r\n useEffect(() => {\r\n if (isExternalUpdateRef.current) {\r\n isExternalUpdateRef.current = false;\r\n }\r\n }, [state]);\r\n\r\n return [state, setBroadcastState];\r\n}\r\n","import { useEffect, useRef, useCallback } from 'react';\r\n\r\nexport interface UseAdaptivePollingOptions {\r\n interval: number;\r\n enabled?: boolean;\r\n pauseOnBackground?: boolean;\r\n backgroundSlowdownFactor?: number;\r\n}\r\n\r\nexport function useAdaptivePolling(\r\n callback: () => void,\r\n options: UseAdaptivePollingOptions,\r\n): void {\r\n const {\r\n interval,\r\n enabled = true,\r\n pauseOnBackground = true,\r\n backgroundSlowdownFactor = 5,\r\n } = options;\r\n\r\n const callbackRef = useRef(callback);\r\n useEffect(() => {\r\n callbackRef.current = callback;\r\n }, [callback]);\r\n\r\n const lastFiredRef = useRef<number>(0);\r\n const intervalIdRef = useRef<ReturnType<typeof setInterval> | null>(null);\r\n\r\n const fire = useCallback(() => {\r\n lastFiredRef.current = Date.now();\r\n callbackRef.current();\r\n }, []);\r\n\r\n const clearTimer = useCallback(() => {\r\n if (intervalIdRef.current != null) {\r\n clearInterval(intervalIdRef.current);\r\n intervalIdRef.current = null;\r\n }\r\n }, []);\r\n\r\n const startTimer = useCallback(\r\n (ms: number) => {\r\n clearTimer();\r\n intervalIdRef.current = setInterval(fire, ms);\r\n },\r\n [clearTimer, fire],\r\n );\r\n\r\n useEffect(() => {\r\n if (!enabled) {\r\n clearTimer();\r\n return;\r\n }\r\n\r\n const isHidden = (): boolean =>\r\n typeof document !== 'undefined' && document.visibilityState === 'hidden';\r\n\r\n const effectiveInterval = (): number => {\r\n if (!isHidden()) return interval;\r\n if (pauseOnBackground) return 0;\r\n return interval * backgroundSlowdownFactor;\r\n };\r\n\r\n const startingInterval = effectiveInterval();\r\n if (startingInterval > 0) {\r\n startTimer(startingInterval);\r\n }\r\n\r\n const onVisibilityChange = () => {\r\n if (document.visibilityState === 'visible') {\r\n const elapsed = Date.now() - lastFiredRef.current;\r\n if (elapsed >= interval) {\r\n fire();\r\n }\r\n startTimer(interval);\r\n } else {\r\n if (pauseOnBackground) {\r\n clearTimer();\r\n } else {\r\n startTimer(interval * backgroundSlowdownFactor);\r\n }\r\n }\r\n };\r\n\r\n if (typeof document !== 'undefined') {\r\n document.addEventListener('visibilitychange', onVisibilityChange);\r\n }\r\n\r\n return () => {\r\n clearTimer();\r\n if (typeof document !== 'undefined') {\r\n document.removeEventListener('visibilitychange', onVisibilityChange);\r\n }\r\n };\r\n }, [interval, enabled, pauseOnBackground, backgroundSlowdownFactor, startTimer, clearTimer, fire]);\r\n}\r\n"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ import {useRef,useState,useCallback,useEffect}from'react';var w=()=>typeof window<"u"&&typeof window.requestIdleCallback=="function";function x(i={}){let{timeout:d,fallbackInterval:r=50}=i,s=useRef([]),u=useRef(null),o=useRef(false),a=useRef(false),[f,n]=useState(0),e=useCallback(l=>{if(w()){let m=d!=null?{timeout:d}:{};u.current=window.requestIdleCallback(l,m);}else u.current=window.setTimeout(l,r);},[d,r]),t=useCallback(()=>{u.current!=null&&(w()?window.cancelIdleCallback(u.current):window.clearTimeout(u.current),u.current=null);},[]),c=useCallback(()=>{if(a.current||o.current)return;let l=s.current.shift();if(!l){o.current=false;return}o.current=true,n(s.current.length),(async()=>{try{await l();}catch(k){console.error("[useIdleQueue] Task threw an error:",k);}finally{o.current=false,!a.current&&s.current.length>0&&e(c);}})();},[e]),p=useCallback(l=>{a.current||(s.current.push(l),n(s.current.length),o.current||e(c));},[e,c]),y=useCallback(()=>{s.current=[],n(0),t(),o.current=false;},[t]);return useEffect(()=>(a.current=false,()=>{a.current=true,t(),s.current=[];}),[t]),{enqueue:p,clearQueue:y,queueLength:f}}function C(i,d){let[r,s]=useState(d),u=useRef(r);useEffect(()=>{u.current=r;},[r]);let o=useRef(null),a=useRef(false);useEffect(()=>{if(typeof BroadcastChannel>"u")return;let n=new BroadcastChannel(i);return o.current=n,n.onmessage=e=>{try{let t=typeof e.data=="string"?JSON.parse(e.data):e.data,c=JSON.stringify(u.current),p=JSON.stringify(t);c!==p&&(a.current=!0,s(t));}catch(t){console.error("[useBroadcastState] Failed to parse incoming message:",t);}},n.onmessageerror=e=>{console.error("[useBroadcastState] Message deserialization error:",e);},()=>{n.close(),o.current=null;}},[i]);let f=useCallback(n=>{let e=typeof n=="function"?n(u.current):n;if(u.current=e,s(e),o.current)try{o.current.postMessage(JSON.stringify(e));}catch(t){console.error("[useBroadcastState] Failed to broadcast state:",t);}},[]);return useEffect(()=>{a.current&&(a.current=false);},[r]),[r,f]}function U(i,d){let{interval:r,enabled:s=true,pauseOnBackground:u=true,backgroundSlowdownFactor:o=5}=d,a=useRef(i);useEffect(()=>{a.current=i;},[i]);let f=useRef(0),n=useRef(null),e=useCallback(()=>{f.current=Date.now(),a.current();},[]),t=useCallback(()=>{n.current!=null&&(clearInterval(n.current),n.current=null);},[]),c=useCallback(p=>{t(),n.current=setInterval(e,p);},[t,e]);useEffect(()=>{if(!s){t();return}let l=typeof document<"u"&&document.visibilityState==="hidden"?u?0:r*o:r;l>0&&c(l);let m=()=>{document.visibilityState==="visible"?(Date.now()-f.current>=r&&e(),c(r)):u?t():c(r*o);};return typeof document<"u"&&document.addEventListener("visibilitychange",m),()=>{t(),typeof document<"u"&&document.removeEventListener("visibilitychange",m);}},[r,s,u,o,c,t,e]);}export{U as useAdaptivePolling,C as useBroadcastState,x as useIdleQueue};//# sourceMappingURL=index.mjs.map
2
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hooks/useIdleQueue.ts","../src/hooks/useBroadcastState.ts","../src/hooks/useAdaptivePolling.ts"],"names":["hasIdleCallback","useIdleQueue","options","timeout","fallbackInterval","queueRef","useRef","handleRef","processingRef","unmountedRef","queueLength","setQueueLength","useState","schedule","useCallback","fn","opts","cancelScheduled","processQueue","task","error","enqueue","clearQueue","useEffect","useBroadcastState","channelName","initialValue","state","setState","stateRef","channelRef","isExternalUpdateRef","channel","event","incoming","current","next","setBroadcastState","value","nextValue","useAdaptivePolling","callback","interval","enabled","pauseOnBackground","backgroundSlowdownFactor","callbackRef","lastFiredRef","intervalIdRef","fire","clearTimer","startTimer","ms","startingInterval","onVisibilityChange"],"mappings":"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","file":"index.mjs","sourcesContent":["import { useState, useRef, useCallback, useEffect } from 'react';\r\n\r\nexport type IdleQueueTask = () => void | Promise<void>;\r\n\r\nexport interface UseIdleQueueOptions {\r\n timeout?: number;\r\n fallbackInterval?: number;\r\n}\r\n\r\nexport interface UseIdleQueueReturn {\r\n enqueue: (task: IdleQueueTask) => void;\r\n clearQueue: () => void;\r\n queueLength: number;\r\n}\r\n\r\nconst hasIdleCallback = (): boolean =>\r\n typeof window !== 'undefined' &&\r\n typeof window.requestIdleCallback === 'function';\r\n\r\nexport function useIdleQueue(options: UseIdleQueueOptions = {}): UseIdleQueueReturn {\r\n const { timeout, fallbackInterval = 50 } = options;\r\n\r\n const queueRef = useRef<IdleQueueTask[]>([]);\r\n const handleRef = useRef<number | null>(null);\r\n const processingRef = useRef(false);\r\n const unmountedRef = useRef(false);\r\n\r\n const [queueLength, setQueueLength] = useState(0);\r\n\r\n const schedule = useCallback(\r\n (fn: () => void) => {\r\n if (hasIdleCallback()) {\r\n const opts: IdleRequestOptions = timeout != null ? { timeout } : {};\r\n handleRef.current = window.requestIdleCallback(fn, opts);\r\n } else {\r\n handleRef.current = window.setTimeout(fn, fallbackInterval) as unknown as number;\r\n }\r\n },\r\n [timeout, fallbackInterval],\r\n );\r\n\r\n const cancelScheduled = useCallback(() => {\r\n if (handleRef.current == null) return;\r\n\r\n if (hasIdleCallback()) {\r\n window.cancelIdleCallback(handleRef.current);\r\n } else {\r\n window.clearTimeout(handleRef.current);\r\n }\r\n handleRef.current = null;\r\n }, []);\r\n\r\n const processQueue = useCallback(() => {\r\n if (unmountedRef.current || processingRef.current) return;\r\n\r\n const task = queueRef.current.shift();\r\n if (!task) {\r\n processingRef.current = false;\r\n return;\r\n }\r\n\r\n processingRef.current = true;\r\n setQueueLength(queueRef.current.length);\r\n\r\n const execute = async () => {\r\n try {\r\n await task();\r\n } catch (error) {\r\n console.error('[useIdleQueue] Task threw an error:', error);\r\n } finally {\r\n processingRef.current = false;\r\n if (!unmountedRef.current && queueRef.current.length > 0) {\r\n schedule(processQueue);\r\n }\r\n }\r\n };\r\n\r\n void execute();\r\n }, [schedule]);\r\n\r\n const enqueue = useCallback(\r\n (task: IdleQueueTask) => {\r\n if (unmountedRef.current) return;\r\n\r\n queueRef.current.push(task);\r\n setQueueLength(queueRef.current.length);\r\n\r\n if (!processingRef.current) {\r\n schedule(processQueue);\r\n }\r\n },\r\n [schedule, processQueue],\r\n );\r\n\r\n const clearQueue = useCallback(() => {\r\n queueRef.current = [];\r\n setQueueLength(0);\r\n cancelScheduled();\r\n processingRef.current = false;\r\n }, [cancelScheduled]);\r\n\r\n useEffect(() => {\r\n unmountedRef.current = false;\r\n\r\n return () => {\r\n unmountedRef.current = true;\r\n cancelScheduled();\r\n queueRef.current = [];\r\n };\r\n }, [cancelScheduled]);\r\n\r\n return { enqueue, clearQueue, queueLength };\r\n}\r\n","import { useState, useRef, useCallback, useEffect } from 'react';\r\n\r\nexport function useBroadcastState<T>(\r\n channelName: string,\r\n initialValue: T,\r\n): [T, (value: T | ((prevState: T) => T)) => void] {\r\n const [state, setState] = useState<T>(initialValue);\r\n\r\n const stateRef = useRef<T>(state);\r\n useEffect(() => {\r\n stateRef.current = state;\r\n }, [state]);\r\n\r\n const channelRef = useRef<BroadcastChannel | null>(null);\r\n const isExternalUpdateRef = useRef(false);\r\n\r\n useEffect(() => {\r\n if (typeof BroadcastChannel === 'undefined') return;\r\n\r\n const channel = new BroadcastChannel(channelName);\r\n channelRef.current = channel;\r\n\r\n channel.onmessage = (event: MessageEvent) => {\r\n try {\r\n const incoming: T =\r\n typeof event.data === 'string'\r\n ? (JSON.parse(event.data) as T)\r\n : (event.data as T);\r\n\r\n const current = JSON.stringify(stateRef.current);\r\n const next = JSON.stringify(incoming);\r\n\r\n if (current !== next) {\r\n isExternalUpdateRef.current = true;\r\n setState(incoming);\r\n }\r\n } catch (error) {\r\n console.error(\r\n '[useBroadcastState] Failed to parse incoming message:',\r\n error,\r\n );\r\n }\r\n };\r\n\r\n channel.onmessageerror = (event: MessageEvent) => {\r\n console.error(\r\n '[useBroadcastState] Message deserialization error:',\r\n event,\r\n );\r\n };\r\n\r\n return () => {\r\n channel.close();\r\n channelRef.current = null;\r\n };\r\n }, [channelName]);\r\n\r\n const setBroadcastState = useCallback(\r\n (value: T | ((prevState: T) => T)) => {\r\n const nextValue: T =\r\n typeof value === 'function'\r\n ? (value as (prevState: T) => T)(stateRef.current)\r\n : value;\r\n\r\n stateRef.current = nextValue;\r\n setState(nextValue);\r\n\r\n if (channelRef.current) {\r\n try {\r\n channelRef.current.postMessage(JSON.stringify(nextValue));\r\n } catch (error) {\r\n console.error(\r\n '[useBroadcastState] Failed to broadcast state:',\r\n error,\r\n );\r\n }\r\n }\r\n },\r\n [],\r\n );\r\n\r\n useEffect(() => {\r\n if (isExternalUpdateRef.current) {\r\n isExternalUpdateRef.current = false;\r\n }\r\n }, [state]);\r\n\r\n return [state, setBroadcastState];\r\n}\r\n","import { useEffect, useRef, useCallback } from 'react';\r\n\r\nexport interface UseAdaptivePollingOptions {\r\n interval: number;\r\n enabled?: boolean;\r\n pauseOnBackground?: boolean;\r\n backgroundSlowdownFactor?: number;\r\n}\r\n\r\nexport function useAdaptivePolling(\r\n callback: () => void,\r\n options: UseAdaptivePollingOptions,\r\n): void {\r\n const {\r\n interval,\r\n enabled = true,\r\n pauseOnBackground = true,\r\n backgroundSlowdownFactor = 5,\r\n } = options;\r\n\r\n const callbackRef = useRef(callback);\r\n useEffect(() => {\r\n callbackRef.current = callback;\r\n }, [callback]);\r\n\r\n const lastFiredRef = useRef<number>(0);\r\n const intervalIdRef = useRef<ReturnType<typeof setInterval> | null>(null);\r\n\r\n const fire = useCallback(() => {\r\n lastFiredRef.current = Date.now();\r\n callbackRef.current();\r\n }, []);\r\n\r\n const clearTimer = useCallback(() => {\r\n if (intervalIdRef.current != null) {\r\n clearInterval(intervalIdRef.current);\r\n intervalIdRef.current = null;\r\n }\r\n }, []);\r\n\r\n const startTimer = useCallback(\r\n (ms: number) => {\r\n clearTimer();\r\n intervalIdRef.current = setInterval(fire, ms);\r\n },\r\n [clearTimer, fire],\r\n );\r\n\r\n useEffect(() => {\r\n if (!enabled) {\r\n clearTimer();\r\n return;\r\n }\r\n\r\n const isHidden = (): boolean =>\r\n typeof document !== 'undefined' && document.visibilityState === 'hidden';\r\n\r\n const effectiveInterval = (): number => {\r\n if (!isHidden()) return interval;\r\n if (pauseOnBackground) return 0;\r\n return interval * backgroundSlowdownFactor;\r\n };\r\n\r\n const startingInterval = effectiveInterval();\r\n if (startingInterval > 0) {\r\n startTimer(startingInterval);\r\n }\r\n\r\n const onVisibilityChange = () => {\r\n if (document.visibilityState === 'visible') {\r\n const elapsed = Date.now() - lastFiredRef.current;\r\n if (elapsed >= interval) {\r\n fire();\r\n }\r\n startTimer(interval);\r\n } else {\r\n if (pauseOnBackground) {\r\n clearTimer();\r\n } else {\r\n startTimer(interval * backgroundSlowdownFactor);\r\n }\r\n }\r\n };\r\n\r\n if (typeof document !== 'undefined') {\r\n document.addEventListener('visibilitychange', onVisibilityChange);\r\n }\r\n\r\n return () => {\r\n clearTimer();\r\n if (typeof document !== 'undefined') {\r\n document.removeEventListener('visibilitychange', onVisibilityChange);\r\n }\r\n };\r\n }, [interval, enabled, pauseOnBackground, backgroundSlowdownFactor, startTimer, clearTimer, fire]);\r\n}\r\n"]}
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "use-web-kit",
3
+ "version": "0.1.0",
4
+ "description": "A highly optimized, zero-dependency, lightweight npm library for React custom hooks focusing on performance and Browser APIs.",
5
+ "main": "dist/index.cjs",
6
+ "module": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "require": {
15
+ "types": "./dist/index.d.cts",
16
+ "default": "./dist/index.cjs"
17
+ }
18
+ }
19
+ },
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "scripts": {
24
+ "build": "tsup",
25
+ "name": "use-web-kit",
26
+ "test:watch": "jest --watch",
27
+ "lint": "tsc --noEmit",
28
+ "prepublishOnly": "npm run build"
29
+ },
30
+ "keywords": [
31
+ "react",
32
+ "hooks",
33
+ "custom-hooks",
34
+ "useIdleQueue",
35
+ "useBroadcastState",
36
+ "useAdaptivePolling",
37
+ "requestIdleCallback",
38
+ "BroadcastChannel",
39
+ "Page Visibility API",
40
+ "polling",
41
+ "typescript"
42
+ ],
43
+ "author": "Tanush Bhootra <https://github.com/tanushbhootra576>",
44
+ "license": "MIT",
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/tanushbhootra576/use-web-kit.git"
48
+ },
49
+ "bugs": {
50
+ "url": "https://github.com/tanushbhootra576/use-web-kit/issues"
51
+ },
52
+ "homepage": "https://github.com/tanushbhootra576/use-web-kit#readme",
53
+ "peerDependencies": {
54
+ "react": ">=16.8.0"
55
+ },
56
+ "devDependencies": {
57
+ "@testing-library/react": "^16.1.0",
58
+ "@testing-library/jest-dom": "^6.6.3",
59
+ "@types/jest": "^29.5.14",
60
+ "@types/react": "^19.0.0",
61
+ "@types/react-dom": "^19.0.0",
62
+ "jest": "^29.7.0",
63
+ "jest-environment-jsdom": "^29.7.0",
64
+ "react": "^19.0.0",
65
+ "react-dom": "^19.0.0",
66
+ "ts-jest": "^29.2.5",
67
+ "tsup": "^8.3.5",
68
+ "typescript": "^5.7.0"
69
+ }
70
+ }