react-visibility-hooks 1.0.11 â 2.0.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/LICENSE +21 -0
- package/README.md +23 -10
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +38 -6
- package/dist/index.d.ts +38 -6
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +48 -8
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Aniket Raj
|
|
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,19 +1,23 @@
|
|
|
1
1
|
# react-visibility-hooks
|
|
2
2
|
|
|
3
|
-
> Tiny, SSR-safe React hooks for page visibility, idle detection and
|
|
3
|
+
> Tiny, SSR-safe React hooks for page visibility, idle detection, smart polling and auto-pause video
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/react-visibility-hooks)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://bundlephobia.com/package/react-visibility-hooks)
|
|
8
|
+
[](https://github.com/exewhyz/react-visibility-hooks/actions/workflows/ci.yml)
|
|
7
9
|
|
|
8
|
-
A collection of lightweight React hooks that help you build performance-conscious applications by detecting page visibility, user idle state, and managing smart polling strategies.
|
|
10
|
+
A collection of lightweight React hooks that help you build performance-conscious applications by detecting page visibility, user idle state, auto-pausing video, and managing smart polling strategies.
|
|
9
11
|
|
|
10
12
|
## Features
|
|
11
13
|
|
|
12
|
-
- ðŠķ **Lightweight** -
|
|
13
|
-
- ð **SSR-safe** - Works seamlessly with Next.js, Remix, and other SSR frameworks
|
|
14
|
+
- ðŠķ **Lightweight** - ~822 B brotlied, zero dependencies
|
|
15
|
+
- ð **SSR-safe** - Works seamlessly with Next.js (App Router & Pages Router), Remix, and other SSR frameworks
|
|
14
16
|
- ðĶ **Tree-shakeable** - Import only what you need
|
|
15
|
-
- ðŊ **TypeScript** - Fully typed with
|
|
17
|
+
- ðŊ **TypeScript** - Fully typed with exported interfaces
|
|
16
18
|
- ⥠**Performance-focused** - Pause expensive operations when users aren't looking
|
|
19
|
+
- ðŽ **Auto-pause video** - Pause/resume `<video>` elements on tab visibility
|
|
20
|
+
- ð **Smart polling** - Visibility-aware data fetching with dedup
|
|
17
21
|
|
|
18
22
|
## Installation
|
|
19
23
|
|
|
@@ -28,6 +32,7 @@ npm install react-visibility-hooks
|
|
|
28
32
|
Detect when the browser tab is visible or hidden using the [Page Visibility API](https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API).
|
|
29
33
|
|
|
30
34
|
**Use cases:**
|
|
35
|
+
|
|
31
36
|
- Pause animations when tab is hidden
|
|
32
37
|
- Stop fetching data when user isn't viewing the page
|
|
33
38
|
- Save battery and resources
|
|
@@ -48,9 +53,10 @@ function MyComponent() {
|
|
|
48
53
|
|
|
49
54
|
### `useIdleVisibility`
|
|
50
55
|
|
|
51
|
-
Detect when the user is idle (no mouse
|
|
56
|
+
Detect when the user is idle (no mouse, keyboard, pointer, scroll, or touch activity) combined with page visibility state.
|
|
52
57
|
|
|
53
58
|
**Use cases:**
|
|
59
|
+
|
|
54
60
|
- Show "Are you still there?" prompts
|
|
55
61
|
- Auto-pause media after inactivity
|
|
56
62
|
- Reduce background activity when user is away
|
|
@@ -71,13 +77,15 @@ function MyComponent() {
|
|
|
71
77
|
```
|
|
72
78
|
|
|
73
79
|
**Parameters:**
|
|
80
|
+
|
|
74
81
|
- `timeout` (optional): Milliseconds of inactivity before user is considered idle (default: 60000)
|
|
75
82
|
|
|
76
83
|
### `useAutoPauseVideo`
|
|
77
84
|
|
|
78
|
-
Automatically pause and resume videos based on page visibility.
|
|
85
|
+
Automatically pause and resume videos based on page visibility. Only resumes playback if the video was actually playing before the tab was hidden â it won't override a user's manual pause.
|
|
79
86
|
|
|
80
87
|
**Use cases:**
|
|
88
|
+
|
|
81
89
|
- Auto-pause videos when user switches tabs
|
|
82
90
|
- Better UX for video-heavy applications
|
|
83
91
|
- Save bandwidth and resources
|
|
@@ -101,6 +109,7 @@ function VideoPlayer() {
|
|
|
101
109
|
Visibility-aware polling built with plain React â no external dependencies. Automatically pauses when the tab is hidden, resumes when visible, and skips re-renders when data hasn't changed.
|
|
102
110
|
|
|
103
111
|
**Use cases:**
|
|
112
|
+
|
|
104
113
|
- Real-time dashboards
|
|
105
114
|
- Live notifications
|
|
106
115
|
- Auto-refresh data feeds
|
|
@@ -142,9 +151,10 @@ function Dashboard() {
|
|
|
142
151
|
| `isLoading` | `boolean` | `true` until the first fetch completes |
|
|
143
152
|
| `isError` | `boolean` | `true` if the last fetch threw |
|
|
144
153
|
| `error` | `Error \| undefined` | The error object, if any |
|
|
145
|
-
| `refetch` | `() => void
|
|
154
|
+
| `refetch` | `() => Promise<void>` | Manually trigger a fetch |
|
|
146
155
|
|
|
147
156
|
**Optimizations:**
|
|
157
|
+
|
|
148
158
|
- Skips re-renders when polled data is identical to the previous result
|
|
149
159
|
- Prevents concurrent fetches from overlapping
|
|
150
160
|
- Polling pauses automatically when the tab is hidden and resumes when visible
|
|
@@ -152,6 +162,7 @@ function Dashboard() {
|
|
|
152
162
|
## SSR Support
|
|
153
163
|
|
|
154
164
|
All hooks are SSR-safe and will work correctly with:
|
|
165
|
+
|
|
155
166
|
- Next.js (App Router & Pages Router)
|
|
156
167
|
- Remix
|
|
157
168
|
- Gatsby
|
|
@@ -166,8 +177,9 @@ This package is written in TypeScript and includes type definitions out of the b
|
|
|
166
177
|
## Browser Support
|
|
167
178
|
|
|
168
179
|
Works in all modern browsers that support:
|
|
180
|
+
|
|
169
181
|
- [Page Visibility API](https://caniuse.com/pagevisibility)
|
|
170
|
-
- React
|
|
182
|
+
- React >=16.8.0 (hooks)
|
|
171
183
|
|
|
172
184
|
## Examples
|
|
173
185
|
|
|
@@ -215,7 +227,7 @@ function IdleWarning() {
|
|
|
215
227
|
|
|
216
228
|
## Contributing
|
|
217
229
|
|
|
218
|
-
Contributions are welcome! Please
|
|
230
|
+
Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) before submitting a Pull Request.
|
|
219
231
|
|
|
220
232
|
## License
|
|
221
233
|
|
|
@@ -226,3 +238,4 @@ MIT ÂĐ [Aniket Raj](https://github.com/exewhyz)
|
|
|
226
238
|
- [GitHub Repository](https://github.com/exewhyz/react-visibility-hooks)
|
|
227
239
|
- [Issue Tracker](https://github.com/exewhyz/react-visibility-hooks/issues)
|
|
228
240
|
- [NPM Package](https://www.npmjs.com/package/react-visibility-hooks)
|
|
241
|
+
- [Changelog](CHANGELOG.md)
|
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
'use strict';var react=require('react');function u(){let[r,e]=react.useState(()=>typeof document=="undefined"?true:document.visibilityState==="visible"),t=react.useCallback(()=>{e(document.visibilityState==="visible");},[]);return react.useEffect(()=>{if(typeof document!="undefined")return t(),document.addEventListener("visibilitychange",t),()=>document.removeEventListener("visibilitychange",t)},[t]),r}function O(r=6e4){let e=u(),[t,n]=react.useState(false),s=react.useRef(void 0),o=react.useCallback(()=>{n(false),clearTimeout(s.current),s.current=setTimeout(()=>n(true),r);},[r]);return react.useEffect(()=>{if(typeof window=="undefined")return;let m=["mousemove","keydown","pointerdown","scroll","touchstart"];return m.forEach(f=>window.addEventListener(f,o,{passive:true})),o(),()=>{m.forEach(f=>window.removeEventListener(f,o)),clearTimeout(s.current);}},[o]),react.useEffect(()=>{e&&o();},[e,o]),{visible:e,idle:t}}function j(r){let e=u(),t=react.useRef(false);react.useEffect(()=>{let n=r.current;n&&(!e&&!n.paused?(n.pause(),t.current=true):e&&t.current&&(n.play().catch(()=>{}),t.current=false));},[e,r]);}function M(r,e){let{interval:t=5e3,enabled:n=true}=e!=null?e:{},s=u(),[o,m]=react.useState(void 0),[f,h]=react.useState(true),[y,R]=react.useState(void 0),E=react.useRef(r),a=react.useRef(void 0),b=react.useRef(false),g=react.useRef(void 0),d=react.useRef(true);E.current=r,react.useEffect(()=>(d.current=true,()=>{d.current=false;}),[]);let l=react.useCallback(async()=>{if(!b.current){b.current=true;try{let i=await E.current();if(!d.current)return;let V=JSON.stringify(i);V!==g.current&&(g.current=V,m(i)),R(x=>x!==void 0?void 0:x);}catch(i){if(!d.current)return;R(i instanceof Error?i:new Error(String(i)));}finally{b.current=false,d.current&&h(i=>i&&false);}}},[]),S=react.useRef(false);return react.useEffect(()=>{S.current||(S.current=true,l());},[l]),react.useEffect(()=>{if(!n||!s){clearInterval(a.current),a.current=void 0;return}return l(),a.current=setInterval(l,t),()=>{clearInterval(a.current),a.current=void 0;}},[s,n,t,l]),{data:o,isLoading:f,isError:y!==void 0,error:y,refetch:l}}exports.useAutoPauseVideo=j;exports.useDocVisible=u;exports.useIdleVisibility=O;exports.useSmartPolling=M;//# sourceMappingURL=index.cjs.map
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/useDocVisible.ts","../src/useIdleVisibility.ts","../src/useAutoPauseVideo.ts","../src/useSmartPolling.ts"],"sourcesContent":["export * from \"./useDocVisible\";\nexport * from \"./useIdleVisibility\";\nexport * from \"./useAutoPauseVideo\";\nexport * from \"./useSmartPolling\";\n","import { useEffect, useState } from \"react\";\n\nexport function useDocVisible(): boolean {\n const [visible, setVisible] = useState(\n typeof document === \"undefined\" || document.visibilityState === \"visible\"\n );\n\n useEffect(() => {\n if (typeof document === \"undefined\") return;\n const handler = () => setVisible(document.visibilityState === \"visible\");\n document.addEventListener(\"visibilitychange\", handler);\n return () => document.removeEventListener(\"visibilitychange\", handler);\n }, []);\n\n return visible;\n}\n","import { useEffect, useState } from \"react\";\nimport { useDocVisible } from \"./useDocVisible\";\n\nexport function useIdleVisibility(timeout = 60000) {\n const visible = useDocVisible();\n const [idle, setIdle] = useState(false);\n\n useEffect(() => {\n let timer: any;\n\n const reset = () => {\n setIdle(false);\n clearTimeout(timer);\n timer = setTimeout(() => setIdle(true), timeout);\n };\n\n window.addEventListener(\"mousemove\", reset);\n window.addEventListener(\"keydown\", reset);\n reset();\n\n return () => {\n window.removeEventListener(\"mousemove\", reset);\n window.removeEventListener(\"keydown\", reset);\n clearTimeout(timer);\n };\n }, [timeout]);\n\n return { visible, idle };\n}\n","import { useEffect } from \"react\";\nimport { useDocVisible } from \"./useDocVisible\";\n\nexport function useAutoPauseVideo(ref: React.RefObject<HTMLVideoElement>) {\n const visible = useDocVisible();\n\n useEffect(() => {\n const video = ref.current;\n if (!video) return;\n\n if (!visible) video.pause();\n else video.play().catch(() => {});\n }, [visible, ref]);\n}\n","import { useState, useEffect, useRef, useCallback } from \"react\";\nimport { useDocVisible } from \"./useDocVisible\";\n\ninterface SmartPollingOptions {\n interval?: number;\n enabled?: boolean;\n}\n\ninterface SmartPollingResult<T> {\n data: T | undefined;\n isLoading: boolean;\n isError: boolean;\n error: Error | undefined;\n refetch: () => void;\n}\n\nexport function useSmartPolling<T = any>(\n fetchFn: () => Promise<T>,\n options?: SmartPollingOptions,\n): SmartPollingResult<T> {\n const { interval = 5000, enabled = true } = options ?? {};\n const visible = useDocVisible();\n const [data, setData] = useState<T | undefined>(undefined);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | undefined>(undefined);\n const fetchRef = useRef(fetchFn);\n const timerRef = useRef<ReturnType<typeof setInterval> | undefined>(\n undefined,\n );\n const isFetchingRef = useRef(false);\n const lastJsonRef = useRef<string | undefined>(undefined);\n\n fetchRef.current = fetchFn;\n\n const execute = useCallback(async () => {\n // Prevent concurrent fetches from overlapping intervals\n if (isFetchingRef.current) return;\n isFetchingRef.current = true;\n\n try {\n const result = await fetchRef.current();\n\n // Only update state if data actually changed (avoids unnecessary re-renders)\n const json = JSON.stringify(result);\n if (json !== lastJsonRef.current) {\n lastJsonRef.current = json;\n setData(result);\n }\n setError((prev) => (prev !== undefined ? undefined : prev));\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n isFetchingRef.current = false;\n setIsLoading((prev) => (prev ? false : prev));\n }\n }, []);\n\n // Initial fetch â always runs once regardless of `enabled`\n const hasFetchedRef = useRef(false);\n useEffect(() => {\n if (hasFetchedRef.current) return;\n hasFetchedRef.current = true;\n execute();\n }, [execute]);\n\n // Polling â only when visible and enabled\n useEffect(() => {\n if (!enabled || !visible) {\n clearInterval(timerRef.current);\n return;\n }\n\n timerRef.current = setInterval(execute, interval);\n return () => clearInterval(timerRef.current);\n }, [visible, enabled, interval, execute]);\n\n return { data, isLoading, isError: !!error, error, refetch: execute };\n}\n"],"mappings":"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","names":["index_exports","__export","useAutoPauseVideo","useDocVisible","useIdleVisibility","useSmartPolling","__toCommonJS","import_react","useDocVisible","visible","setVisible","handler","import_react","useIdleVisibility","timeout","visible","useDocVisible","idle","setIdle","timer","reset","import_react","useAutoPauseVideo","ref","visible","useDocVisible","video","import_react","useSmartPolling","fetchFn","options","interval","enabled","visible","useDocVisible","data","setData","isLoading","setIsLoading","error","setError","fetchRef","timerRef","isFetchingRef","lastJsonRef","execute","result","json","prev","err","hasFetchedRef"]}
|
|
1
|
+
{"version":3,"sources":["../src/useDocVisible.ts","../src/useIdleVisibility.ts","../src/useAutoPauseVideo.ts","../src/useSmartPolling.ts"],"names":["useDocVisible","visible","setVisible","useState","handler","useCallback","useEffect","useIdleVisibility","timeout","idle","setIdle","timerRef","useRef","reset","events","e","useAutoPauseVideo","ref","wasPausedByUs","video","useSmartPolling","fetchFn","options","interval","enabled","data","setData","isLoading","setIsLoading","error","setError","fetchRef","isFetchingRef","lastJsonRef","mountedRef","execute","result","json","prev","err","hasFetchedRef"],"mappings":"wCAMO,SAASA,CAAAA,EAAyB,CACvC,GAAM,CAACC,CAAAA,CAASC,CAAU,CAAA,CAAIC,cAAAA,CAAS,IACrC,OAAO,QAAA,EAAa,WAAA,CAAc,IAAA,CAAO,QAAA,CAAS,eAAA,GAAoB,SACxE,CAAA,CAEMC,CAAAA,CAAUC,iBAAAA,CAAY,IAAM,CAChCH,CAAAA,CAAW,QAAA,CAAS,eAAA,GAAoB,SAAS,EACnD,CAAA,CAAG,EAAE,CAAA,CAEL,OAAAI,eAAAA,CAAU,IAAM,CACd,GAAI,OAAO,QAAA,EAAa,WAAA,CAGxB,OAAAF,CAAAA,EAAQ,CAER,QAAA,CAAS,gBAAA,CAAiB,kBAAA,CAAoBA,CAAO,CAAA,CAC9C,IAAM,QAAA,CAAS,mBAAA,CAAoB,kBAAA,CAAoBA,CAAO,CACvE,CAAA,CAAG,CAACA,CAAO,CAAC,CAAA,CAELH,CACT,CCVO,SAASM,CAAAA,CAAkBC,CAAAA,CAAU,GAAA,CAA8B,CACxE,IAAMP,CAAAA,CAAUD,CAAAA,EAAc,CACxB,CAACS,CAAAA,CAAMC,CAAO,CAAA,CAAIP,cAAAA,CAAS,KAAK,CAAA,CAChCQ,CAAAA,CAAWC,YAAAA,CAAkD,MAAS,CAAA,CAEtEC,CAAAA,CAAQR,iBAAAA,CAAY,IAAM,CAC9BK,CAAAA,CAAQ,KAAK,CAAA,CACb,YAAA,CAAaC,CAAAA,CAAS,OAAO,CAAA,CAC7BA,CAAAA,CAAS,OAAA,CAAU,UAAA,CAAW,IAAMD,CAAAA,CAAQ,IAAI,CAAA,CAAGF,CAAO,EAC5D,CAAA,CAAG,CAACA,CAAO,CAAC,CAAA,CAEZ,OAAAF,eAAAA,CAAU,IAAM,CACd,GAAI,OAAO,MAAA,EAAW,WAAA,CAAa,OAEnC,IAAMQ,CAAAA,CAAsC,CAC1C,WAAA,CACA,SAAA,CACA,aAAA,CACA,QAAA,CACA,YACF,CAAA,CAEA,OAAAA,CAAAA,CAAO,OAAA,CAASC,CAAAA,EAAM,MAAA,CAAO,gBAAA,CAAiBA,CAAAA,CAAGF,CAAAA,CAAO,CAAE,OAAA,CAAS,IAAK,CAAC,CAAC,CAAA,CAC1EA,CAAAA,EAAM,CAEC,IAAM,CACXC,CAAAA,CAAO,OAAA,CAASC,CAAAA,EAAM,MAAA,CAAO,mBAAA,CAAoBA,CAAAA,CAAGF,CAAK,CAAC,CAAA,CAC1D,YAAA,CAAaF,CAAAA,CAAS,OAAO,EAC/B,CACF,CAAA,CAAG,CAACE,CAAK,CAAC,CAAA,CAGVP,eAAAA,CAAU,IAAM,CACVL,CAAAA,EAASY,CAAAA,GACf,CAAA,CAAG,CAACZ,CAAAA,CAASY,CAAK,CAAC,CAAA,CAEZ,CAAE,OAAA,CAAAZ,CAAAA,CAAS,IAAA,CAAAQ,CAAK,CACzB,CC5CO,SAASO,CAAAA,CAAkBC,CAAAA,CAAqD,CACrF,IAAMhB,CAAAA,CAAUD,CAAAA,EAAc,CACxBkB,CAAAA,CAAgBN,YAAAA,CAAO,KAAK,CAAA,CAElCN,eAAAA,CAAU,IAAM,CACd,IAAMa,CAAAA,CAAQF,CAAAA,CAAI,OAAA,CACbE,CAAAA,GAED,CAAClB,CAAAA,EAAW,CAACkB,CAAAA,CAAM,MAAA,EACrBA,CAAAA,CAAM,KAAA,EAAM,CACZD,CAAAA,CAAc,OAAA,CAAU,IAAA,EACfjB,CAAAA,EAAWiB,CAAAA,CAAc,OAAA,GAClCC,CAAAA,CAAM,IAAA,EAAK,CAAE,KAAA,CAAM,IAAM,CAEzB,CAAC,CAAA,CACDD,CAAAA,CAAc,OAAA,CAAU,KAAA,CAAA,EAE5B,CAAA,CAAG,CAACjB,CAAAA,CAASgB,CAAG,CAAC,EACnB,CCEO,SAASG,CAAAA,CACdC,CAAAA,CACAC,CAAAA,CACuB,CACvB,GAAM,CAAE,QAAA,CAAAC,CAAAA,CAAW,GAAA,CAAM,OAAA,CAAAC,CAAAA,CAAU,IAAK,CAAA,CAAIF,CAAAA,EAAA,IAAA,CAAAA,CAAAA,CAAW,EAAC,CAClDrB,CAAAA,CAAUD,CAAAA,EAAc,CAExB,CAACyB,CAAAA,CAAMC,CAAO,CAAA,CAAIvB,cAAAA,CAAwB,MAAS,CAAA,CACnD,CAACwB,CAAAA,CAAWC,CAAY,CAAA,CAAIzB,cAAAA,CAAS,IAAI,CAAA,CACzC,CAAC0B,CAAAA,CAAOC,CAAQ,CAAA,CAAI3B,cAAAA,CAA4B,MAAS,CAAA,CAEzD4B,CAAAA,CAAWnB,YAAAA,CAAOS,CAAO,CAAA,CACzBV,CAAAA,CAAWC,YAAAA,CAAmD,MAAS,CAAA,CACvEoB,CAAAA,CAAgBpB,YAAAA,CAAO,KAAK,CAAA,CAC5BqB,CAAAA,CAAcrB,YAAAA,CAA2B,MAAS,CAAA,CAClDsB,CAAAA,CAAatB,YAAAA,CAAO,IAAI,CAAA,CAG9BmB,CAAAA,CAAS,OAAA,CAAUV,CAAAA,CAEnBf,eAAAA,CAAU,KACR4B,CAAAA,CAAW,OAAA,CAAU,IAAA,CACd,IAAM,CACXA,CAAAA,CAAW,OAAA,CAAU,MACvB,CAAA,CAAA,CACC,EAAE,CAAA,CAEL,IAAMC,CAAAA,CAAU9B,iBAAAA,CAAY,SAAY,CACtC,GAAI,CAAA2B,CAAAA,CAAc,OAAA,CAClB,CAAAA,CAAAA,CAAc,OAAA,CAAU,IAAA,CAExB,GAAI,CACF,IAAMI,CAAAA,CAAS,MAAML,CAAAA,CAAS,OAAA,EAAQ,CACtC,GAAI,CAACG,CAAAA,CAAW,OAAA,CAAS,OAEzB,IAAMG,CAAAA,CAAO,IAAA,CAAK,SAAA,CAAUD,CAAM,CAAA,CAC9BC,CAAAA,GAASJ,CAAAA,CAAY,OAAA,GACvBA,CAAAA,CAAY,OAAA,CAAUI,CAAAA,CACtBX,CAAAA,CAAQU,CAAM,CAAA,CAAA,CAEhBN,CAAAA,CAAUQ,CAAAA,EAAUA,CAAAA,GAAS,KAAA,CAAA,CAAY,KAAA,CAAA,CAAYA,CAAK,EAC5D,CAAA,MAASC,CAAAA,CAAK,CACZ,GAAI,CAACL,CAAAA,CAAW,OAAA,CAAS,OACzBJ,CAAAA,CAASS,CAAAA,YAAe,KAAA,CAAQA,CAAAA,CAAM,IAAI,KAAA,CAAM,MAAA,CAAOA,CAAG,CAAC,CAAC,EAC9D,CAAA,OAAE,CACAP,CAAAA,CAAc,OAAA,CAAU,KAAA,CACpBE,CAAAA,CAAW,OAAA,EACbN,CAAAA,CAAcU,CAAAA,EAAUA,CAAAA,EAAO,KAAa,EAEhD,CAAA,CACF,CAAA,CAAG,EAAE,CAAA,CAGCE,CAAAA,CAAgB5B,YAAAA,CAAO,KAAK,CAAA,CAClC,OAAAN,eAAAA,CAAU,IAAM,CACVkC,CAAAA,CAAc,OAAA,GAClBA,CAAAA,CAAc,OAAA,CAAU,IAAA,CACxBL,CAAAA,EAAQ,EACV,CAAA,CAAG,CAACA,CAAO,CAAC,CAAA,CAGZ7B,eAAAA,CAAU,IAAM,CACd,GAAI,CAACkB,CAAAA,EAAW,CAACvB,CAAAA,CAAS,CACxB,aAAA,CAAcU,CAAAA,CAAS,OAAO,CAAA,CAC9BA,CAAAA,CAAS,OAAA,CAAU,MAAA,CACnB,MACF,CAGA,OAAAwB,CAAAA,EAAQ,CAERxB,CAAAA,CAAS,OAAA,CAAU,WAAA,CAAYwB,CAAAA,CAASZ,CAAQ,CAAA,CACzC,IAAM,CACX,aAAA,CAAcZ,CAAAA,CAAS,OAAO,CAAA,CAC9BA,CAAAA,CAAS,OAAA,CAAU,OACrB,CACF,CAAA,CAAG,CAACV,CAAAA,CAASuB,CAAAA,CAASD,CAAAA,CAAUY,CAAO,CAAC,CAAA,CAEjC,CACL,IAAA,CAAAV,CAAAA,CACA,SAAA,CAAAE,CAAAA,CACA,OAAA,CAASE,CAAAA,GAAU,MAAA,CACnB,KAAA,CAAAA,CAAAA,CACA,OAAA,CAASM,CACX,CACF","file":"index.cjs","sourcesContent":["import { useCallback, useEffect, useState } from \"react\";\n\n/**\n * Returns `true` when the page is visible, `false` when hidden.\n * SSR-safe â defaults to `true` on the server.\n */\nexport function useDocVisible(): boolean {\n const [visible, setVisible] = useState(() =>\n typeof document === \"undefined\" ? true : document.visibilityState === \"visible\",\n );\n\n const handler = useCallback(() => {\n setVisible(document.visibilityState === \"visible\");\n }, []);\n\n useEffect(() => {\n if (typeof document === \"undefined\") return;\n\n // Sync in case value changed between SSR hydration and effect\n handler();\n\n document.addEventListener(\"visibilitychange\", handler);\n return () => document.removeEventListener(\"visibilitychange\", handler);\n }, [handler]);\n\n return visible;\n}\n","import { useEffect, useRef, useState, useCallback } from \"react\";\nimport { useDocVisible } from \"./useDocVisible\";\n\nexport interface IdleVisibilityResult {\n /** Whether the page is visible */\n visible: boolean;\n /** Whether the user is idle (no interaction for `timeout` ms) */\n idle: boolean;\n}\n\n/**\n * Combines page-visibility with idle detection.\n *\n * @param timeout - Milliseconds of inactivity before the user is\n * considered idle (default `60_000`).\n */\nexport function useIdleVisibility(timeout = 60_000): IdleVisibilityResult {\n const visible = useDocVisible();\n const [idle, setIdle] = useState(false);\n const timerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n\n const reset = useCallback(() => {\n setIdle(false);\n clearTimeout(timerRef.current);\n timerRef.current = setTimeout(() => setIdle(true), timeout);\n }, [timeout]);\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n\n const events: Array<keyof WindowEventMap> = [\n \"mousemove\",\n \"keydown\",\n \"pointerdown\",\n \"scroll\",\n \"touchstart\",\n ];\n\n events.forEach((e) => window.addEventListener(e, reset, { passive: true }));\n reset();\n\n return () => {\n events.forEach((e) => window.removeEventListener(e, reset));\n clearTimeout(timerRef.current);\n };\n }, [reset]);\n\n // Reset idle timer when tab becomes visible again\n useEffect(() => {\n if (visible) reset();\n }, [visible, reset]);\n\n return { visible, idle };\n}\n","import { useEffect, useRef } from \"react\";\nimport { useDocVisible } from \"./useDocVisible\";\n\n/**\n * Automatically pauses a `<video>` when the page becomes hidden\n * and resumes playback when the page becomes visible again.\n *\n * Only resumes if the video was playing before it was paused by this hook.\n */\nexport function useAutoPauseVideo(ref: React.RefObject<HTMLVideoElement | null>): void {\n const visible = useDocVisible();\n const wasPausedByUs = useRef(false);\n\n useEffect(() => {\n const video = ref.current;\n if (!video) return;\n\n if (!visible && !video.paused) {\n video.pause();\n wasPausedByUs.current = true;\n } else if (visible && wasPausedByUs.current) {\n video.play().catch(() => {\n /* autoplay may be blocked */\n });\n wasPausedByUs.current = false;\n }\n }, [visible, ref]);\n}\n","import { useState, useEffect, useRef, useCallback } from \"react\";\nimport { useDocVisible } from \"./useDocVisible\";\n\nexport interface SmartPollingOptions {\n /** Polling interval in ms (default `5000`) */\n interval?: number;\n /** Enable / disable polling (default `true`). The initial fetch always fires. */\n enabled?: boolean;\n}\n\nexport interface SmartPollingResult<T> {\n data: T | undefined;\n isLoading: boolean;\n isError: boolean;\n error: Error | undefined;\n /** Manually trigger a fetch */\n refetch: () => Promise<void>;\n}\n\n/**\n * Visibility-aware polling hook.\n *\n * - Pauses when the tab is hidden.\n * - Skips re-renders when data hasn't changed (shallow JSON comparison).\n * - Prevents overlapping fetches.\n *\n * @param fetchFn - Async function that returns data.\n * @param options - Optional configuration.\n */\nexport function useSmartPolling<T = unknown>(\n fetchFn: () => Promise<T>,\n options?: SmartPollingOptions,\n): SmartPollingResult<T> {\n const { interval = 5000, enabled = true } = options ?? {};\n const visible = useDocVisible();\n\n const [data, setData] = useState<T | undefined>(undefined);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | undefined>(undefined);\n\n const fetchRef = useRef(fetchFn);\n const timerRef = useRef<ReturnType<typeof setInterval> | undefined>(undefined);\n const isFetchingRef = useRef(false);\n const lastJsonRef = useRef<string | undefined>(undefined);\n const mountedRef = useRef(true);\n\n // Always keep the latest fetchFn\n fetchRef.current = fetchFn;\n\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n const execute = useCallback(async () => {\n if (isFetchingRef.current) return;\n isFetchingRef.current = true;\n\n try {\n const result = await fetchRef.current();\n if (!mountedRef.current) return;\n\n const json = JSON.stringify(result);\n if (json !== lastJsonRef.current) {\n lastJsonRef.current = json;\n setData(result);\n }\n setError((prev) => (prev !== undefined ? undefined : prev));\n } catch (err) {\n if (!mountedRef.current) return;\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n isFetchingRef.current = false;\n if (mountedRef.current) {\n setIsLoading((prev) => (prev ? false : prev));\n }\n }\n }, []);\n\n // Initial fetch â always runs once regardless of `enabled`\n const hasFetchedRef = useRef(false);\n useEffect(() => {\n if (hasFetchedRef.current) return;\n hasFetchedRef.current = true;\n execute();\n }, [execute]);\n\n // Polling â only when visible AND enabled\n useEffect(() => {\n if (!enabled || !visible) {\n clearInterval(timerRef.current);\n timerRef.current = undefined;\n return;\n }\n\n // Immediately fetch when re-enabled / tab returns\n execute();\n\n timerRef.current = setInterval(execute, interval);\n return () => {\n clearInterval(timerRef.current);\n timerRef.current = undefined;\n };\n }, [visible, enabled, interval, execute]);\n\n return {\n data,\n isLoading,\n isError: error !== undefined,\n error,\n refetch: execute,\n };\n}\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,14 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns `true` when the page is visible, `false` when hidden.
|
|
3
|
+
* SSR-safe â defaults to `true` on the server.
|
|
4
|
+
*/
|
|
1
5
|
declare function useDocVisible(): boolean;
|
|
2
6
|
|
|
3
|
-
|
|
7
|
+
interface IdleVisibilityResult {
|
|
8
|
+
/** Whether the page is visible */
|
|
4
9
|
visible: boolean;
|
|
10
|
+
/** Whether the user is idle (no interaction for `timeout` ms) */
|
|
5
11
|
idle: boolean;
|
|
6
|
-
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Combines page-visibility with idle detection.
|
|
15
|
+
*
|
|
16
|
+
* @param timeout - Milliseconds of inactivity before the user is
|
|
17
|
+
* considered idle (default `60_000`).
|
|
18
|
+
*/
|
|
19
|
+
declare function useIdleVisibility(timeout?: number): IdleVisibilityResult;
|
|
7
20
|
|
|
8
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Automatically pauses a `<video>` when the page becomes hidden
|
|
23
|
+
* and resumes playback when the page becomes visible again.
|
|
24
|
+
*
|
|
25
|
+
* Only resumes if the video was playing before it was paused by this hook.
|
|
26
|
+
*/
|
|
27
|
+
declare function useAutoPauseVideo(ref: React.RefObject<HTMLVideoElement | null>): void;
|
|
9
28
|
|
|
10
29
|
interface SmartPollingOptions {
|
|
30
|
+
/** Polling interval in ms (default `5000`) */
|
|
11
31
|
interval?: number;
|
|
32
|
+
/** Enable / disable polling (default `true`). The initial fetch always fires. */
|
|
12
33
|
enabled?: boolean;
|
|
13
34
|
}
|
|
14
35
|
interface SmartPollingResult<T> {
|
|
@@ -16,8 +37,19 @@ interface SmartPollingResult<T> {
|
|
|
16
37
|
isLoading: boolean;
|
|
17
38
|
isError: boolean;
|
|
18
39
|
error: Error | undefined;
|
|
19
|
-
|
|
40
|
+
/** Manually trigger a fetch */
|
|
41
|
+
refetch: () => Promise<void>;
|
|
20
42
|
}
|
|
21
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Visibility-aware polling hook.
|
|
45
|
+
*
|
|
46
|
+
* - Pauses when the tab is hidden.
|
|
47
|
+
* - Skips re-renders when data hasn't changed (shallow JSON comparison).
|
|
48
|
+
* - Prevents overlapping fetches.
|
|
49
|
+
*
|
|
50
|
+
* @param fetchFn - Async function that returns data.
|
|
51
|
+
* @param options - Optional configuration.
|
|
52
|
+
*/
|
|
53
|
+
declare function useSmartPolling<T = unknown>(fetchFn: () => Promise<T>, options?: SmartPollingOptions): SmartPollingResult<T>;
|
|
22
54
|
|
|
23
|
-
export { useAutoPauseVideo, useDocVisible, useIdleVisibility, useSmartPolling };
|
|
55
|
+
export { type IdleVisibilityResult, type SmartPollingOptions, type SmartPollingResult, useAutoPauseVideo, useDocVisible, useIdleVisibility, useSmartPolling };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns `true` when the page is visible, `false` when hidden.
|
|
3
|
+
* SSR-safe â defaults to `true` on the server.
|
|
4
|
+
*/
|
|
1
5
|
declare function useDocVisible(): boolean;
|
|
2
6
|
|
|
3
|
-
|
|
7
|
+
interface IdleVisibilityResult {
|
|
8
|
+
/** Whether the page is visible */
|
|
4
9
|
visible: boolean;
|
|
10
|
+
/** Whether the user is idle (no interaction for `timeout` ms) */
|
|
5
11
|
idle: boolean;
|
|
6
|
-
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Combines page-visibility with idle detection.
|
|
15
|
+
*
|
|
16
|
+
* @param timeout - Milliseconds of inactivity before the user is
|
|
17
|
+
* considered idle (default `60_000`).
|
|
18
|
+
*/
|
|
19
|
+
declare function useIdleVisibility(timeout?: number): IdleVisibilityResult;
|
|
7
20
|
|
|
8
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Automatically pauses a `<video>` when the page becomes hidden
|
|
23
|
+
* and resumes playback when the page becomes visible again.
|
|
24
|
+
*
|
|
25
|
+
* Only resumes if the video was playing before it was paused by this hook.
|
|
26
|
+
*/
|
|
27
|
+
declare function useAutoPauseVideo(ref: React.RefObject<HTMLVideoElement | null>): void;
|
|
9
28
|
|
|
10
29
|
interface SmartPollingOptions {
|
|
30
|
+
/** Polling interval in ms (default `5000`) */
|
|
11
31
|
interval?: number;
|
|
32
|
+
/** Enable / disable polling (default `true`). The initial fetch always fires. */
|
|
12
33
|
enabled?: boolean;
|
|
13
34
|
}
|
|
14
35
|
interface SmartPollingResult<T> {
|
|
@@ -16,8 +37,19 @@ interface SmartPollingResult<T> {
|
|
|
16
37
|
isLoading: boolean;
|
|
17
38
|
isError: boolean;
|
|
18
39
|
error: Error | undefined;
|
|
19
|
-
|
|
40
|
+
/** Manually trigger a fetch */
|
|
41
|
+
refetch: () => Promise<void>;
|
|
20
42
|
}
|
|
21
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Visibility-aware polling hook.
|
|
45
|
+
*
|
|
46
|
+
* - Pauses when the tab is hidden.
|
|
47
|
+
* - Skips re-renders when data hasn't changed (shallow JSON comparison).
|
|
48
|
+
* - Prevents overlapping fetches.
|
|
49
|
+
*
|
|
50
|
+
* @param fetchFn - Async function that returns data.
|
|
51
|
+
* @param options - Optional configuration.
|
|
52
|
+
*/
|
|
53
|
+
declare function useSmartPolling<T = unknown>(fetchFn: () => Promise<T>, options?: SmartPollingOptions): SmartPollingResult<T>;
|
|
22
54
|
|
|
23
|
-
export { useAutoPauseVideo, useDocVisible, useIdleVisibility, useSmartPolling };
|
|
55
|
+
export { type IdleVisibilityResult, type SmartPollingOptions, type SmartPollingResult, useAutoPauseVideo, useDocVisible, useIdleVisibility, useSmartPolling };
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{useEffect
|
|
1
|
+
import {useState,useCallback,useEffect,useRef}from'react';function u(){let[r,e]=useState(()=>typeof document=="undefined"?true:document.visibilityState==="visible"),t=useCallback(()=>{e(document.visibilityState==="visible");},[]);return useEffect(()=>{if(typeof document!="undefined")return t(),document.addEventListener("visibilitychange",t),()=>document.removeEventListener("visibilitychange",t)},[t]),r}function O(r=6e4){let e=u(),[t,n]=useState(false),s=useRef(void 0),o=useCallback(()=>{n(false),clearTimeout(s.current),s.current=setTimeout(()=>n(true),r);},[r]);return useEffect(()=>{if(typeof window=="undefined")return;let m=["mousemove","keydown","pointerdown","scroll","touchstart"];return m.forEach(f=>window.addEventListener(f,o,{passive:true})),o(),()=>{m.forEach(f=>window.removeEventListener(f,o)),clearTimeout(s.current);}},[o]),useEffect(()=>{e&&o();},[e,o]),{visible:e,idle:t}}function j(r){let e=u(),t=useRef(false);useEffect(()=>{let n=r.current;n&&(!e&&!n.paused?(n.pause(),t.current=true):e&&t.current&&(n.play().catch(()=>{}),t.current=false));},[e,r]);}function M(r,e){let{interval:t=5e3,enabled:n=true}=e!=null?e:{},s=u(),[o,m]=useState(void 0),[f,h]=useState(true),[y,R]=useState(void 0),E=useRef(r),a=useRef(void 0),b=useRef(false),g=useRef(void 0),d=useRef(true);E.current=r,useEffect(()=>(d.current=true,()=>{d.current=false;}),[]);let l=useCallback(async()=>{if(!b.current){b.current=true;try{let i=await E.current();if(!d.current)return;let V=JSON.stringify(i);V!==g.current&&(g.current=V,m(i)),R(x=>x!==void 0?void 0:x);}catch(i){if(!d.current)return;R(i instanceof Error?i:new Error(String(i)));}finally{b.current=false,d.current&&h(i=>i&&false);}}},[]),S=useRef(false);return useEffect(()=>{S.current||(S.current=true,l());},[l]),useEffect(()=>{if(!n||!s){clearInterval(a.current),a.current=void 0;return}return l(),a.current=setInterval(l,t),()=>{clearInterval(a.current),a.current=void 0;}},[s,n,t,l]),{data:o,isLoading:f,isError:y!==void 0,error:y,refetch:l}}export{j as useAutoPauseVideo,u as useDocVisible,O as useIdleVisibility,M as useSmartPolling};//# sourceMappingURL=index.js.map
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/useDocVisible.ts","../src/useIdleVisibility.ts","../src/useAutoPauseVideo.ts","../src/useSmartPolling.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\n\nexport function useDocVisible(): boolean {\n const [visible, setVisible] = useState(\n typeof document === \"undefined\" || document.visibilityState === \"visible\"\n );\n\n useEffect(() => {\n if (typeof document === \"undefined\") return;\n const handler = () => setVisible(document.visibilityState === \"visible\");\n document.addEventListener(\"visibilitychange\", handler);\n return () => document.removeEventListener(\"visibilitychange\", handler);\n }, []);\n\n return visible;\n}\n","import { useEffect, useState } from \"react\";\nimport { useDocVisible } from \"./useDocVisible\";\n\nexport function useIdleVisibility(timeout = 60000) {\n const visible = useDocVisible();\n const [idle, setIdle] = useState(false);\n\n useEffect(() => {\n let timer: any;\n\n const reset = () => {\n setIdle(false);\n clearTimeout(timer);\n timer = setTimeout(() => setIdle(true), timeout);\n };\n\n window.addEventListener(\"mousemove\", reset);\n window.addEventListener(\"keydown\", reset);\n reset();\n\n return () => {\n window.removeEventListener(\"mousemove\", reset);\n window.removeEventListener(\"keydown\", reset);\n clearTimeout(timer);\n };\n }, [timeout]);\n\n return { visible, idle };\n}\n","import { useEffect } from \"react\";\nimport { useDocVisible } from \"./useDocVisible\";\n\nexport function useAutoPauseVideo(ref: React.RefObject<HTMLVideoElement>) {\n const visible = useDocVisible();\n\n useEffect(() => {\n const video = ref.current;\n if (!video) return;\n\n if (!visible) video.pause();\n else video.play().catch(() => {});\n }, [visible, ref]);\n}\n","import { useState, useEffect, useRef, useCallback } from \"react\";\nimport { useDocVisible } from \"./useDocVisible\";\n\ninterface SmartPollingOptions {\n interval?: number;\n enabled?: boolean;\n}\n\ninterface SmartPollingResult<T> {\n data: T | undefined;\n isLoading: boolean;\n isError: boolean;\n error: Error | undefined;\n refetch: () => void;\n}\n\nexport function useSmartPolling<T = any>(\n fetchFn: () => Promise<T>,\n options?: SmartPollingOptions,\n): SmartPollingResult<T> {\n const { interval = 5000, enabled = true } = options ?? {};\n const visible = useDocVisible();\n const [data, setData] = useState<T | undefined>(undefined);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | undefined>(undefined);\n const fetchRef = useRef(fetchFn);\n const timerRef = useRef<ReturnType<typeof setInterval> | undefined>(\n undefined,\n );\n const isFetchingRef = useRef(false);\n const lastJsonRef = useRef<string | undefined>(undefined);\n\n fetchRef.current = fetchFn;\n\n const execute = useCallback(async () => {\n // Prevent concurrent fetches from overlapping intervals\n if (isFetchingRef.current) return;\n isFetchingRef.current = true;\n\n try {\n const result = await fetchRef.current();\n\n // Only update state if data actually changed (avoids unnecessary re-renders)\n const json = JSON.stringify(result);\n if (json !== lastJsonRef.current) {\n lastJsonRef.current = json;\n setData(result);\n }\n setError((prev) => (prev !== undefined ? undefined : prev));\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n isFetchingRef.current = false;\n setIsLoading((prev) => (prev ? false : prev));\n }\n }, []);\n\n // Initial fetch â always runs once regardless of `enabled`\n const hasFetchedRef = useRef(false);\n useEffect(() => {\n if (hasFetchedRef.current) return;\n hasFetchedRef.current = true;\n execute();\n }, [execute]);\n\n // Polling â only when visible and enabled\n useEffect(() => {\n if (!enabled || !visible) {\n clearInterval(timerRef.current);\n return;\n }\n\n timerRef.current = setInterval(execute, interval);\n return () => clearInterval(timerRef.current);\n }, [visible, enabled, interval, execute]);\n\n return { data, isLoading, isError: !!error, error, refetch: execute };\n}\n"],"mappings":"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","names":["useEffect","useState","useDocVisible","visible","setVisible","handler","useEffect","useState","useIdleVisibility","timeout","visible","useDocVisible","idle","setIdle","useState","useEffect","timer","reset","useEffect","useAutoPauseVideo","ref","visible","useDocVisible","useEffect","video","useState","useEffect","useRef","useCallback","useSmartPolling","fetchFn","options","interval","enabled","visible","useDocVisible","data","setData","useState","isLoading","setIsLoading","error","setError","fetchRef","useRef","timerRef","isFetchingRef","lastJsonRef","execute","useCallback","result","json","prev","err","hasFetchedRef","useEffect"]}
|
|
1
|
+
{"version":3,"sources":["../src/useDocVisible.ts","../src/useIdleVisibility.ts","../src/useAutoPauseVideo.ts","../src/useSmartPolling.ts"],"names":["useDocVisible","visible","setVisible","useState","handler","useCallback","useEffect","useIdleVisibility","timeout","idle","setIdle","timerRef","useRef","reset","events","e","useAutoPauseVideo","ref","wasPausedByUs","video","useSmartPolling","fetchFn","options","interval","enabled","data","setData","isLoading","setIsLoading","error","setError","fetchRef","isFetchingRef","lastJsonRef","mountedRef","execute","result","json","prev","err","hasFetchedRef"],"mappings":"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","file":"index.js","sourcesContent":["import { useCallback, useEffect, useState } from \"react\";\n\n/**\n * Returns `true` when the page is visible, `false` when hidden.\n * SSR-safe â defaults to `true` on the server.\n */\nexport function useDocVisible(): boolean {\n const [visible, setVisible] = useState(() =>\n typeof document === \"undefined\" ? true : document.visibilityState === \"visible\",\n );\n\n const handler = useCallback(() => {\n setVisible(document.visibilityState === \"visible\");\n }, []);\n\n useEffect(() => {\n if (typeof document === \"undefined\") return;\n\n // Sync in case value changed between SSR hydration and effect\n handler();\n\n document.addEventListener(\"visibilitychange\", handler);\n return () => document.removeEventListener(\"visibilitychange\", handler);\n }, [handler]);\n\n return visible;\n}\n","import { useEffect, useRef, useState, useCallback } from \"react\";\nimport { useDocVisible } from \"./useDocVisible\";\n\nexport interface IdleVisibilityResult {\n /** Whether the page is visible */\n visible: boolean;\n /** Whether the user is idle (no interaction for `timeout` ms) */\n idle: boolean;\n}\n\n/**\n * Combines page-visibility with idle detection.\n *\n * @param timeout - Milliseconds of inactivity before the user is\n * considered idle (default `60_000`).\n */\nexport function useIdleVisibility(timeout = 60_000): IdleVisibilityResult {\n const visible = useDocVisible();\n const [idle, setIdle] = useState(false);\n const timerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n\n const reset = useCallback(() => {\n setIdle(false);\n clearTimeout(timerRef.current);\n timerRef.current = setTimeout(() => setIdle(true), timeout);\n }, [timeout]);\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n\n const events: Array<keyof WindowEventMap> = [\n \"mousemove\",\n \"keydown\",\n \"pointerdown\",\n \"scroll\",\n \"touchstart\",\n ];\n\n events.forEach((e) => window.addEventListener(e, reset, { passive: true }));\n reset();\n\n return () => {\n events.forEach((e) => window.removeEventListener(e, reset));\n clearTimeout(timerRef.current);\n };\n }, [reset]);\n\n // Reset idle timer when tab becomes visible again\n useEffect(() => {\n if (visible) reset();\n }, [visible, reset]);\n\n return { visible, idle };\n}\n","import { useEffect, useRef } from \"react\";\nimport { useDocVisible } from \"./useDocVisible\";\n\n/**\n * Automatically pauses a `<video>` when the page becomes hidden\n * and resumes playback when the page becomes visible again.\n *\n * Only resumes if the video was playing before it was paused by this hook.\n */\nexport function useAutoPauseVideo(ref: React.RefObject<HTMLVideoElement | null>): void {\n const visible = useDocVisible();\n const wasPausedByUs = useRef(false);\n\n useEffect(() => {\n const video = ref.current;\n if (!video) return;\n\n if (!visible && !video.paused) {\n video.pause();\n wasPausedByUs.current = true;\n } else if (visible && wasPausedByUs.current) {\n video.play().catch(() => {\n /* autoplay may be blocked */\n });\n wasPausedByUs.current = false;\n }\n }, [visible, ref]);\n}\n","import { useState, useEffect, useRef, useCallback } from \"react\";\nimport { useDocVisible } from \"./useDocVisible\";\n\nexport interface SmartPollingOptions {\n /** Polling interval in ms (default `5000`) */\n interval?: number;\n /** Enable / disable polling (default `true`). The initial fetch always fires. */\n enabled?: boolean;\n}\n\nexport interface SmartPollingResult<T> {\n data: T | undefined;\n isLoading: boolean;\n isError: boolean;\n error: Error | undefined;\n /** Manually trigger a fetch */\n refetch: () => Promise<void>;\n}\n\n/**\n * Visibility-aware polling hook.\n *\n * - Pauses when the tab is hidden.\n * - Skips re-renders when data hasn't changed (shallow JSON comparison).\n * - Prevents overlapping fetches.\n *\n * @param fetchFn - Async function that returns data.\n * @param options - Optional configuration.\n */\nexport function useSmartPolling<T = unknown>(\n fetchFn: () => Promise<T>,\n options?: SmartPollingOptions,\n): SmartPollingResult<T> {\n const { interval = 5000, enabled = true } = options ?? {};\n const visible = useDocVisible();\n\n const [data, setData] = useState<T | undefined>(undefined);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | undefined>(undefined);\n\n const fetchRef = useRef(fetchFn);\n const timerRef = useRef<ReturnType<typeof setInterval> | undefined>(undefined);\n const isFetchingRef = useRef(false);\n const lastJsonRef = useRef<string | undefined>(undefined);\n const mountedRef = useRef(true);\n\n // Always keep the latest fetchFn\n fetchRef.current = fetchFn;\n\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n const execute = useCallback(async () => {\n if (isFetchingRef.current) return;\n isFetchingRef.current = true;\n\n try {\n const result = await fetchRef.current();\n if (!mountedRef.current) return;\n\n const json = JSON.stringify(result);\n if (json !== lastJsonRef.current) {\n lastJsonRef.current = json;\n setData(result);\n }\n setError((prev) => (prev !== undefined ? undefined : prev));\n } catch (err) {\n if (!mountedRef.current) return;\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n isFetchingRef.current = false;\n if (mountedRef.current) {\n setIsLoading((prev) => (prev ? false : prev));\n }\n }\n }, []);\n\n // Initial fetch â always runs once regardless of `enabled`\n const hasFetchedRef = useRef(false);\n useEffect(() => {\n if (hasFetchedRef.current) return;\n hasFetchedRef.current = true;\n execute();\n }, [execute]);\n\n // Polling â only when visible AND enabled\n useEffect(() => {\n if (!enabled || !visible) {\n clearInterval(timerRef.current);\n timerRef.current = undefined;\n return;\n }\n\n // Immediately fetch when re-enabled / tab returns\n execute();\n\n timerRef.current = setInterval(execute, interval);\n return () => {\n clearInterval(timerRef.current);\n timerRef.current = undefined;\n };\n }, [visible, enabled, interval, execute]);\n\n return {\n data,\n isLoading,\n isError: error !== undefined,\n error,\n refetch: execute,\n };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-visibility-hooks",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Tiny, SSR-safe React hooks for page visibility, idle detection and
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Tiny, SSR-safe React hooks for page visibility, idle detection, smart polling and auto-pause video",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
7
7
|
"module": "./dist/index.js",
|
|
@@ -14,25 +14,43 @@
|
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
|
-
"dist"
|
|
17
|
+
"dist",
|
|
18
|
+
"LICENSE",
|
|
19
|
+
"README.md"
|
|
18
20
|
],
|
|
19
21
|
"sideEffects": false,
|
|
20
22
|
"scripts": {
|
|
21
23
|
"build": "tsup",
|
|
22
|
-
"test": "jest",
|
|
23
|
-
"
|
|
24
|
+
"test": "jest --coverage",
|
|
25
|
+
"test:watch": "jest --watch",
|
|
26
|
+
"lint": "eslint src __tests__",
|
|
27
|
+
"format": "prettier --write \"src/**/*.ts\" \"__tests__/**/*.ts\" \"*.json\" \"*.js\" \"*.ts\" \"*.mjs\"",
|
|
28
|
+
"prepare": "npm run build",
|
|
29
|
+
"typecheck": "tsc --noEmit",
|
|
30
|
+
"prepublishOnly": "npm run lint && npm run format && npm run typecheck && npm run test && npm run build",
|
|
31
|
+
"size": "size-limit"
|
|
24
32
|
},
|
|
25
33
|
"keywords": [
|
|
26
34
|
"react",
|
|
27
35
|
"react-hooks",
|
|
36
|
+
"hooks",
|
|
28
37
|
"visibility",
|
|
29
38
|
"page-visibility",
|
|
39
|
+
"visibilitychange",
|
|
30
40
|
"idle-detection",
|
|
41
|
+
"idle",
|
|
31
42
|
"performance",
|
|
32
43
|
"react-performance",
|
|
33
44
|
"smart-polling",
|
|
45
|
+
"polling",
|
|
46
|
+
"auto-pause",
|
|
47
|
+
"video",
|
|
34
48
|
"analytics",
|
|
35
|
-
"react-utils"
|
|
49
|
+
"react-utils",
|
|
50
|
+
"ssr",
|
|
51
|
+
"ssr-safe",
|
|
52
|
+
"nextjs",
|
|
53
|
+
"typescript"
|
|
36
54
|
],
|
|
37
55
|
"author": "Aniket Raj",
|
|
38
56
|
"license": "MIT",
|
|
@@ -45,17 +63,39 @@
|
|
|
45
63
|
"url": "https://github.com/exewhyz/react-visibility-hooks/issues"
|
|
46
64
|
},
|
|
47
65
|
"peerDependencies": {
|
|
48
|
-
"react": ">=
|
|
66
|
+
"react": ">=16.8.0"
|
|
67
|
+
},
|
|
68
|
+
"peerDependenciesMeta": {
|
|
69
|
+
"react": {
|
|
70
|
+
"optional": false
|
|
71
|
+
}
|
|
49
72
|
},
|
|
50
73
|
"devDependencies": {
|
|
74
|
+
"@eslint/js": "^9.39.2",
|
|
75
|
+
"@size-limit/preset-small-lib": "^11.0.0",
|
|
51
76
|
"@testing-library/react": "^16.3.2",
|
|
52
77
|
"@types/jest": "^30.0.0",
|
|
53
78
|
"@types/react": "^19.2.13",
|
|
79
|
+
"eslint": "^9.39.2",
|
|
54
80
|
"jest": "^30.2.0",
|
|
55
81
|
"jest-environment-jsdom": "^30.2.0",
|
|
56
82
|
"jsdom": "^28.0.0",
|
|
83
|
+
"prettier": "^3.8.1",
|
|
84
|
+
"react": "^19.0.0",
|
|
85
|
+
"react-dom": "^19.0.0",
|
|
86
|
+
"size-limit": "^11.0.0",
|
|
57
87
|
"ts-jest": "^29.4.6",
|
|
58
88
|
"tsup": "^8.5.1",
|
|
59
|
-
"typescript": "^5.9.3"
|
|
89
|
+
"typescript": "^5.9.3",
|
|
90
|
+
"typescript-eslint": "^8.54.0"
|
|
91
|
+
},
|
|
92
|
+
"size-limit": [
|
|
93
|
+
{
|
|
94
|
+
"path": "dist/index.js",
|
|
95
|
+
"limit": "1.5 kB"
|
|
96
|
+
}
|
|
97
|
+
],
|
|
98
|
+
"engines": {
|
|
99
|
+
"node": ">=16"
|
|
60
100
|
}
|
|
61
101
|
}
|