react-visibility-hooks 1.0.13 → 3.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/README.md CHANGED
@@ -1,23 +1,28 @@
1
1
  # react-visibility-hooks
2
2
 
3
- > Tiny, SSR-safe React hooks for page visibility, idle detection, smart polling and auto-pause video
3
+ > Tiny, SSR-safe React hooks for page visibility, idle detection, smart polling, network awareness, wake lock, battery status and more
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/react-visibility-hooks.svg)](https://www.npmjs.com/package/react-visibility-hooks)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
  [![Bundle Size](https://img.shields.io/bundlephobia/minzip/react-visibility-hooks)](https://bundlephobia.com/package/react-visibility-hooks)
8
8
  [![CI](https://github.com/exewhyz/react-visibility-hooks/actions/workflows/ci.yml/badge.svg)](https://github.com/exewhyz/react-visibility-hooks/actions/workflows/ci.yml)
9
9
 
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.
10
+ A collection of lightweight React hooks that help you build performance-conscious, resource-aware applications. Detect page visibility, user idle state, network conditions, battery status, and more — with zero dependencies.
11
11
 
12
12
  ## Features
13
13
 
14
- - ðŸŠķ **Lightweight** - ~822 B brotlied, zero dependencies
15
- - 🔒 **SSR-safe** - Works seamlessly with Next.js (App Router & Pages Router), Remix, and other SSR frameworks
16
- - ðŸ“Ķ **Tree-shakeable** - Import only what you need
17
- - ðŸŽŊ **TypeScript** - Fully typed with exported interfaces
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
14
+ - ðŸŠķ **Lightweight** — Zero dependencies, tree-shakeable
15
+ - 🔒 **SSR-safe** — Works with Next.js (App & Pages Router), Remix, Gatsby, and all SSR frameworks
16
+ - ðŸ“Ķ **Tree-shakeable** — Import only what you need
17
+ - ðŸŽŊ **TypeScript** — Fully typed with exported interfaces
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
21
+ - 🌐 **Network-aware** — Adapt to online/offline state and connection quality
22
+ - 🔋 **Battery-aware** — React to device battery level and charging state
23
+ - ðŸ“ą **Wake Lock** — Prevent screen dimming during critical tasks
24
+ - ⏱ïļ **Inactivity timeout** — Session management with warning countdowns
25
+ - 🔀 **Focus transitions** — Declarative callbacks on tab show/hide
21
26
 
22
27
  ## Installation
23
28
 
@@ -25,17 +30,32 @@ A collection of lightweight React hooks that help you build performance-consciou
25
30
  npm install react-visibility-hooks
26
31
  ```
27
32
 
33
+ ```bash
34
+ yarn add react-visibility-hooks
35
+ ```
36
+
37
+ ```bash
38
+ pnpm add react-visibility-hooks
39
+ ```
40
+
41
+ ## Quick Start
42
+
43
+ ```tsx
44
+ import {
45
+ useDocVisible,
46
+ useSmartPolling,
47
+ useNetworkAwarePolling,
48
+ usePageFocusEffect,
49
+ } from 'react-visibility-hooks';
50
+ ```
51
+
28
52
  ## Hooks
29
53
 
30
54
  ### `useDocVisible`
31
55
 
32
56
  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).
33
57
 
34
- **Use cases:**
35
-
36
- - Pause animations when tab is hidden
37
- - Stop fetching data when user isn't viewing the page
38
- - Save battery and resources
58
+ **Use cases:** Pause animations, stop background fetches, save battery.
39
59
 
40
60
  ```tsx
41
61
  import { useDocVisible } from 'react-visibility-hooks';
@@ -43,29 +63,23 @@ import { useDocVisible } from 'react-visibility-hooks';
43
63
  function MyComponent() {
44
64
  const isVisible = useDocVisible();
45
65
 
46
- return (
47
- <div>
48
- Tab is currently: {isVisible ? 'visible' : 'hidden'}
49
- </div>
50
- );
66
+ return <div>Tab is currently: {isVisible ? 'visible' : 'hidden'}</div>;
51
67
  }
52
68
  ```
53
69
 
70
+ ---
71
+
54
72
  ### `useIdleVisibility`
55
73
 
56
74
  Detect when the user is idle (no mouse, keyboard, pointer, scroll, or touch activity) combined with page visibility state.
57
75
 
58
- **Use cases:**
59
-
60
- - Show "Are you still there?" prompts
61
- - Auto-pause media after inactivity
62
- - Reduce background activity when user is away
76
+ **Use cases:** "Are you still there?" prompts, auto-pause media, reduce background activity.
63
77
 
64
78
  ```tsx
65
79
  import { useIdleVisibility } from 'react-visibility-hooks';
66
80
 
67
81
  function MyComponent() {
68
- const { visible, idle } = useIdleVisibility(60000); // 60 seconds timeout
82
+ const { visible, idle } = useIdleVisibility(60_000); // 60 seconds
69
83
 
70
84
  return (
71
85
  <div>
@@ -76,19 +90,17 @@ function MyComponent() {
76
90
  }
77
91
  ```
78
92
 
79
- **Parameters:**
93
+ | Parameter | Type | Default | Description |
94
+ | --------- | -------- | -------- | --------------------------------------------- |
95
+ | `timeout` | `number` | `60_000` | Ms of inactivity before the user is idle |
80
96
 
81
- - `timeout` (optional): Milliseconds of inactivity before user is considered idle (default: 60000)
97
+ ---
82
98
 
83
99
  ### `useAutoPauseVideo`
84
100
 
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.
86
-
87
- **Use cases:**
101
+ Automatically pause and resume `<video>` elements on tab visibility. Only resumes if the video was playing before the tab was hidden — never overrides a user's manual pause.
88
102
 
89
- - Auto-pause videos when user switches tabs
90
- - Better UX for video-heavy applications
91
- - Save bandwidth and resources
103
+ **Use cases:** Auto-pause videos, save bandwidth, better UX for video-heavy apps.
92
104
 
93
105
  ```tsx
94
106
  import { useRef } from 'react';
@@ -98,36 +110,29 @@ function VideoPlayer() {
98
110
  const videoRef = useRef<HTMLVideoElement>(null);
99
111
  useAutoPauseVideo(videoRef);
100
112
 
101
- return (
102
- <video ref={videoRef} src="video.mp4" controls />
103
- );
113
+ return <video ref={videoRef} src="video.mp4" controls />;
104
114
  }
105
115
  ```
106
116
 
107
- ### `useSmartPolling`
117
+ ---
108
118
 
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.
119
+ ### `useSmartPolling`
110
120
 
111
- **Use cases:**
121
+ Visibility-aware data polling. Automatically pauses when the tab is hidden, resumes when visible, and skips re-renders when data hasn't changed.
112
122
 
113
- - Real-time dashboards
114
- - Live notifications
115
- - Auto-refresh data feeds
123
+ **Use cases:** Real-time dashboards, live notifications, auto-refresh feeds.
116
124
 
117
125
  ```tsx
118
126
  import { useSmartPolling } from 'react-visibility-hooks';
119
127
 
120
128
  function Dashboard() {
121
129
  const { data, isLoading, isError, error, refetch } = useSmartPolling(
122
- async () => {
123
- const response = await fetch('/api/stats');
124
- return response.json();
125
- },
126
- { interval: 3000 }
130
+ () => fetch('/api/stats').then((r) => r.json()),
131
+ { interval: 3000 },
127
132
  );
128
133
 
129
- if (isLoading) return <div>Loading...</div>;
130
- if (isError) return <div>Error: {error?.message}</div>;
134
+ if (isLoading) return <p>Loadingâ€Ķ</p>;
135
+ if (isError) return <p>Error: {error?.message}</p>;
131
136
 
132
137
  return (
133
138
  <div>
@@ -138,48 +143,309 @@ function Dashboard() {
138
143
  }
139
144
  ```
140
145
 
141
- **Options:**
142
- | Option | Type | Default | Description |
143
- |--------|------|---------|-------------|
144
- | `interval` | `number` | `5000` | Polling interval in milliseconds |
145
- | `enabled` | `boolean` | `true` | Enable/disable polling |
146
+ **Options (`SmartPollingOptions`):**
147
+
148
+ | Option | Type | Default | Description |
149
+ | ---------- | --------- | ------- | ------------------------- |
150
+ | `interval` | `number` | `5000` | Polling interval in ms |
151
+ | `enabled` | `boolean` | `true` | Enable / disable polling |
152
+
153
+ **Returns (`SmartPollingResult<T>`):**
154
+
155
+ | Property | Type | Description |
156
+ | ----------- | --------------------- | ------------------------------------- |
157
+ | `data` | `T \| undefined` | The latest fetched data |
158
+ | `isLoading` | `boolean` | `true` until the first fetch completes|
159
+ | `isError` | `boolean` | `true` if the last fetch threw |
160
+ | `error` | `Error \| undefined` | The error object, if any |
161
+ | `refetch` | `() => Promise<void>` | Manually trigger a fetch |
162
+
163
+ ---
164
+
165
+ ### `usePageFocusEffect` âœĻ <sup>NEW</sup>
166
+
167
+ Run side-effect callbacks on visibility **transitions** — not on every render. Think of it as `useEffect` for tab show/hide.
168
+
169
+ **Use cases:** Refetch stale data on tab return, save state on tab leave, track visibility analytics.
170
+
171
+ ```tsx
172
+ import { usePageFocusEffect } from 'react-visibility-hooks';
173
+
174
+ function MyComponent() {
175
+ usePageFocusEffect({
176
+ onVisible: () => {
177
+ console.log('Welcome back! Refetchingâ€Ķ');
178
+ refetchData();
179
+ return () => console.log('cleanup on next transition');
180
+ },
181
+ onHidden: () => {
182
+ saveScrollPosition();
183
+ },
184
+ });
185
+ }
186
+ ```
187
+
188
+ **Options (`PageFocusEffectOptions`):**
189
+
190
+ | Option | Type | Description |
191
+ | ----------- | -------------------------------- | ---------------------------------------------------------------- |
192
+ | `onVisible` | `() => void \| (() => void)` | Fires on hidden → visible. May return a cleanup function. |
193
+ | `onHidden` | `() => void` | Fires on visible → hidden. |
194
+
195
+ > The initial render is **not** treated as a transition. Callbacks only fire on actual visibility changes.
196
+
197
+ ---
198
+
199
+ ### `useNetworkAwarePolling` âœĻ <sup>NEW</sup>
200
+
201
+ Extends `useSmartPolling` with network awareness. Pauses when offline and adapts the polling interval on slow connections (2g / slow-2g).
202
+
203
+ **Use cases:** Mobile-first apps, offline-resilient dashboards, bandwidth-conscious polling.
204
+
205
+ ```tsx
206
+ import { useNetworkAwarePolling } from 'react-visibility-hooks';
207
+
208
+ function LiveFeed() {
209
+ const { data, isOnline, effectiveInterval } = useNetworkAwarePolling(
210
+ () => fetch('/api/feed').then((r) => r.json()),
211
+ { interval: 5000, slowMultiplier: 3 },
212
+ );
213
+
214
+ return (
215
+ <div>
216
+ <p>Status: {isOnline ? 'ðŸŸĒ Online' : 'ðŸ”ī Offline'}</p>
217
+ <p>Polling every {effectiveInterval / 1000}s</p>
218
+ <pre>{JSON.stringify(data, null, 2)}</pre>
219
+ </div>
220
+ );
221
+ }
222
+ ```
223
+
224
+ **Options (`NetworkAwarePollingOptions`):**
225
+
226
+ | Option | Type | Default | Description |
227
+ | ----------------- | --------- | ------- | ----------------------------------------------- |
228
+ | `interval` | `number` | `5000` | Base polling interval in ms |
229
+ | `enabled` | `boolean` | `true` | Enable / disable polling |
230
+ | `slowMultiplier` | `number` | `3` | Multiplier applied on 2g/slow-2g connections |
231
+ | `pauseOffline` | `boolean` | `true` | Pause polling when offline |
146
232
 
147
- **Returns:**
148
- | Property | Type | Description |
149
- |----------|------|-------------|
150
- | `data` | `T \| undefined` | The latest fetched data |
151
- | `isLoading` | `boolean` | `true` until the first fetch completes |
152
- | `isError` | `boolean` | `true` if the last fetch threw |
153
- | `error` | `Error \| undefined` | The error object, if any |
154
- | `refetch` | `() => Promise<void>` | Manually trigger a fetch |
233
+ **Returns (`NetworkAwarePollingResult<T>`):**
155
234
 
156
- **Optimizations:**
235
+ Inherits all of `SmartPollingResult<T>` plus:
157
236
 
158
- - Skips re-renders when polled data is identical to the previous result
159
- - Prevents concurrent fetches from overlapping
160
- - Polling pauses automatically when the tab is hidden and resumes when visible
237
+ | Property | Type | Description |
238
+ | ------------------- | --------- | -------------------------------------------------- |
239
+ | `isOnline` | `boolean` | `true` when the browser reports being online |
240
+ | `effectiveInterval` | `number` | Actual polling interval after network adjustment |
241
+
242
+ ---
243
+
244
+ ### `useInactivityTimeout` âœĻ <sup>NEW</sup>
245
+
246
+ Countdown-based session/inactivity manager with a warning phase. Built on `useIdleVisibility`.
247
+
248
+ **Use cases:** Auto-logout, session expiry warnings, "still there?" modals.
249
+
250
+ ```tsx
251
+ import { useInactivityTimeout } from 'react-visibility-hooks';
252
+
253
+ function SessionManager() {
254
+ const { isWarning, isTimedOut, remainingSeconds, resetTimer } =
255
+ useInactivityTimeout({
256
+ timeout: 300_000, // 5 minutes total
257
+ warningBefore: 60_000, // warn 1 minute before
258
+ onWarning: () => showWarningToast(),
259
+ onTimeout: () => logout(),
260
+ });
261
+
262
+ if (isTimedOut) return <p>Session expired.</p>;
263
+
264
+ if (isWarning) {
265
+ return (
266
+ <div>
267
+ <p>Session expires in {remainingSeconds}s</p>
268
+ <button onClick={resetTimer}>Stay logged in</button>
269
+ </div>
270
+ );
271
+ }
272
+
273
+ return null;
274
+ }
275
+ ```
276
+
277
+ **Options (`InactivityTimeoutOptions`):**
278
+
279
+ | Option | Type | Default | Description |
280
+ | --------------- | ------------ | --------- | ------------------------------------------------- |
281
+ | `timeout` | `number` | `300_000` | Total inactivity before timeout (ms) |
282
+ | `warningBefore` | `number` | `60_000` | How long before timeout to start warning (ms) |
283
+ | `onTimeout` | `() => void` | — | Called when the full timeout elapses |
284
+ | `onWarning` | `() => void` | — | Called when entering the warning phase |
285
+
286
+ **Returns (`InactivityTimeoutResult`):**
287
+
288
+ | Property | Type | Description |
289
+ | ------------------ | ------------ | -------------------------------------------------- |
290
+ | `idle` | `boolean` | `true` when the user is idle |
291
+ | `visible` | `boolean` | Current page-visibility state |
292
+ | `isWarning` | `boolean` | `true` during the warning countdown |
293
+ | `isTimedOut` | `boolean` | `true` after the full timeout has elapsed |
294
+ | `remainingSeconds` | `number` | Seconds until timeout (`-1` when not idle) |
295
+ | `resetTimer` | `() => void` | Manually reset the timer |
296
+
297
+ ---
298
+
299
+ ### `useWakeLock` âœĻ <sup>NEW</sup>
300
+
301
+ Manage the [Screen Wake Lock API](https://developer.mozilla.org/en-US/docs/Web/API/Screen_Wake_Lock_API) to prevent the screen from dimming or locking.
302
+
303
+ **Use cases:** Video calls, presentations, recipe/reading apps, kiosk mode.
304
+
305
+ ```tsx
306
+ import { useWakeLock } from 'react-visibility-hooks';
307
+
308
+ function PresentationMode() {
309
+ const { isActive, isSupported, request, release } = useWakeLock();
310
+
311
+ if (!isSupported) return <p>Wake Lock not supported</p>;
312
+
313
+ return (
314
+ <div>
315
+ <p>Screen lock prevention: {isActive ? 'ON' : 'OFF'}</p>
316
+ <button onClick={isActive ? release : request}>
317
+ {isActive ? 'Allow screen lock' : 'Keep screen on'}
318
+ </button>
319
+ </div>
320
+ );
321
+ }
322
+ ```
323
+
324
+ **Parameters:**
325
+
326
+ | Parameter | Type | Default | Description |
327
+ | --------------- | --------- | ------- | --------------------------------------------------- |
328
+ | `autoReacquire` | `boolean` | `true` | Re-request lock when the tab becomes visible again |
329
+
330
+ **Returns (`WakeLockResult`):**
331
+
332
+ | Property | Type | Description |
333
+ | ------------- | --------------------- | ---------------------------------------- |
334
+ | `isActive` | `boolean` | `true` while a Wake Lock is held |
335
+ | `request` | `() => Promise<void>` | Request the screen Wake Lock |
336
+ | `release` | `() => Promise<void>` | Release the current Wake Lock |
337
+ | `isSupported` | `boolean` | `true` if the API is available |
338
+
339
+ ---
340
+
341
+ ### `useBatteryAware` âœĻ <sup>NEW</sup>
342
+
343
+ Expose device battery status via the [Battery Status API](https://developer.mozilla.org/en-US/docs/Web/API/Battery_Status_API).
344
+
345
+ **Use cases:** Reduce polling on low battery, disable animations, show battery indicators.
346
+
347
+ ```tsx
348
+ import { useBatteryAware } from 'react-visibility-hooks';
349
+
350
+ function BatteryIndicator() {
351
+ const { charging, level, isLowBattery, isSupported } = useBatteryAware(0.15);
352
+
353
+ if (!isSupported) return null;
354
+
355
+ return (
356
+ <div>
357
+ <p>🔋 {Math.round(level * 100)}%{charging ? ' ⚡ Charging' : ''}</p>
358
+ {isLowBattery && <p>⚠ïļ Low battery — reducing activity</p>}
359
+ </div>
360
+ );
361
+ }
362
+ ```
363
+
364
+ **Parameters:**
365
+
366
+ | Parameter | Type | Default | Description |
367
+ | -------------- | -------- | ------- | ------------------------------------------------------- |
368
+ | `lowThreshold` | `number` | `0.15` | Level (0–1) below which `isLowBattery` is `true` |
369
+
370
+ **Returns (`BatteryState`):**
371
+
372
+ | Property | Type | Description |
373
+ | -------------- | --------- | ---------------------------------------------------- |
374
+ | `charging` | `boolean` | `true` when the device is plugged in |
375
+ | `level` | `number` | Battery level between 0 and 1 |
376
+ | `isLowBattery` | `boolean` | `true` when not charging and below threshold |
377
+ | `isSupported` | `boolean` | `true` when the Battery Status API is available |
378
+
379
+ ---
380
+
381
+ ## Combining Hooks
382
+
383
+ The real power comes from composing hooks together:
384
+
385
+ ```tsx
386
+ import {
387
+ useNetworkAwarePolling,
388
+ useBatteryAware,
389
+ useInactivityTimeout,
390
+ } from 'react-visibility-hooks';
391
+
392
+ function SmartDashboard() {
393
+ const { isLowBattery } = useBatteryAware();
394
+ const { isTimedOut } = useInactivityTimeout({ timeout: 300_000 });
395
+
396
+ const { data } = useNetworkAwarePolling(
397
+ () => fetch('/api/metrics').then((r) => r.json()),
398
+ {
399
+ interval: isLowBattery ? 30_000 : 5_000,
400
+ enabled: !isTimedOut,
401
+ },
402
+ );
403
+
404
+ return <pre>{JSON.stringify(data, null, 2)}</pre>;
405
+ }
406
+ ```
161
407
 
162
408
  ## SSR Support
163
409
 
164
- All hooks are SSR-safe and will work correctly with:
410
+ All hooks are SSR-safe and work correctly with:
165
411
 
166
- - Next.js (App Router & Pages Router)
167
- - Remix
168
- - Gatsby
412
+ - **Next.js** (App Router & Pages Router)
413
+ - **Remix**
414
+ - **Gatsby**
169
415
  - Any other React SSR framework
170
416
 
171
- The hooks check for `document` availability and gracefully handle server-side rendering.
417
+ The hooks check for `document`/`window`/`navigator` availability and return sensible defaults on the server (e.g., `visible = true`, `online = true`, `charging = true`).
172
418
 
173
419
  ## TypeScript
174
420
 
175
- This package is written in TypeScript and includes type definitions out of the box. All hooks are fully typed for the best developer experience.
421
+ Written in TypeScript with full type definitions. All interfaces are exported:
422
+
423
+ ```tsx
424
+ import type {
425
+ IdleVisibilityResult,
426
+ SmartPollingOptions,
427
+ SmartPollingResult,
428
+ PageFocusEffectOptions,
429
+ NetworkAwarePollingOptions,
430
+ NetworkAwarePollingResult,
431
+ InactivityTimeoutOptions,
432
+ InactivityTimeoutResult,
433
+ WakeLockResult,
434
+ BatteryState,
435
+ } from 'react-visibility-hooks';
436
+ ```
176
437
 
177
438
  ## Browser Support
178
439
 
179
- Works in all modern browsers that support:
440
+ | Feature | API | Support |
441
+ | ---------------------- | ----------------------- | ----------------------------------------------------------------------- |
442
+ | Visibility detection | Page Visibility API | [All modern browsers](https://caniuse.com/pagevisibility) |
443
+ | Network detection | `navigator.onLine` | [All modern browsers](https://caniuse.com/online-status) |
444
+ | Connection quality | Network Information API | [Chrome, Edge, Opera](https://caniuse.com/netinfo) |
445
+ | Wake Lock | Screen Wake Lock API | [Chrome, Edge, Opera](https://caniuse.com/wake-lock) |
446
+ | Battery status | Battery Status API | [Chrome, Edge, Opera](https://caniuse.com/battery-status) |
180
447
 
181
- - [Page Visibility API](https://caniuse.com/pagevisibility)
182
- - React >=16.8.0 (hooks)
448
+ > All hooks degrade gracefully — `isSupported` flags let you build progressive UIs.
183
449
 
184
450
  ## Examples
185
451
 
@@ -193,38 +459,61 @@ function ExpensiveComponent() {
193
459
  const isVisible = useDocVisible();
194
460
 
195
461
  useEffect(() => {
196
- if (!isVisible) return; // Skip when hidden
462
+ if (!isVisible) return;
197
463
 
198
464
  const interval = setInterval(() => {
199
- // Expensive operation
200
465
  performCalculation();
201
466
  }, 1000);
202
467
 
203
468
  return () => clearInterval(interval);
204
469
  }, [isVisible]);
205
470
 
206
- return <div>...</div>;
471
+ return <div>â€Ķ</div>;
207
472
  }
208
473
  ```
209
474
 
210
- ### Show idle warning
475
+ ### Session timeout with warning modal
211
476
 
212
477
  ```tsx
213
- import { useIdleVisibility } from 'react-visibility-hooks';
214
-
215
- function IdleWarning() {
216
- const { idle } = useIdleVisibility(300000); // 5 minutes
478
+ import { useInactivityTimeout } from 'react-visibility-hooks';
217
479
 
218
- if (!idle) return null;
480
+ function App() {
481
+ const { isWarning, remainingSeconds, resetTimer, isTimedOut } =
482
+ useInactivityTimeout({
483
+ timeout: 600_000,
484
+ warningBefore: 120_000,
485
+ onTimeout: () => window.location.href = '/login',
486
+ });
219
487
 
220
488
  return (
221
- <div className="warning">
222
- Are you still there? Your session will expire soon.
223
- </div>
489
+ <>
490
+ <MainContent />
491
+ {isWarning && (
492
+ <Modal>
493
+ <p>Session expires in {remainingSeconds}s</p>
494
+ <button onClick={resetTimer}>I'm still here</button>
495
+ </Modal>
496
+ )}
497
+ </>
224
498
  );
225
499
  }
226
500
  ```
227
501
 
502
+ ### Battery-aware polling
503
+
504
+ ```tsx
505
+ import { useSmartPolling, useBatteryAware } from 'react-visibility-hooks';
506
+
507
+ function Feed() {
508
+ const { isLowBattery } = useBatteryAware();
509
+ const { data } = useSmartPolling(fetchFeed, {
510
+ interval: isLowBattery ? 30_000 : 5_000,
511
+ });
512
+
513
+ return <FeedList items={data} />;
514
+ }
515
+ ```
516
+
228
517
  ## Contributing
229
518
 
230
519
  Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) before submitting a Pull Request.
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
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
1
+ 'use strict';var react=require('react');function m(){let[i,n]=react.useState(()=>typeof document=="undefined"?true:document.visibilityState==="visible"),t=react.useCallback(()=>{n(document.visibilityState==="visible");},[]);return react.useEffect(()=>{if(typeof document!="undefined")return t(),document.addEventListener("visibilitychange",t),()=>document.removeEventListener("visibilitychange",t)},[t]),i}function T(i=6e4){let n=m(),[t,r]=react.useState(false),e=react.useRef(void 0),o=react.useCallback(()=>{r(false),clearTimeout(e.current),e.current=setTimeout(()=>r(true),i);},[i]);return react.useEffect(()=>{if(typeof window=="undefined")return;let s=["mousemove","keydown","pointerdown","scroll","touchstart"];return s.forEach(u=>window.addEventListener(u,o,{passive:true})),o(),()=>{s.forEach(u=>window.removeEventListener(u,o)),clearTimeout(e.current);}},[o]),react.useEffect(()=>{n&&o();},[n,o]),{visible:n,idle:t}}function Q(i){let n=m(),t=react.useRef(false);react.useEffect(()=>{let r=i.current;r&&(!n&&!r.paused?(r.pause(),t.current=true):n&&t.current&&(r.play().catch(()=>{}),t.current=false));},[n,i]);}function I(i,n){let{interval:t=5e3,enabled:r=true}=n!=null?n:{},e=m(),[o,s]=react.useState(void 0),[u,l]=react.useState(true),[c,b]=react.useState(void 0),g=react.useRef(i),p=react.useRef(void 0),f=react.useRef(false),w=react.useRef(void 0),v=react.useRef(true);g.current=i,react.useEffect(()=>(v.current=true,()=>{v.current=false;}),[]);let d=react.useCallback(async()=>{if(!f.current){f.current=true;try{let a=await g.current();if(!v.current)return;let x=JSON.stringify(a);x!==w.current&&(w.current=x,s(a)),b(k=>k!==void 0?void 0:k);}catch(a){if(!v.current)return;b(a instanceof Error?a:new Error(String(a)));}finally{f.current=false,v.current&&l(a=>a&&false);}}},[]),y=react.useRef(false);return react.useEffect(()=>{y.current||(y.current=true,d());},[d]),react.useEffect(()=>{if(!r||!e){clearInterval(p.current),p.current=void 0;return}return d(),p.current=setInterval(d,t),()=>{clearInterval(p.current),p.current=void 0;}},[e,r,t,d]),{data:o,isLoading:u,isError:c!==void 0,error:c,refetch:d}}function Y(i){let n=m(),t=react.useRef(void 0),r=react.useRef(void 0),e=react.useRef(i.onVisible),o=react.useRef(i.onHidden);e.current=i.onVisible,o.current=i.onHidden,react.useEffect(()=>{var s,u,l;if(t.current===void 0){t.current=n;return}if(n&&!t.current){(s=r.current)==null||s.call(r),r.current=void 0;let c=(u=e.current)==null?void 0:u.call(e);typeof c=="function"&&(r.current=c);}else !n&&t.current&&((l=o.current)==null||l.call(o));t.current=n;},[n]),react.useEffect(()=>()=>{var s;(s=r.current)==null||s.call(r);},[]);}function ee(i,n){let{interval:t=5e3,enabled:r=true,slowMultiplier:e=3,pauseOffline:o=true,...s}=n!=null?n:{},u=te(),l=ne(t,e);return {...I(i,{...s,interval:l,enabled:o?r&&u:r}),isOnline:u,effectiveInterval:l}}function te(){let[i,n]=react.useState(()=>typeof navigator=="undefined"?true:navigator.onLine),t=react.useCallback(()=>n(true),[]),r=react.useCallback(()=>n(false),[]);return react.useEffect(()=>{if(typeof window!="undefined")return window.addEventListener("online",t),window.addEventListener("offline",r),()=>{window.removeEventListener("online",t),window.removeEventListener("offline",r);}},[t,r]),i}function ne(i,n){if(typeof navigator=="undefined")return i;let t=navigator.connection;return (t==null?void 0:t.effectiveType)==="2g"||(t==null?void 0:t.effectiveType)==="slow-2g"?i*n:i}function oe(i){let{timeout:n=3e5,warningBefore:t=6e4,onTimeout:r,onWarning:e}=i!=null?i:{},o=Math.min(t,n),s=n-o,{idle:u,visible:l}=T(s),[c,b]=react.useState(-1),[g,p]=react.useState(false),f=react.useRef(void 0),w=react.useRef(void 0),v=react.useRef(false),d=react.useRef(r),y=react.useRef(e);d.current=r,y.current=e;let a=react.useCallback(()=>{clearInterval(f.current),f.current=void 0,w.current=void 0,v.current=false,b(-1),p(false);},[]);react.useEffect(()=>{var L;if(!u||g){u||a();return}w.current=Date.now(),v.current||(v.current=true,(L=y.current)==null||L.call(y));let k=o;return f.current=setInterval(()=>{var B,V;let H=Date.now()-((B=w.current)!=null?B:Date.now()),O=Math.max(0,Math.ceil((k-H)/1e3));b(O),O<=0&&(clearInterval(f.current),f.current=void 0,p(true),(V=d.current)==null||V.call(d));},1e3),()=>{clearInterval(f.current),f.current=void 0;}},[u,g,o,a]);let x=u&&!g&&c>=0;return {idle:u||g,visible:l,isWarning:x,isTimedOut:g,remainingSeconds:c,resetTimer:a}}function se(i=true){let n=m(),[t,r]=react.useState(false),e=react.useRef(void 0),o=react.useRef(false),s=typeof navigator!="undefined"&&navigator.wakeLock!=null,u=react.useCallback(async()=>{if(s&&!(e.current&&!e.current.released))try{let c=await navigator.wakeLock.request("screen");e.current=c,o.current=!0,r(!0),c.addEventListener("release",()=>{r(!1),e.current=void 0;});}catch{}},[s]),l=react.useCallback(async()=>{o.current=false,e.current&&!e.current.released&&await e.current.release();},[]);return react.useEffect(()=>{i&&n&&o.current&&!e.current&&u();},[n,i,u]),react.useEffect(()=>()=>{e.current&&!e.current.released&&e.current.release().catch(()=>{});},[]),{isActive:t,request:u,release:l,isSupported:s}}function fe(i=.15){let[n,t]=react.useState({charging:true,level:1,isLowBattery:false,isSupported:false}),r=react.useCallback(e=>{t({charging:e.charging,level:e.level,isLowBattery:!e.charging&&e.level<i,isSupported:true});},[i]);return react.useEffect(()=>{if(typeof navigator=="undefined"||typeof navigator.getBattery!="function")return;let e,o=false,s=()=>{e&&!o&&r(e);};return navigator.getBattery().then(u=>{o||(e=u,r(u),u.addEventListener("chargingchange",s),u.addEventListener("levelchange",s));}).catch(()=>{}),()=>{o=true,e&&(e.removeEventListener("chargingchange",s),e.removeEventListener("levelchange",s));}},[r]),n}exports.useAutoPauseVideo=Q;exports.useBatteryAware=fe;exports.useDocVisible=m;exports.useIdleVisibility=T;exports.useInactivityTimeout=oe;exports.useNetworkAwarePolling=ee;exports.usePageFocusEffect=Y;exports.useSmartPolling=I;exports.useWakeLock=se;//# sourceMappingURL=index.cjs.map
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
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.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"]}
1
+ {"version":3,"sources":["../src/useDocVisible.ts","../src/useIdleVisibility.ts","../src/useAutoPauseVideo.ts","../src/useSmartPolling.ts","../src/usePageFocusEffect.ts","../src/useNetworkAwarePolling.ts","../src/useInactivityTimeout.ts","../src/useWakeLock.ts","../src/useBatteryAware.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","usePageFocusEffect","prevRef","cleanupRef","onVisibleRef","onHiddenRef","_a","_b","_c","useNetworkAwarePolling","slowMultiplier","pauseOffline","rest","online","useOnline","effectiveInterval","getEffectiveInterval","setOnline","handleOnline","handleOffline","base","multiplier","conn","useInactivityTimeout","warningBefore","onTimeout","onWarning","clampedWarning","idleThreshold","remaining","setRemaining","timedOut","setTimedOut","intervalRef","startRef","warnedRef","onTimeoutRef","onWarningRef","clear","warningMs","elapsed","left","isWarning","useWakeLock","autoReacquire","isActive","setIsActive","sentinelRef","wantedRef","isSupported","request","sentinel","release","useBatteryAware","lowThreshold","state","setState","update","battery","cancelled","onChange","b"],"mappings":"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","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","import { useEffect, useRef } from \"react\";\nimport { useDocVisible } from \"./useDocVisible\";\n\nexport interface PageFocusEffectOptions {\n /** Callback fired when the page transitions from hidden → visible. May return a cleanup function. */\n onVisible?: () => void | (() => void);\n /** Callback fired when the page transitions from visible → hidden. */\n onHidden?: () => void;\n}\n\n/**\n * Runs side-effect callbacks on visibility **transitions** rather than\n * on every render.\n *\n * - `onVisible` fires when the tab goes from hidden → visible.\n * - `onHidden` fires when the tab goes from visible → hidden.\n * - The very first render is **not** treated as a transition.\n * - `onVisible` may return a cleanup function (like `useEffect`).\n *\n * SSR-safe — no-ops on the server.\n *\n * @example\n * ```tsx\n * usePageFocusEffect({\n * onVisible: () => { refetchStaleData(); },\n * onHidden: () => { saveScrollPosition(); },\n * });\n * ```\n */\nexport function usePageFocusEffect(options: PageFocusEffectOptions): void {\n const visible = useDocVisible();\n const prevRef = useRef<boolean | undefined>(undefined);\n const cleanupRef = useRef<(() => void) | undefined>(undefined);\n\n // Keep latest callbacks in refs to avoid re-triggering the effect\n const onVisibleRef = useRef(options.onVisible);\n const onHiddenRef = useRef(options.onHidden);\n onVisibleRef.current = options.onVisible;\n onHiddenRef.current = options.onHidden;\n\n useEffect(() => {\n // Skip the initial mount — only fire on actual transitions\n if (prevRef.current === undefined) {\n prevRef.current = visible;\n return;\n }\n\n if (visible && !prevRef.current) {\n // hidden → visible\n cleanupRef.current?.();\n cleanupRef.current = undefined;\n const result = onVisibleRef.current?.();\n if (typeof result === \"function\") {\n cleanupRef.current = result;\n }\n } else if (!visible && prevRef.current) {\n // visible → hidden\n onHiddenRef.current?.();\n }\n\n prevRef.current = visible;\n }, [visible]);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n cleanupRef.current?.();\n };\n }, []);\n}\n","import { useState, useEffect, useCallback } from \"react\";\nimport { useSmartPolling } from \"./useSmartPolling\";\nimport type { SmartPollingOptions, SmartPollingResult } from \"./useSmartPolling\";\n\nexport interface NetworkAwarePollingOptions extends SmartPollingOptions {\n /** Multiplier applied to `interval` on slow connections like 2g (default `3`). */\n slowMultiplier?: number;\n /** Whether to pause polling when the browser is offline (default `true`). */\n pauseOffline?: boolean;\n}\n\nexport interface NetworkAwarePollingResult<T> extends SmartPollingResult<T> {\n /** `true` when the browser reports being online. */\n isOnline: boolean;\n /** The effective polling interval after network-quality adjustment. */\n effectiveInterval: number;\n}\n\n/**\n * Network + visibility-aware polling.\n *\n * Extends `useSmartPolling` with:\n * - Automatic pause when the browser goes offline.\n * - Adaptive interval — polls slower on poor connections (2g / slow-2g).\n *\n * SSR-safe — defaults to online on the server.\n *\n * @param fetchFn - Async function that returns data.\n * @param options - Optional configuration.\n */\nexport function useNetworkAwarePolling<T = unknown>(\n fetchFn: () => Promise<T>,\n options?: NetworkAwarePollingOptions,\n): NetworkAwarePollingResult<T> {\n const {\n interval = 5000,\n enabled = true,\n slowMultiplier = 3,\n pauseOffline = true,\n ...rest\n } = options ?? {};\n\n const online = useOnline();\n const effectiveInterval = getEffectiveInterval(interval, slowMultiplier);\n\n const shouldPoll = pauseOffline ? enabled && online : enabled;\n\n const result = useSmartPolling(fetchFn, {\n ...rest,\n interval: effectiveInterval,\n enabled: shouldPoll,\n });\n\n return { ...result, isOnline: online, effectiveInterval };\n}\n\n/* ------------------------------------------------------------------ */\n/* Internal helpers */\n/* ------------------------------------------------------------------ */\n\n/** SSR-safe hook that tracks `navigator.onLine`. */\nfunction useOnline(): boolean {\n const [online, setOnline] = useState(() =>\n typeof navigator === \"undefined\" ? true : navigator.onLine,\n );\n\n const handleOnline = useCallback(() => setOnline(true), []);\n const handleOffline = useCallback(() => setOnline(false), []);\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n\n window.addEventListener(\"online\", handleOnline);\n window.addEventListener(\"offline\", handleOffline);\n return () => {\n window.removeEventListener(\"online\", handleOnline);\n window.removeEventListener(\"offline\", handleOffline);\n };\n }, [handleOnline, handleOffline]);\n\n return online;\n}\n\n/** Returns a longer interval when the connection is slow. */\nfunction getEffectiveInterval(base: number, multiplier: number): number {\n if (typeof navigator === \"undefined\") return base;\n\n const conn = (\n navigator as Navigator & {\n connection?: { effectiveType?: string };\n }\n ).connection;\n\n if (conn?.effectiveType === \"2g\" || conn?.effectiveType === \"slow-2g\") {\n return base * multiplier;\n }\n return base;\n}\n","import { useState, useEffect, useRef, useCallback } from \"react\";\nimport { useIdleVisibility } from \"./useIdleVisibility\";\n\nexport interface InactivityTimeoutOptions {\n /** Total inactivity before `onTimeout` fires, in ms (default `300_000` — 5 min). */\n timeout?: number;\n /**\n * How long before the final timeout to enter the \"warning\" phase, in ms\n * (default `60_000` — 1 min).\n *\n * Set to `0` to disable the warning phase.\n */\n warningBefore?: number;\n /** Called once when the full timeout elapses. */\n onTimeout?: () => void;\n /** Called when entering the warning phase. */\n onWarning?: () => void;\n}\n\nexport interface InactivityTimeoutResult {\n /** `true` once the user has been idle long enough to start counting down. */\n idle: boolean;\n /** Current page-visibility state. */\n visible: boolean;\n /** `true` during the warning countdown (before final timeout). */\n isWarning: boolean;\n /** `true` after the full timeout has elapsed. */\n isTimedOut: boolean;\n /** Seconds remaining until timeout (`-1` when the user is not idle). */\n remainingSeconds: number;\n /** Manually reset the timer and cancel any pending timeout. */\n resetTimer: () => void;\n}\n\n/**\n * Countdown-based session/inactivity manager built on top of\n * `useIdleVisibility`.\n *\n * 1. After `timeout - warningBefore` ms of inactivity the hook enters\n * the **warning** phase and starts a per-second countdown.\n * 2. When the countdown hits zero `onTimeout` fires and `isTimedOut`\n * becomes `true`.\n * 3. Any user interaction resets the timer.\n *\n * SSR-safe — all timers only run in the browser.\n */\nexport function useInactivityTimeout(\n options?: InactivityTimeoutOptions,\n): InactivityTimeoutResult {\n const {\n timeout = 300_000,\n warningBefore = 60_000,\n onTimeout,\n onWarning,\n } = options ?? {};\n\n // Clamp so warningBefore never exceeds timeout\n const clampedWarning = Math.min(warningBefore, timeout);\n const idleThreshold = timeout - clampedWarning;\n\n const { idle, visible } = useIdleVisibility(idleThreshold);\n\n const [remaining, setRemaining] = useState(-1);\n const [timedOut, setTimedOut] = useState(false);\n const intervalRef = useRef<ReturnType<typeof setInterval> | undefined>(undefined);\n const startRef = useRef<number | undefined>(undefined);\n const warnedRef = useRef(false);\n\n // Keep callback refs stable\n const onTimeoutRef = useRef(onTimeout);\n const onWarningRef = useRef(onWarning);\n onTimeoutRef.current = onTimeout;\n onWarningRef.current = onWarning;\n\n const clear = useCallback(() => {\n clearInterval(intervalRef.current);\n intervalRef.current = undefined;\n startRef.current = undefined;\n warnedRef.current = false;\n setRemaining(-1);\n setTimedOut(false);\n }, []);\n\n useEffect(() => {\n if (!idle || timedOut) {\n if (!idle) clear();\n return;\n }\n\n startRef.current = Date.now();\n\n // Fire the warning callback once at the start of the countdown\n if (!warnedRef.current) {\n warnedRef.current = true;\n onWarningRef.current?.();\n }\n\n const warningMs = clampedWarning;\n\n intervalRef.current = setInterval(() => {\n const elapsed = Date.now() - (startRef.current ?? Date.now());\n const left = Math.max(0, Math.ceil((warningMs - elapsed) / 1000));\n setRemaining(left);\n\n if (left <= 0) {\n clearInterval(intervalRef.current);\n intervalRef.current = undefined;\n setTimedOut(true);\n onTimeoutRef.current?.();\n }\n }, 1000);\n\n return () => {\n clearInterval(intervalRef.current);\n intervalRef.current = undefined;\n };\n }, [idle, timedOut, clampedWarning, clear]);\n\n const isWarning = idle && !timedOut && remaining >= 0;\n\n return {\n idle: idle || timedOut,\n visible,\n isWarning,\n isTimedOut: timedOut,\n remainingSeconds: remaining,\n resetTimer: clear,\n };\n}\n","import { useState, useCallback, useEffect, useRef } from \"react\";\nimport { useDocVisible } from \"./useDocVisible\";\n\nexport interface WakeLockResult {\n /** `true` while a Wake Lock is actively held. */\n isActive: boolean;\n /** Request the screen Wake Lock. No-ops if unsupported. */\n request: () => Promise<void>;\n /** Release the current Wake Lock. */\n release: () => Promise<void>;\n /** `true` when the Screen Wake Lock API is available in this browser. */\n isSupported: boolean;\n}\n\n/**\n * Manages the [Screen Wake Lock API](https://developer.mozilla.org/en-US/docs/Web/API/Screen_Wake_Lock_API)\n * to prevent the screen from dimming or locking.\n *\n * Features:\n * - Automatic re-acquire on tab re-focus when `autoReacquire` is `true`.\n * - Cleans up the Wake Lock on unmount.\n * - SSR-safe — `isSupported` will be `false` on the server.\n *\n * @param autoReacquire - Re-request the lock when the tab becomes visible\n * again after being hidden (default `true`).\n */\nexport function useWakeLock(autoReacquire = true): WakeLockResult {\n const visible = useDocVisible();\n const [isActive, setIsActive] = useState(false);\n const sentinelRef = useRef<WakeLockSentinel | undefined>(undefined);\n const wantedRef = useRef(false);\n\n const isSupported = typeof navigator !== \"undefined\" && navigator.wakeLock != null;\n\n const request = useCallback(async () => {\n if (!isSupported) return;\n // Already holding a lock — skip\n if (sentinelRef.current && !sentinelRef.current.released) return;\n\n try {\n const sentinel = await navigator.wakeLock.request(\"screen\");\n sentinelRef.current = sentinel;\n wantedRef.current = true;\n setIsActive(true);\n\n sentinel.addEventListener(\"release\", () => {\n setIsActive(false);\n sentinelRef.current = undefined;\n });\n } catch {\n // Permission denied, low battery, or other platform error\n }\n }, [isSupported]);\n\n const release = useCallback(async () => {\n wantedRef.current = false;\n if (sentinelRef.current && !sentinelRef.current.released) {\n await sentinelRef.current.release();\n }\n }, []);\n\n // Re-acquire on tab visibility change\n useEffect(() => {\n if (autoReacquire && visible && wantedRef.current && !sentinelRef.current) {\n void request();\n }\n }, [visible, autoReacquire, request]);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n if (sentinelRef.current && !sentinelRef.current.released) {\n sentinelRef.current.release().catch(() => {});\n }\n };\n }, []);\n\n return { isActive, request, release, isSupported };\n}\n","import { useState, useEffect, useCallback } from \"react\";\n\nexport interface BatteryState {\n /** `true` when the device is plugged in. Defaults to `true` (optimistic). */\n charging: boolean;\n /** Battery level between 0 and 1. Defaults to `1`. */\n level: number;\n /** `true` when the device is **not** charging and `level` is below `lowThreshold`. */\n isLowBattery: boolean;\n /** `true` when the Battery Status API is available. */\n isSupported: boolean;\n}\n\ninterface BatteryManager extends EventTarget {\n charging: boolean;\n level: number;\n}\n\n/**\n * Exposes device battery status via the\n * [Battery Status API](https://developer.mozilla.org/en-US/docs/Web/API/Battery_Status_API).\n *\n * Use cases:\n * - Reduce polling frequency on low battery.\n * - Disable animations / heavy computations when the battery is low.\n * - Show a battery-aware UI indicator.\n *\n * SSR-safe — returns optimistic defaults on the server.\n *\n * @param lowThreshold - Level (0–1) below which `isLowBattery` becomes `true`\n * when the device is not charging (default `0.15`).\n */\nexport function useBatteryAware(lowThreshold = 0.15): BatteryState {\n const [state, setState] = useState<BatteryState>({\n charging: true,\n level: 1,\n isLowBattery: false,\n isSupported: false,\n });\n\n const update = useCallback(\n (battery: BatteryManager) => {\n setState({\n charging: battery.charging,\n level: battery.level,\n isLowBattery: !battery.charging && battery.level < lowThreshold,\n isSupported: true,\n });\n },\n [lowThreshold],\n );\n\n useEffect(() => {\n if (\n typeof navigator === \"undefined\" ||\n typeof (navigator as Navigator & { getBattery?: unknown }).getBattery !== \"function\"\n ) {\n return;\n }\n\n let battery: BatteryManager | undefined;\n let cancelled = false;\n\n const onChange = () => {\n if (battery && !cancelled) update(battery);\n };\n\n (navigator as Navigator & { getBattery: () => Promise<BatteryManager> })\n .getBattery()\n .then((b) => {\n if (cancelled) return;\n battery = b;\n update(b);\n b.addEventListener(\"chargingchange\", onChange);\n b.addEventListener(\"levelchange\", onChange);\n })\n .catch(() => {\n // API exists but call failed — leave defaults\n });\n\n return () => {\n cancelled = true;\n if (battery) {\n battery.removeEventListener(\"chargingchange\", onChange);\n battery.removeEventListener(\"levelchange\", onChange);\n }\n };\n }, [update]);\n\n return state;\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -52,4 +52,150 @@ interface SmartPollingResult<T> {
52
52
  */
53
53
  declare function useSmartPolling<T = unknown>(fetchFn: () => Promise<T>, options?: SmartPollingOptions): SmartPollingResult<T>;
54
54
 
55
- export { type IdleVisibilityResult, type SmartPollingOptions, type SmartPollingResult, useAutoPauseVideo, useDocVisible, useIdleVisibility, useSmartPolling };
55
+ interface PageFocusEffectOptions {
56
+ /** Callback fired when the page transitions from hidden → visible. May return a cleanup function. */
57
+ onVisible?: () => void | (() => void);
58
+ /** Callback fired when the page transitions from visible → hidden. */
59
+ onHidden?: () => void;
60
+ }
61
+ /**
62
+ * Runs side-effect callbacks on visibility **transitions** rather than
63
+ * on every render.
64
+ *
65
+ * - `onVisible` fires when the tab goes from hidden → visible.
66
+ * - `onHidden` fires when the tab goes from visible → hidden.
67
+ * - The very first render is **not** treated as a transition.
68
+ * - `onVisible` may return a cleanup function (like `useEffect`).
69
+ *
70
+ * SSR-safe — no-ops on the server.
71
+ *
72
+ * @example
73
+ * ```tsx
74
+ * usePageFocusEffect({
75
+ * onVisible: () => { refetchStaleData(); },
76
+ * onHidden: () => { saveScrollPosition(); },
77
+ * });
78
+ * ```
79
+ */
80
+ declare function usePageFocusEffect(options: PageFocusEffectOptions): void;
81
+
82
+ interface NetworkAwarePollingOptions extends SmartPollingOptions {
83
+ /** Multiplier applied to `interval` on slow connections like 2g (default `3`). */
84
+ slowMultiplier?: number;
85
+ /** Whether to pause polling when the browser is offline (default `true`). */
86
+ pauseOffline?: boolean;
87
+ }
88
+ interface NetworkAwarePollingResult<T> extends SmartPollingResult<T> {
89
+ /** `true` when the browser reports being online. */
90
+ isOnline: boolean;
91
+ /** The effective polling interval after network-quality adjustment. */
92
+ effectiveInterval: number;
93
+ }
94
+ /**
95
+ * Network + visibility-aware polling.
96
+ *
97
+ * Extends `useSmartPolling` with:
98
+ * - Automatic pause when the browser goes offline.
99
+ * - Adaptive interval — polls slower on poor connections (2g / slow-2g).
100
+ *
101
+ * SSR-safe — defaults to online on the server.
102
+ *
103
+ * @param fetchFn - Async function that returns data.
104
+ * @param options - Optional configuration.
105
+ */
106
+ declare function useNetworkAwarePolling<T = unknown>(fetchFn: () => Promise<T>, options?: NetworkAwarePollingOptions): NetworkAwarePollingResult<T>;
107
+
108
+ interface InactivityTimeoutOptions {
109
+ /** Total inactivity before `onTimeout` fires, in ms (default `300_000` — 5 min). */
110
+ timeout?: number;
111
+ /**
112
+ * How long before the final timeout to enter the "warning" phase, in ms
113
+ * (default `60_000` — 1 min).
114
+ *
115
+ * Set to `0` to disable the warning phase.
116
+ */
117
+ warningBefore?: number;
118
+ /** Called once when the full timeout elapses. */
119
+ onTimeout?: () => void;
120
+ /** Called when entering the warning phase. */
121
+ onWarning?: () => void;
122
+ }
123
+ interface InactivityTimeoutResult {
124
+ /** `true` once the user has been idle long enough to start counting down. */
125
+ idle: boolean;
126
+ /** Current page-visibility state. */
127
+ visible: boolean;
128
+ /** `true` during the warning countdown (before final timeout). */
129
+ isWarning: boolean;
130
+ /** `true` after the full timeout has elapsed. */
131
+ isTimedOut: boolean;
132
+ /** Seconds remaining until timeout (`-1` when the user is not idle). */
133
+ remainingSeconds: number;
134
+ /** Manually reset the timer and cancel any pending timeout. */
135
+ resetTimer: () => void;
136
+ }
137
+ /**
138
+ * Countdown-based session/inactivity manager built on top of
139
+ * `useIdleVisibility`.
140
+ *
141
+ * 1. After `timeout - warningBefore` ms of inactivity the hook enters
142
+ * the **warning** phase and starts a per-second countdown.
143
+ * 2. When the countdown hits zero `onTimeout` fires and `isTimedOut`
144
+ * becomes `true`.
145
+ * 3. Any user interaction resets the timer.
146
+ *
147
+ * SSR-safe — all timers only run in the browser.
148
+ */
149
+ declare function useInactivityTimeout(options?: InactivityTimeoutOptions): InactivityTimeoutResult;
150
+
151
+ interface WakeLockResult {
152
+ /** `true` while a Wake Lock is actively held. */
153
+ isActive: boolean;
154
+ /** Request the screen Wake Lock. No-ops if unsupported. */
155
+ request: () => Promise<void>;
156
+ /** Release the current Wake Lock. */
157
+ release: () => Promise<void>;
158
+ /** `true` when the Screen Wake Lock API is available in this browser. */
159
+ isSupported: boolean;
160
+ }
161
+ /**
162
+ * Manages the [Screen Wake Lock API](https://developer.mozilla.org/en-US/docs/Web/API/Screen_Wake_Lock_API)
163
+ * to prevent the screen from dimming or locking.
164
+ *
165
+ * Features:
166
+ * - Automatic re-acquire on tab re-focus when `autoReacquire` is `true`.
167
+ * - Cleans up the Wake Lock on unmount.
168
+ * - SSR-safe — `isSupported` will be `false` on the server.
169
+ *
170
+ * @param autoReacquire - Re-request the lock when the tab becomes visible
171
+ * again after being hidden (default `true`).
172
+ */
173
+ declare function useWakeLock(autoReacquire?: boolean): WakeLockResult;
174
+
175
+ interface BatteryState {
176
+ /** `true` when the device is plugged in. Defaults to `true` (optimistic). */
177
+ charging: boolean;
178
+ /** Battery level between 0 and 1. Defaults to `1`. */
179
+ level: number;
180
+ /** `true` when the device is **not** charging and `level` is below `lowThreshold`. */
181
+ isLowBattery: boolean;
182
+ /** `true` when the Battery Status API is available. */
183
+ isSupported: boolean;
184
+ }
185
+ /**
186
+ * Exposes device battery status via the
187
+ * [Battery Status API](https://developer.mozilla.org/en-US/docs/Web/API/Battery_Status_API).
188
+ *
189
+ * Use cases:
190
+ * - Reduce polling frequency on low battery.
191
+ * - Disable animations / heavy computations when the battery is low.
192
+ * - Show a battery-aware UI indicator.
193
+ *
194
+ * SSR-safe — returns optimistic defaults on the server.
195
+ *
196
+ * @param lowThreshold - Level (0–1) below which `isLowBattery` becomes `true`
197
+ * when the device is not charging (default `0.15`).
198
+ */
199
+ declare function useBatteryAware(lowThreshold?: number): BatteryState;
200
+
201
+ export { type BatteryState, type IdleVisibilityResult, type InactivityTimeoutOptions, type InactivityTimeoutResult, type NetworkAwarePollingOptions, type NetworkAwarePollingResult, type PageFocusEffectOptions, type SmartPollingOptions, type SmartPollingResult, type WakeLockResult, useAutoPauseVideo, useBatteryAware, useDocVisible, useIdleVisibility, useInactivityTimeout, useNetworkAwarePolling, usePageFocusEffect, useSmartPolling, useWakeLock };
package/dist/index.d.ts CHANGED
@@ -52,4 +52,150 @@ interface SmartPollingResult<T> {
52
52
  */
53
53
  declare function useSmartPolling<T = unknown>(fetchFn: () => Promise<T>, options?: SmartPollingOptions): SmartPollingResult<T>;
54
54
 
55
- export { type IdleVisibilityResult, type SmartPollingOptions, type SmartPollingResult, useAutoPauseVideo, useDocVisible, useIdleVisibility, useSmartPolling };
55
+ interface PageFocusEffectOptions {
56
+ /** Callback fired when the page transitions from hidden → visible. May return a cleanup function. */
57
+ onVisible?: () => void | (() => void);
58
+ /** Callback fired when the page transitions from visible → hidden. */
59
+ onHidden?: () => void;
60
+ }
61
+ /**
62
+ * Runs side-effect callbacks on visibility **transitions** rather than
63
+ * on every render.
64
+ *
65
+ * - `onVisible` fires when the tab goes from hidden → visible.
66
+ * - `onHidden` fires when the tab goes from visible → hidden.
67
+ * - The very first render is **not** treated as a transition.
68
+ * - `onVisible` may return a cleanup function (like `useEffect`).
69
+ *
70
+ * SSR-safe — no-ops on the server.
71
+ *
72
+ * @example
73
+ * ```tsx
74
+ * usePageFocusEffect({
75
+ * onVisible: () => { refetchStaleData(); },
76
+ * onHidden: () => { saveScrollPosition(); },
77
+ * });
78
+ * ```
79
+ */
80
+ declare function usePageFocusEffect(options: PageFocusEffectOptions): void;
81
+
82
+ interface NetworkAwarePollingOptions extends SmartPollingOptions {
83
+ /** Multiplier applied to `interval` on slow connections like 2g (default `3`). */
84
+ slowMultiplier?: number;
85
+ /** Whether to pause polling when the browser is offline (default `true`). */
86
+ pauseOffline?: boolean;
87
+ }
88
+ interface NetworkAwarePollingResult<T> extends SmartPollingResult<T> {
89
+ /** `true` when the browser reports being online. */
90
+ isOnline: boolean;
91
+ /** The effective polling interval after network-quality adjustment. */
92
+ effectiveInterval: number;
93
+ }
94
+ /**
95
+ * Network + visibility-aware polling.
96
+ *
97
+ * Extends `useSmartPolling` with:
98
+ * - Automatic pause when the browser goes offline.
99
+ * - Adaptive interval — polls slower on poor connections (2g / slow-2g).
100
+ *
101
+ * SSR-safe — defaults to online on the server.
102
+ *
103
+ * @param fetchFn - Async function that returns data.
104
+ * @param options - Optional configuration.
105
+ */
106
+ declare function useNetworkAwarePolling<T = unknown>(fetchFn: () => Promise<T>, options?: NetworkAwarePollingOptions): NetworkAwarePollingResult<T>;
107
+
108
+ interface InactivityTimeoutOptions {
109
+ /** Total inactivity before `onTimeout` fires, in ms (default `300_000` — 5 min). */
110
+ timeout?: number;
111
+ /**
112
+ * How long before the final timeout to enter the "warning" phase, in ms
113
+ * (default `60_000` — 1 min).
114
+ *
115
+ * Set to `0` to disable the warning phase.
116
+ */
117
+ warningBefore?: number;
118
+ /** Called once when the full timeout elapses. */
119
+ onTimeout?: () => void;
120
+ /** Called when entering the warning phase. */
121
+ onWarning?: () => void;
122
+ }
123
+ interface InactivityTimeoutResult {
124
+ /** `true` once the user has been idle long enough to start counting down. */
125
+ idle: boolean;
126
+ /** Current page-visibility state. */
127
+ visible: boolean;
128
+ /** `true` during the warning countdown (before final timeout). */
129
+ isWarning: boolean;
130
+ /** `true` after the full timeout has elapsed. */
131
+ isTimedOut: boolean;
132
+ /** Seconds remaining until timeout (`-1` when the user is not idle). */
133
+ remainingSeconds: number;
134
+ /** Manually reset the timer and cancel any pending timeout. */
135
+ resetTimer: () => void;
136
+ }
137
+ /**
138
+ * Countdown-based session/inactivity manager built on top of
139
+ * `useIdleVisibility`.
140
+ *
141
+ * 1. After `timeout - warningBefore` ms of inactivity the hook enters
142
+ * the **warning** phase and starts a per-second countdown.
143
+ * 2. When the countdown hits zero `onTimeout` fires and `isTimedOut`
144
+ * becomes `true`.
145
+ * 3. Any user interaction resets the timer.
146
+ *
147
+ * SSR-safe — all timers only run in the browser.
148
+ */
149
+ declare function useInactivityTimeout(options?: InactivityTimeoutOptions): InactivityTimeoutResult;
150
+
151
+ interface WakeLockResult {
152
+ /** `true` while a Wake Lock is actively held. */
153
+ isActive: boolean;
154
+ /** Request the screen Wake Lock. No-ops if unsupported. */
155
+ request: () => Promise<void>;
156
+ /** Release the current Wake Lock. */
157
+ release: () => Promise<void>;
158
+ /** `true` when the Screen Wake Lock API is available in this browser. */
159
+ isSupported: boolean;
160
+ }
161
+ /**
162
+ * Manages the [Screen Wake Lock API](https://developer.mozilla.org/en-US/docs/Web/API/Screen_Wake_Lock_API)
163
+ * to prevent the screen from dimming or locking.
164
+ *
165
+ * Features:
166
+ * - Automatic re-acquire on tab re-focus when `autoReacquire` is `true`.
167
+ * - Cleans up the Wake Lock on unmount.
168
+ * - SSR-safe — `isSupported` will be `false` on the server.
169
+ *
170
+ * @param autoReacquire - Re-request the lock when the tab becomes visible
171
+ * again after being hidden (default `true`).
172
+ */
173
+ declare function useWakeLock(autoReacquire?: boolean): WakeLockResult;
174
+
175
+ interface BatteryState {
176
+ /** `true` when the device is plugged in. Defaults to `true` (optimistic). */
177
+ charging: boolean;
178
+ /** Battery level between 0 and 1. Defaults to `1`. */
179
+ level: number;
180
+ /** `true` when the device is **not** charging and `level` is below `lowThreshold`. */
181
+ isLowBattery: boolean;
182
+ /** `true` when the Battery Status API is available. */
183
+ isSupported: boolean;
184
+ }
185
+ /**
186
+ * Exposes device battery status via the
187
+ * [Battery Status API](https://developer.mozilla.org/en-US/docs/Web/API/Battery_Status_API).
188
+ *
189
+ * Use cases:
190
+ * - Reduce polling frequency on low battery.
191
+ * - Disable animations / heavy computations when the battery is low.
192
+ * - Show a battery-aware UI indicator.
193
+ *
194
+ * SSR-safe — returns optimistic defaults on the server.
195
+ *
196
+ * @param lowThreshold - Level (0–1) below which `isLowBattery` becomes `true`
197
+ * when the device is not charging (default `0.15`).
198
+ */
199
+ declare function useBatteryAware(lowThreshold?: number): BatteryState;
200
+
201
+ export { type BatteryState, type IdleVisibilityResult, type InactivityTimeoutOptions, type InactivityTimeoutResult, type NetworkAwarePollingOptions, type NetworkAwarePollingResult, type PageFocusEffectOptions, type SmartPollingOptions, type SmartPollingResult, type WakeLockResult, useAutoPauseVideo, useBatteryAware, useDocVisible, useIdleVisibility, useInactivityTimeout, useNetworkAwarePolling, usePageFocusEffect, useSmartPolling, useWakeLock };
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
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
1
+ import {useState,useCallback,useEffect,useRef}from'react';function m(){let[i,n]=useState(()=>typeof document=="undefined"?true:document.visibilityState==="visible"),t=useCallback(()=>{n(document.visibilityState==="visible");},[]);return useEffect(()=>{if(typeof document!="undefined")return t(),document.addEventListener("visibilitychange",t),()=>document.removeEventListener("visibilitychange",t)},[t]),i}function T(i=6e4){let n=m(),[t,r]=useState(false),e=useRef(void 0),o=useCallback(()=>{r(false),clearTimeout(e.current),e.current=setTimeout(()=>r(true),i);},[i]);return useEffect(()=>{if(typeof window=="undefined")return;let s=["mousemove","keydown","pointerdown","scroll","touchstart"];return s.forEach(u=>window.addEventListener(u,o,{passive:true})),o(),()=>{s.forEach(u=>window.removeEventListener(u,o)),clearTimeout(e.current);}},[o]),useEffect(()=>{n&&o();},[n,o]),{visible:n,idle:t}}function Q(i){let n=m(),t=useRef(false);useEffect(()=>{let r=i.current;r&&(!n&&!r.paused?(r.pause(),t.current=true):n&&t.current&&(r.play().catch(()=>{}),t.current=false));},[n,i]);}function I(i,n){let{interval:t=5e3,enabled:r=true}=n!=null?n:{},e=m(),[o,s]=useState(void 0),[u,l]=useState(true),[c,b]=useState(void 0),g=useRef(i),p=useRef(void 0),f=useRef(false),w=useRef(void 0),v=useRef(true);g.current=i,useEffect(()=>(v.current=true,()=>{v.current=false;}),[]);let d=useCallback(async()=>{if(!f.current){f.current=true;try{let a=await g.current();if(!v.current)return;let x=JSON.stringify(a);x!==w.current&&(w.current=x,s(a)),b(k=>k!==void 0?void 0:k);}catch(a){if(!v.current)return;b(a instanceof Error?a:new Error(String(a)));}finally{f.current=false,v.current&&l(a=>a&&false);}}},[]),y=useRef(false);return useEffect(()=>{y.current||(y.current=true,d());},[d]),useEffect(()=>{if(!r||!e){clearInterval(p.current),p.current=void 0;return}return d(),p.current=setInterval(d,t),()=>{clearInterval(p.current),p.current=void 0;}},[e,r,t,d]),{data:o,isLoading:u,isError:c!==void 0,error:c,refetch:d}}function Y(i){let n=m(),t=useRef(void 0),r=useRef(void 0),e=useRef(i.onVisible),o=useRef(i.onHidden);e.current=i.onVisible,o.current=i.onHidden,useEffect(()=>{var s,u,l;if(t.current===void 0){t.current=n;return}if(n&&!t.current){(s=r.current)==null||s.call(r),r.current=void 0;let c=(u=e.current)==null?void 0:u.call(e);typeof c=="function"&&(r.current=c);}else !n&&t.current&&((l=o.current)==null||l.call(o));t.current=n;},[n]),useEffect(()=>()=>{var s;(s=r.current)==null||s.call(r);},[]);}function ee(i,n){let{interval:t=5e3,enabled:r=true,slowMultiplier:e=3,pauseOffline:o=true,...s}=n!=null?n:{},u=te(),l=ne(t,e);return {...I(i,{...s,interval:l,enabled:o?r&&u:r}),isOnline:u,effectiveInterval:l}}function te(){let[i,n]=useState(()=>typeof navigator=="undefined"?true:navigator.onLine),t=useCallback(()=>n(true),[]),r=useCallback(()=>n(false),[]);return useEffect(()=>{if(typeof window!="undefined")return window.addEventListener("online",t),window.addEventListener("offline",r),()=>{window.removeEventListener("online",t),window.removeEventListener("offline",r);}},[t,r]),i}function ne(i,n){if(typeof navigator=="undefined")return i;let t=navigator.connection;return (t==null?void 0:t.effectiveType)==="2g"||(t==null?void 0:t.effectiveType)==="slow-2g"?i*n:i}function oe(i){let{timeout:n=3e5,warningBefore:t=6e4,onTimeout:r,onWarning:e}=i!=null?i:{},o=Math.min(t,n),s=n-o,{idle:u,visible:l}=T(s),[c,b]=useState(-1),[g,p]=useState(false),f=useRef(void 0),w=useRef(void 0),v=useRef(false),d=useRef(r),y=useRef(e);d.current=r,y.current=e;let a=useCallback(()=>{clearInterval(f.current),f.current=void 0,w.current=void 0,v.current=false,b(-1),p(false);},[]);useEffect(()=>{var L;if(!u||g){u||a();return}w.current=Date.now(),v.current||(v.current=true,(L=y.current)==null||L.call(y));let k=o;return f.current=setInterval(()=>{var B,V;let H=Date.now()-((B=w.current)!=null?B:Date.now()),O=Math.max(0,Math.ceil((k-H)/1e3));b(O),O<=0&&(clearInterval(f.current),f.current=void 0,p(true),(V=d.current)==null||V.call(d));},1e3),()=>{clearInterval(f.current),f.current=void 0;}},[u,g,o,a]);let x=u&&!g&&c>=0;return {idle:u||g,visible:l,isWarning:x,isTimedOut:g,remainingSeconds:c,resetTimer:a}}function se(i=true){let n=m(),[t,r]=useState(false),e=useRef(void 0),o=useRef(false),s=typeof navigator!="undefined"&&navigator.wakeLock!=null,u=useCallback(async()=>{if(s&&!(e.current&&!e.current.released))try{let c=await navigator.wakeLock.request("screen");e.current=c,o.current=!0,r(!0),c.addEventListener("release",()=>{r(!1),e.current=void 0;});}catch{}},[s]),l=useCallback(async()=>{o.current=false,e.current&&!e.current.released&&await e.current.release();},[]);return useEffect(()=>{i&&n&&o.current&&!e.current&&u();},[n,i,u]),useEffect(()=>()=>{e.current&&!e.current.released&&e.current.release().catch(()=>{});},[]),{isActive:t,request:u,release:l,isSupported:s}}function fe(i=.15){let[n,t]=useState({charging:true,level:1,isLowBattery:false,isSupported:false}),r=useCallback(e=>{t({charging:e.charging,level:e.level,isLowBattery:!e.charging&&e.level<i,isSupported:true});},[i]);return useEffect(()=>{if(typeof navigator=="undefined"||typeof navigator.getBattery!="function")return;let e,o=false,s=()=>{e&&!o&&r(e);};return navigator.getBattery().then(u=>{o||(e=u,r(u),u.addEventListener("chargingchange",s),u.addEventListener("levelchange",s));}).catch(()=>{}),()=>{o=true,e&&(e.removeEventListener("chargingchange",s),e.removeEventListener("levelchange",s));}},[r]),n}export{Q as useAutoPauseVideo,fe as useBatteryAware,m as useDocVisible,T as useIdleVisibility,oe as useInactivityTimeout,ee as useNetworkAwarePolling,Y as usePageFocusEffect,I as useSmartPolling,se as useWakeLock};//# 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"],"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"]}
1
+ {"version":3,"sources":["../src/useDocVisible.ts","../src/useIdleVisibility.ts","../src/useAutoPauseVideo.ts","../src/useSmartPolling.ts","../src/usePageFocusEffect.ts","../src/useNetworkAwarePolling.ts","../src/useInactivityTimeout.ts","../src/useWakeLock.ts","../src/useBatteryAware.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","usePageFocusEffect","prevRef","cleanupRef","onVisibleRef","onHiddenRef","_a","_b","_c","useNetworkAwarePolling","slowMultiplier","pauseOffline","rest","online","useOnline","effectiveInterval","getEffectiveInterval","setOnline","handleOnline","handleOffline","base","multiplier","conn","useInactivityTimeout","warningBefore","onTimeout","onWarning","clampedWarning","idleThreshold","remaining","setRemaining","timedOut","setTimedOut","intervalRef","startRef","warnedRef","onTimeoutRef","onWarningRef","clear","warningMs","elapsed","left","isWarning","useWakeLock","autoReacquire","isActive","setIsActive","sentinelRef","wantedRef","isSupported","request","sentinel","release","useBatteryAware","lowThreshold","state","setState","update","battery","cancelled","onChange","b"],"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","import { useEffect, useRef } from \"react\";\nimport { useDocVisible } from \"./useDocVisible\";\n\nexport interface PageFocusEffectOptions {\n /** Callback fired when the page transitions from hidden → visible. May return a cleanup function. */\n onVisible?: () => void | (() => void);\n /** Callback fired when the page transitions from visible → hidden. */\n onHidden?: () => void;\n}\n\n/**\n * Runs side-effect callbacks on visibility **transitions** rather than\n * on every render.\n *\n * - `onVisible` fires when the tab goes from hidden → visible.\n * - `onHidden` fires when the tab goes from visible → hidden.\n * - The very first render is **not** treated as a transition.\n * - `onVisible` may return a cleanup function (like `useEffect`).\n *\n * SSR-safe — no-ops on the server.\n *\n * @example\n * ```tsx\n * usePageFocusEffect({\n * onVisible: () => { refetchStaleData(); },\n * onHidden: () => { saveScrollPosition(); },\n * });\n * ```\n */\nexport function usePageFocusEffect(options: PageFocusEffectOptions): void {\n const visible = useDocVisible();\n const prevRef = useRef<boolean | undefined>(undefined);\n const cleanupRef = useRef<(() => void) | undefined>(undefined);\n\n // Keep latest callbacks in refs to avoid re-triggering the effect\n const onVisibleRef = useRef(options.onVisible);\n const onHiddenRef = useRef(options.onHidden);\n onVisibleRef.current = options.onVisible;\n onHiddenRef.current = options.onHidden;\n\n useEffect(() => {\n // Skip the initial mount — only fire on actual transitions\n if (prevRef.current === undefined) {\n prevRef.current = visible;\n return;\n }\n\n if (visible && !prevRef.current) {\n // hidden → visible\n cleanupRef.current?.();\n cleanupRef.current = undefined;\n const result = onVisibleRef.current?.();\n if (typeof result === \"function\") {\n cleanupRef.current = result;\n }\n } else if (!visible && prevRef.current) {\n // visible → hidden\n onHiddenRef.current?.();\n }\n\n prevRef.current = visible;\n }, [visible]);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n cleanupRef.current?.();\n };\n }, []);\n}\n","import { useState, useEffect, useCallback } from \"react\";\nimport { useSmartPolling } from \"./useSmartPolling\";\nimport type { SmartPollingOptions, SmartPollingResult } from \"./useSmartPolling\";\n\nexport interface NetworkAwarePollingOptions extends SmartPollingOptions {\n /** Multiplier applied to `interval` on slow connections like 2g (default `3`). */\n slowMultiplier?: number;\n /** Whether to pause polling when the browser is offline (default `true`). */\n pauseOffline?: boolean;\n}\n\nexport interface NetworkAwarePollingResult<T> extends SmartPollingResult<T> {\n /** `true` when the browser reports being online. */\n isOnline: boolean;\n /** The effective polling interval after network-quality adjustment. */\n effectiveInterval: number;\n}\n\n/**\n * Network + visibility-aware polling.\n *\n * Extends `useSmartPolling` with:\n * - Automatic pause when the browser goes offline.\n * - Adaptive interval — polls slower on poor connections (2g / slow-2g).\n *\n * SSR-safe — defaults to online on the server.\n *\n * @param fetchFn - Async function that returns data.\n * @param options - Optional configuration.\n */\nexport function useNetworkAwarePolling<T = unknown>(\n fetchFn: () => Promise<T>,\n options?: NetworkAwarePollingOptions,\n): NetworkAwarePollingResult<T> {\n const {\n interval = 5000,\n enabled = true,\n slowMultiplier = 3,\n pauseOffline = true,\n ...rest\n } = options ?? {};\n\n const online = useOnline();\n const effectiveInterval = getEffectiveInterval(interval, slowMultiplier);\n\n const shouldPoll = pauseOffline ? enabled && online : enabled;\n\n const result = useSmartPolling(fetchFn, {\n ...rest,\n interval: effectiveInterval,\n enabled: shouldPoll,\n });\n\n return { ...result, isOnline: online, effectiveInterval };\n}\n\n/* ------------------------------------------------------------------ */\n/* Internal helpers */\n/* ------------------------------------------------------------------ */\n\n/** SSR-safe hook that tracks `navigator.onLine`. */\nfunction useOnline(): boolean {\n const [online, setOnline] = useState(() =>\n typeof navigator === \"undefined\" ? true : navigator.onLine,\n );\n\n const handleOnline = useCallback(() => setOnline(true), []);\n const handleOffline = useCallback(() => setOnline(false), []);\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n\n window.addEventListener(\"online\", handleOnline);\n window.addEventListener(\"offline\", handleOffline);\n return () => {\n window.removeEventListener(\"online\", handleOnline);\n window.removeEventListener(\"offline\", handleOffline);\n };\n }, [handleOnline, handleOffline]);\n\n return online;\n}\n\n/** Returns a longer interval when the connection is slow. */\nfunction getEffectiveInterval(base: number, multiplier: number): number {\n if (typeof navigator === \"undefined\") return base;\n\n const conn = (\n navigator as Navigator & {\n connection?: { effectiveType?: string };\n }\n ).connection;\n\n if (conn?.effectiveType === \"2g\" || conn?.effectiveType === \"slow-2g\") {\n return base * multiplier;\n }\n return base;\n}\n","import { useState, useEffect, useRef, useCallback } from \"react\";\nimport { useIdleVisibility } from \"./useIdleVisibility\";\n\nexport interface InactivityTimeoutOptions {\n /** Total inactivity before `onTimeout` fires, in ms (default `300_000` — 5 min). */\n timeout?: number;\n /**\n * How long before the final timeout to enter the \"warning\" phase, in ms\n * (default `60_000` — 1 min).\n *\n * Set to `0` to disable the warning phase.\n */\n warningBefore?: number;\n /** Called once when the full timeout elapses. */\n onTimeout?: () => void;\n /** Called when entering the warning phase. */\n onWarning?: () => void;\n}\n\nexport interface InactivityTimeoutResult {\n /** `true` once the user has been idle long enough to start counting down. */\n idle: boolean;\n /** Current page-visibility state. */\n visible: boolean;\n /** `true` during the warning countdown (before final timeout). */\n isWarning: boolean;\n /** `true` after the full timeout has elapsed. */\n isTimedOut: boolean;\n /** Seconds remaining until timeout (`-1` when the user is not idle). */\n remainingSeconds: number;\n /** Manually reset the timer and cancel any pending timeout. */\n resetTimer: () => void;\n}\n\n/**\n * Countdown-based session/inactivity manager built on top of\n * `useIdleVisibility`.\n *\n * 1. After `timeout - warningBefore` ms of inactivity the hook enters\n * the **warning** phase and starts a per-second countdown.\n * 2. When the countdown hits zero `onTimeout` fires and `isTimedOut`\n * becomes `true`.\n * 3. Any user interaction resets the timer.\n *\n * SSR-safe — all timers only run in the browser.\n */\nexport function useInactivityTimeout(\n options?: InactivityTimeoutOptions,\n): InactivityTimeoutResult {\n const {\n timeout = 300_000,\n warningBefore = 60_000,\n onTimeout,\n onWarning,\n } = options ?? {};\n\n // Clamp so warningBefore never exceeds timeout\n const clampedWarning = Math.min(warningBefore, timeout);\n const idleThreshold = timeout - clampedWarning;\n\n const { idle, visible } = useIdleVisibility(idleThreshold);\n\n const [remaining, setRemaining] = useState(-1);\n const [timedOut, setTimedOut] = useState(false);\n const intervalRef = useRef<ReturnType<typeof setInterval> | undefined>(undefined);\n const startRef = useRef<number | undefined>(undefined);\n const warnedRef = useRef(false);\n\n // Keep callback refs stable\n const onTimeoutRef = useRef(onTimeout);\n const onWarningRef = useRef(onWarning);\n onTimeoutRef.current = onTimeout;\n onWarningRef.current = onWarning;\n\n const clear = useCallback(() => {\n clearInterval(intervalRef.current);\n intervalRef.current = undefined;\n startRef.current = undefined;\n warnedRef.current = false;\n setRemaining(-1);\n setTimedOut(false);\n }, []);\n\n useEffect(() => {\n if (!idle || timedOut) {\n if (!idle) clear();\n return;\n }\n\n startRef.current = Date.now();\n\n // Fire the warning callback once at the start of the countdown\n if (!warnedRef.current) {\n warnedRef.current = true;\n onWarningRef.current?.();\n }\n\n const warningMs = clampedWarning;\n\n intervalRef.current = setInterval(() => {\n const elapsed = Date.now() - (startRef.current ?? Date.now());\n const left = Math.max(0, Math.ceil((warningMs - elapsed) / 1000));\n setRemaining(left);\n\n if (left <= 0) {\n clearInterval(intervalRef.current);\n intervalRef.current = undefined;\n setTimedOut(true);\n onTimeoutRef.current?.();\n }\n }, 1000);\n\n return () => {\n clearInterval(intervalRef.current);\n intervalRef.current = undefined;\n };\n }, [idle, timedOut, clampedWarning, clear]);\n\n const isWarning = idle && !timedOut && remaining >= 0;\n\n return {\n idle: idle || timedOut,\n visible,\n isWarning,\n isTimedOut: timedOut,\n remainingSeconds: remaining,\n resetTimer: clear,\n };\n}\n","import { useState, useCallback, useEffect, useRef } from \"react\";\nimport { useDocVisible } from \"./useDocVisible\";\n\nexport interface WakeLockResult {\n /** `true` while a Wake Lock is actively held. */\n isActive: boolean;\n /** Request the screen Wake Lock. No-ops if unsupported. */\n request: () => Promise<void>;\n /** Release the current Wake Lock. */\n release: () => Promise<void>;\n /** `true` when the Screen Wake Lock API is available in this browser. */\n isSupported: boolean;\n}\n\n/**\n * Manages the [Screen Wake Lock API](https://developer.mozilla.org/en-US/docs/Web/API/Screen_Wake_Lock_API)\n * to prevent the screen from dimming or locking.\n *\n * Features:\n * - Automatic re-acquire on tab re-focus when `autoReacquire` is `true`.\n * - Cleans up the Wake Lock on unmount.\n * - SSR-safe — `isSupported` will be `false` on the server.\n *\n * @param autoReacquire - Re-request the lock when the tab becomes visible\n * again after being hidden (default `true`).\n */\nexport function useWakeLock(autoReacquire = true): WakeLockResult {\n const visible = useDocVisible();\n const [isActive, setIsActive] = useState(false);\n const sentinelRef = useRef<WakeLockSentinel | undefined>(undefined);\n const wantedRef = useRef(false);\n\n const isSupported = typeof navigator !== \"undefined\" && navigator.wakeLock != null;\n\n const request = useCallback(async () => {\n if (!isSupported) return;\n // Already holding a lock — skip\n if (sentinelRef.current && !sentinelRef.current.released) return;\n\n try {\n const sentinel = await navigator.wakeLock.request(\"screen\");\n sentinelRef.current = sentinel;\n wantedRef.current = true;\n setIsActive(true);\n\n sentinel.addEventListener(\"release\", () => {\n setIsActive(false);\n sentinelRef.current = undefined;\n });\n } catch {\n // Permission denied, low battery, or other platform error\n }\n }, [isSupported]);\n\n const release = useCallback(async () => {\n wantedRef.current = false;\n if (sentinelRef.current && !sentinelRef.current.released) {\n await sentinelRef.current.release();\n }\n }, []);\n\n // Re-acquire on tab visibility change\n useEffect(() => {\n if (autoReacquire && visible && wantedRef.current && !sentinelRef.current) {\n void request();\n }\n }, [visible, autoReacquire, request]);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n if (sentinelRef.current && !sentinelRef.current.released) {\n sentinelRef.current.release().catch(() => {});\n }\n };\n }, []);\n\n return { isActive, request, release, isSupported };\n}\n","import { useState, useEffect, useCallback } from \"react\";\n\nexport interface BatteryState {\n /** `true` when the device is plugged in. Defaults to `true` (optimistic). */\n charging: boolean;\n /** Battery level between 0 and 1. Defaults to `1`. */\n level: number;\n /** `true` when the device is **not** charging and `level` is below `lowThreshold`. */\n isLowBattery: boolean;\n /** `true` when the Battery Status API is available. */\n isSupported: boolean;\n}\n\ninterface BatteryManager extends EventTarget {\n charging: boolean;\n level: number;\n}\n\n/**\n * Exposes device battery status via the\n * [Battery Status API](https://developer.mozilla.org/en-US/docs/Web/API/Battery_Status_API).\n *\n * Use cases:\n * - Reduce polling frequency on low battery.\n * - Disable animations / heavy computations when the battery is low.\n * - Show a battery-aware UI indicator.\n *\n * SSR-safe — returns optimistic defaults on the server.\n *\n * @param lowThreshold - Level (0–1) below which `isLowBattery` becomes `true`\n * when the device is not charging (default `0.15`).\n */\nexport function useBatteryAware(lowThreshold = 0.15): BatteryState {\n const [state, setState] = useState<BatteryState>({\n charging: true,\n level: 1,\n isLowBattery: false,\n isSupported: false,\n });\n\n const update = useCallback(\n (battery: BatteryManager) => {\n setState({\n charging: battery.charging,\n level: battery.level,\n isLowBattery: !battery.charging && battery.level < lowThreshold,\n isSupported: true,\n });\n },\n [lowThreshold],\n );\n\n useEffect(() => {\n if (\n typeof navigator === \"undefined\" ||\n typeof (navigator as Navigator & { getBattery?: unknown }).getBattery !== \"function\"\n ) {\n return;\n }\n\n let battery: BatteryManager | undefined;\n let cancelled = false;\n\n const onChange = () => {\n if (battery && !cancelled) update(battery);\n };\n\n (navigator as Navigator & { getBattery: () => Promise<BatteryManager> })\n .getBattery()\n .then((b) => {\n if (cancelled) return;\n battery = b;\n update(b);\n b.addEventListener(\"chargingchange\", onChange);\n b.addEventListener(\"levelchange\", onChange);\n })\n .catch(() => {\n // API exists but call failed — leave defaults\n });\n\n return () => {\n cancelled = true;\n if (battery) {\n battery.removeEventListener(\"chargingchange\", onChange);\n battery.removeEventListener(\"levelchange\", onChange);\n }\n };\n }, [update]);\n\n return state;\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "react-visibility-hooks",
3
- "version": "1.0.13",
4
- "description": "Tiny, SSR-safe React hooks for page visibility, idle detection, smart polling and auto-pause video",
3
+ "version": "3.0.0",
4
+ "description": "Tiny, SSR-safe React hooks for page visibility, idle detection, smart polling, network awareness, wake lock, battery status and more",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
7
7
  "module": "./dist/index.js",
@@ -50,7 +50,14 @@
50
50
  "ssr",
51
51
  "ssr-safe",
52
52
  "nextjs",
53
- "typescript"
53
+ "typescript",
54
+ "wake-lock",
55
+ "battery",
56
+ "network-aware",
57
+ "session-timeout",
58
+ "focus-effect",
59
+ "online-offline",
60
+ "inactivity"
54
61
  ],
55
62
  "author": "Aniket Raj",
56
63
  "license": "MIT",
@@ -92,7 +99,7 @@
92
99
  "size-limit": [
93
100
  {
94
101
  "path": "dist/index.js",
95
- "limit": "1.5 kB"
102
+ "limit": "3 kB"
96
103
  }
97
104
  ],
98
105
  "engines": {