@pol-studios/hooks 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,357 @@
1
+ # @pol-studios/hooks
2
+
3
+ > React hooks for POL applications
4
+
5
+ A comprehensive collection of React hooks for state management, storage, device detection, network status, file handling, toasts, forms, and debugging.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pnpm add @pol-studios/hooks
11
+ ```
12
+
13
+ ## Peer Dependencies
14
+
15
+ ```bash
16
+ pnpm add react react-dom @pol-studios/features @pol-studios/utils
17
+ ```
18
+
19
+ ## Quick Start
20
+
21
+ ```tsx
22
+ import {
23
+ useLocalStorage,
24
+ useOnline,
25
+ useMobile,
26
+ useToast,
27
+ } from "@pol-studios/hooks";
28
+
29
+ function MyComponent() {
30
+ // Persist state to localStorage
31
+ const [theme, setTheme] = useLocalStorage("theme", "light");
32
+
33
+ // Check network status
34
+ const isOnline = useOnline();
35
+
36
+ // Check device type
37
+ const isMobile = useMobile();
38
+
39
+ // Show toast notifications
40
+ const { toast } = useToast();
41
+
42
+ return (
43
+ <button onClick={() => toast({ title: "Hello!" })}>
44
+ Show Toast
45
+ </button>
46
+ );
47
+ }
48
+ ```
49
+
50
+ ## Subpath Exports
51
+
52
+ | Path | Description |
53
+ |------|-------------|
54
+ | `@pol-studios/hooks` | All exports |
55
+ | `@pol-studios/hooks/state` | State management hooks (usePartialState, useQueuedState, useDelayedValue) |
56
+ | `@pol-studios/hooks/scroll` | Scroll hooks (useScrollPosition, useScrollDirection, useHideOnScroll) |
57
+ | `@pol-studios/hooks/storage` | Storage hooks (useLocalStorage, useSessionStorage, useIndexedDB) |
58
+ | `@pol-studios/hooks/device` | Device hooks (useDevice, useMobile, useTheme, useDimensions) |
59
+ | `@pol-studios/hooks/network` | Network hooks (useOnline, useNetworkStatus, useMutationQueue) |
60
+ | `@pol-studios/hooks/lifecycle` | Lifecycle hooks (useMounted, useAfterMount, useOnScreen) |
61
+ | `@pol-studios/hooks/file` | File hooks (useFileDialog, useDownload) |
62
+ | `@pol-studios/hooks/toast` | Toast hooks (useToast, toast) |
63
+ | `@pol-studios/hooks/form` | Form hooks (OnValueChangedContext) |
64
+ | `@pol-studios/hooks/debug` | Debug hooks (useRenderCount, useStateTracker) |
65
+ | `@pol-studios/hooks/utils` | Utilities (withComponentMemo, isDevEnvironment) |
66
+
67
+ ## API Reference
68
+
69
+ ### State Hooks
70
+
71
+ #### usePartialState
72
+ Partial state updates without spreading.
73
+
74
+ ```tsx
75
+ import { usePartialState } from "@pol-studios/hooks/state";
76
+
77
+ const [state, updateState] = usePartialState({ name: "", email: "" });
78
+ updateState({ name: "John" }); // Only updates name, keeps email
79
+ ```
80
+
81
+ #### useQueuedState
82
+ Queue state updates for batching.
83
+
84
+ ```tsx
85
+ import { useQueuedState } from "@pol-studios/hooks/state";
86
+
87
+ const [state, queueUpdate, flush] = useQueuedState(initialState);
88
+ ```
89
+
90
+ #### useDelayedValue
91
+ Debounce value changes.
92
+
93
+ ```tsx
94
+ import { useDelayedValue, useDelayedState } from "@pol-studios/hooks/state";
95
+
96
+ const debouncedSearch = useDelayedValue(searchTerm, 300);
97
+ const [value, setValue, debouncedValue] = useDelayedState("", 300);
98
+ ```
99
+
100
+ #### useChangeTracking
101
+ Track changes to state values.
102
+
103
+ ```tsx
104
+ import { useChangeTracking, ChangeTrackingProvider } from "@pol-studios/hooks/state";
105
+
106
+ const { hasChanges, trackChange, resetChanges } = useChangeTracking();
107
+ ```
108
+
109
+ ### Storage Hooks
110
+
111
+ #### useLocalStorage
112
+ Persist state to localStorage.
113
+
114
+ ```tsx
115
+ import { useLocalStorage, useLocalStorageState } from "@pol-studios/hooks/storage";
116
+
117
+ const [value, setValue] = useLocalStorage("key", defaultValue);
118
+ ```
119
+
120
+ #### useSessionStorage
121
+ Persist state to sessionStorage.
122
+
123
+ ```tsx
124
+ import { useSessionStorage } from "@pol-studios/hooks/storage";
125
+
126
+ const [value, setValue] = useSessionStorage("key", defaultValue);
127
+ ```
128
+
129
+ #### useIndexedDB
130
+ Interact with IndexedDB.
131
+
132
+ ```tsx
133
+ import { useIndexedDB, IndexedDBService } from "@pol-studios/hooks/storage";
134
+
135
+ const db = useIndexedDB({ dbName: "myDb", storeName: "myStore" });
136
+ await db.set("key", value);
137
+ const data = await db.get("key");
138
+ ```
139
+
140
+ ### Device Hooks
141
+
142
+ #### useMobile
143
+ Detect mobile devices.
144
+
145
+ ```tsx
146
+ import { useMobile, useIsMobile } from "@pol-studios/hooks/device";
147
+
148
+ const isMobile = useMobile();
149
+ ```
150
+
151
+ #### useTheme
152
+ Detect and manage theme preference.
153
+
154
+ ```tsx
155
+ import { useTheme, useThemeDetector } from "@pol-studios/hooks/device";
156
+
157
+ const theme = useThemeDetector(); // "light" | "dark"
158
+ ```
159
+
160
+ #### useDimensions
161
+ Track element dimensions.
162
+
163
+ ```tsx
164
+ import { useDimensions, useWindowDimensions } from "@pol-studios/hooks/device";
165
+
166
+ const [ref, dimensions] = useDimensions();
167
+ const { width, height } = useWindowDimensions();
168
+ ```
169
+
170
+ #### useReducedMotion
171
+ Respect user motion preferences.
172
+
173
+ ```tsx
174
+ import { useReducedMotion } from "@pol-studios/hooks/device";
175
+
176
+ const prefersReducedMotion = useReducedMotion();
177
+ ```
178
+
179
+ ### Network Hooks
180
+
181
+ #### useOnline
182
+ Check online status.
183
+
184
+ ```tsx
185
+ import { useOnline, useNetworkStatus } from "@pol-studios/hooks/network";
186
+
187
+ const isOnline = useOnline();
188
+ const { online, effectiveType } = useNetworkStatus();
189
+ ```
190
+
191
+ #### useMutationQueue
192
+ Queue mutations for offline support.
193
+
194
+ ```tsx
195
+ import { useMutationQueue } from "@pol-studios/hooks/network";
196
+
197
+ const { queue, addMutation, processMutations } = useMutationQueue();
198
+ ```
199
+
200
+ ### Scroll Hooks
201
+
202
+ #### useScrollPosition
203
+ Track scroll position.
204
+
205
+ ```tsx
206
+ import { useScrollPosition, useScrollDirection } from "@pol-studios/hooks/scroll";
207
+
208
+ const scrollY = useScrollPosition();
209
+ const direction = useScrollDirection(); // "up" | "down"
210
+ ```
211
+
212
+ #### useHideOnScroll
213
+ Hide elements on scroll down.
214
+
215
+ ```tsx
216
+ import { useHideOnScroll } from "@pol-studios/hooks/scroll";
217
+
218
+ const isHidden = useHideOnScroll();
219
+ ```
220
+
221
+ ### Lifecycle Hooks
222
+
223
+ #### useMounted
224
+ Check if component is mounted.
225
+
226
+ ```tsx
227
+ import { useMounted, useAfterMount } from "@pol-studios/hooks/lifecycle";
228
+
229
+ const isMounted = useMounted();
230
+
231
+ useAfterMount(() => {
232
+ // Runs after first render
233
+ });
234
+ ```
235
+
236
+ #### useOnScreen
237
+ Detect element visibility.
238
+
239
+ ```tsx
240
+ import { useOnScreen } from "@pol-studios/hooks/lifecycle";
241
+
242
+ const [ref, isVisible] = useOnScreen({ threshold: 0.5 });
243
+ ```
244
+
245
+ ### File Hooks
246
+
247
+ #### useFileDialog
248
+ Open file picker dialog.
249
+
250
+ ```tsx
251
+ import { useFileDialog } from "@pol-studios/hooks/file";
252
+
253
+ const { openDialog, files } = useFileDialog({ accept: "image/*" });
254
+ ```
255
+
256
+ #### useDownload
257
+ Download files programmatically.
258
+
259
+ ```tsx
260
+ import { useDownload, useDownloadApiFile } from "@pol-studios/hooks/file";
261
+
262
+ const download = useDownload();
263
+ download(blob, "filename.pdf");
264
+ ```
265
+
266
+ ### Toast Hooks
267
+
268
+ #### useToast
269
+ Show toast notifications.
270
+
271
+ ```tsx
272
+ import { useToast, toast } from "@pol-studios/hooks/toast";
273
+
274
+ const { toast: showToast, dismiss } = useToast();
275
+
276
+ // Imperative API
277
+ toast({ title: "Success!", description: "Operation completed." });
278
+
279
+ // Hook API
280
+ showToast({ title: "Hello", variant: "success" });
281
+ ```
282
+
283
+ ### Form Hooks
284
+
285
+ #### OnValueChangedContext
286
+ Track form value changes.
287
+
288
+ ```tsx
289
+ import {
290
+ OnValueChangedContextProvider,
291
+ OnValueChangedContext,
292
+ } from "@pol-studios/hooks/form";
293
+
294
+ <OnValueChangedContextProvider onTrigger={handleFieldChange}>
295
+ <MyForm />
296
+ </OnValueChangedContextProvider>
297
+ ```
298
+
299
+ ### Debug Hooks
300
+
301
+ #### useRenderCount
302
+ Count component renders (dev only).
303
+
304
+ ```tsx
305
+ import { useRenderCount, useStateTracker } from "@pol-studios/hooks/debug";
306
+
307
+ useRenderCount("MyComponent"); // Logs render count
308
+
309
+ useStateTracker(state, "myState"); // Logs state changes
310
+ ```
311
+
312
+ ### Utilities
313
+
314
+ #### withComponentMemo
315
+ HOC for memoizing components.
316
+
317
+ ```tsx
318
+ import { withComponentMemo } from "@pol-studios/hooks/utils";
319
+
320
+ const MemoizedComponent = withComponentMemo(MyComponent);
321
+ ```
322
+
323
+ ## TypeScript Types
324
+
325
+ ```tsx
326
+ import type {
327
+ // Toast types
328
+ ToasterToast,
329
+ Toast,
330
+ ToastState,
331
+ ToastAction,
332
+
333
+ // Network types
334
+ NetworkStatus,
335
+ OnlineStatus,
336
+ QueuedMutation,
337
+
338
+ // Storage types
339
+ StorageService,
340
+ IndexDbService,
341
+ DBOptions,
342
+
343
+ // Form types
344
+ ValueChangeTrigger,
345
+ OnValueChangedContextValue,
346
+ } from "@pol-studios/hooks";
347
+ ```
348
+
349
+ ## Related Packages
350
+
351
+ - [@pol-studios/utils](../utils) - Utility functions
352
+ - [@pol-studios/features](../features) - Feature modules
353
+ - [@pol-studios/ui](../ui) - UI components
354
+
355
+ ## License
356
+
357
+ UNLICENSED
@@ -0,0 +1,54 @@
1
+ import React__default, { ComponentType } from 'react';
2
+
3
+ /**
4
+ * Checks if a value is usable (not undefined, not null, and not NaN if it's a number)
5
+ */
6
+ declare function isUsable(value: any): value is {};
7
+
8
+ /**
9
+ * Checks if the current environment is development mode
10
+ */
11
+ declare function isDevEnvironment(): boolean;
12
+ /**
13
+ * Alias for isDevEnvironment
14
+ */
15
+ declare function isDev(): boolean;
16
+
17
+ /**
18
+ * JSON reviver function that converts ISO date strings to Moment objects
19
+ */
20
+ declare function dateReviver(key: any, value: any): any;
21
+ /**
22
+ * JSON reviver function that converts ISO date strings to native Date objects
23
+ */
24
+ declare function parseWithDate(key: any, value: any): any;
25
+
26
+ /**
27
+ * Downloads a file using POST request
28
+ */
29
+ declare function getFilePost(url: string, fileName: string, queryParams: string): Promise<void>;
30
+ /**
31
+ * Downloads a file using GET request
32
+ */
33
+ declare function getFile(url: string | Request, fileName: string): Promise<void>;
34
+ /**
35
+ * Downloads text content as a file
36
+ */
37
+ declare function downloadFile(text: string): Promise<void>;
38
+
39
+ /**
40
+ * Checks if the app is running as a PWA (Progressive Web App)
41
+ */
42
+ declare function isPwaLaunched(): boolean;
43
+ /**
44
+ * Checks if the current device is an iPhone
45
+ */
46
+ declare function isIphone(): boolean;
47
+ /**
48
+ * Checks if the device has a camera available
49
+ */
50
+ declare function hasCameraAsync(): Promise<boolean>;
51
+
52
+ declare function withComponentMemo<T extends ComponentType<any>>(Component: T, compareFn?: (prevProps: React__default.ComponentProps<T>, nextProps: React__default.ComponentProps<T>) => boolean): T;
53
+
54
+ export { dateReviver, downloadFile, getFile, getFilePost, hasCameraAsync, isDev, isDevEnvironment, isIphone, isPwaLaunched, isUsable, parseWithDate, withComponentMemo };
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Debug hook that tracks how many times a component has rendered.
3
+ * Useful for debugging unnecessary re-renders.
4
+ *
5
+ * @returns The current render count
6
+ */
7
+ declare const useRenderCount: () => number;
8
+
9
+ /**
10
+ * Debug hook that tracks how many times a specific hook's value has changed.
11
+ * Useful for debugging when a dependency is causing unnecessary updates.
12
+ *
13
+ * @param hook - The hook value to track changes on
14
+ * @returns The number of times the hook value has changed
15
+ */
16
+ declare const useRenderHookCount: <T>(hook: T) => number;
17
+
18
+ /**
19
+ * Debug hook that logs state changes to the console.
20
+ * Only runs in development environment.
21
+ *
22
+ * @param state - The state value to track
23
+ */
24
+ declare function useStateTracker<T>(state: T): void;
25
+
26
+ /**
27
+ * Debug hook that provides useState with detailed logging of value changes.
28
+ * Logs the old value, new value, and stack trace when state changes.
29
+ *
30
+ * @param initialValue - Initial state value
31
+ * @param hookName - Name to identify this state in logs
32
+ * @returns Tuple of [state, setState]
33
+ */
34
+ declare function useTrackedState<T>(initialValue: T, hookName: string): [T, (value: React.SetStateAction<T>) => void];
35
+
36
+ export { useRenderCount, useRenderHookCount, useStateTracker, useTrackedState };
@@ -0,0 +1,139 @@
1
+ import { RefObject } from 'react';
2
+
3
+ interface DeviceInfo {
4
+ isMobile: boolean;
5
+ width: number;
6
+ height: number;
7
+ isPwa: boolean;
8
+ }
9
+ /**
10
+ * Hook to get device information including viewport size, mobile status, and PWA status.
11
+ * Combines useWindowDimensions, useMobile, and isPwaLaunched for a complete device context.
12
+ *
13
+ * @returns Object containing isMobile, width, height, and isPwa status
14
+ *
15
+ * @example
16
+ * ```tsx
17
+ * const { isMobile, width, height, isPwa } = useDevice();
18
+ *
19
+ * if (isPwa) {
20
+ * // Show PWA-specific features
21
+ * }
22
+ * ```
23
+ */
24
+ declare function useDevice(): DeviceInfo;
25
+
26
+ /**
27
+ * Hook to detect if the current viewport is mobile-sized.
28
+ * Uses a media query listener for responsive updates.
29
+ *
30
+ * @returns true if viewport width is less than 768px, false otherwise
31
+ *
32
+ * @example
33
+ * ```tsx
34
+ * const isMobile = useMobile();
35
+ *
36
+ * return isMobile ? <MobileNav /> : <DesktopNav />;
37
+ * ```
38
+ */
39
+ declare function useMobile(): boolean;
40
+ /**
41
+ * @deprecated Use `useMobile` instead. This alias is provided for backwards compatibility.
42
+ */
43
+ declare const useIsMobile: typeof useMobile;
44
+
45
+ /**
46
+ * Hook to detect and apply the user's preferred color scheme (dark/light mode).
47
+ * Automatically listens for system preference changes and updates the document.
48
+ *
49
+ * @returns true if dark theme is active, false for light theme
50
+ *
51
+ * @example
52
+ * ```tsx
53
+ * const isDarkTheme = useTheme();
54
+ *
55
+ * return (
56
+ * <div className={isDarkTheme ? 'bg-gray-900' : 'bg-white'}>
57
+ * Content
58
+ * </div>
59
+ * );
60
+ * ```
61
+ */
62
+ declare function useTheme(): boolean;
63
+ /**
64
+ * @deprecated Use `useTheme` instead. This alias is provided for backwards compatibility.
65
+ */
66
+ declare const useThemeDetector: typeof useTheme;
67
+
68
+ interface Dimensions {
69
+ width: number;
70
+ height: number;
71
+ }
72
+ /**
73
+ * Hook to get the dimensions of a DOM element.
74
+ *
75
+ * Note: This is a naive implementation - for production use, consider attaching
76
+ * a resize observer. Also uses ref/effect instead of state/layoutEffect,
77
+ * so dimensions won't be available on initial render.
78
+ *
79
+ * @param ref - React ref to the element to measure
80
+ * @returns Object containing width and height of the element
81
+ *
82
+ * @example
83
+ * ```tsx
84
+ * const containerRef = useRef<HTMLDivElement>(null);
85
+ * const { width, height } = useDimensions(containerRef);
86
+ *
87
+ * return (
88
+ * <div ref={containerRef}>
89
+ * Size: {width}x{height}
90
+ * </div>
91
+ * );
92
+ * ```
93
+ */
94
+ declare function useDimensions(ref: RefObject<HTMLElement>): Dimensions;
95
+
96
+ interface WindowDimensions {
97
+ width: number;
98
+ height: number;
99
+ }
100
+ /**
101
+ * Hook to get the current window dimensions.
102
+ * Updates automatically when the window is resized.
103
+ *
104
+ * @returns Object containing current window width and height
105
+ *
106
+ * @example
107
+ * ```tsx
108
+ * const { width, height } = useWindowDimensions();
109
+ *
110
+ * return <div>Window size: {width}x{height}</div>;
111
+ * ```
112
+ */
113
+ declare function useWindowDimensions(): WindowDimensions;
114
+
115
+ /**
116
+ * Hook to detect if the user prefers reduced motion.
117
+ * Checks both the CSS media query and localStorage preference (if set).
118
+ *
119
+ * @returns true if reduced motion is preferred, false otherwise
120
+ *
121
+ * @example
122
+ * ```tsx
123
+ * const prefersReducedMotion = useReducedMotion();
124
+ *
125
+ * <motion.div
126
+ * animate={{ opacity: prefersReducedMotion ? 1 : 0 }}
127
+ * transition={{ duration: prefersReducedMotion ? 0 : 0.3 }}
128
+ * />
129
+ * ```
130
+ */
131
+ declare function useReducedMotion(): boolean;
132
+ /**
133
+ * Set a user preference for reduced motion (overrides system preference).
134
+ *
135
+ * @param enabled - true to enable reduced motion, false to disable, null to use system preference
136
+ */
137
+ declare function setReducedMotionPreference(enabled: boolean | null): void;
138
+
139
+ export { setReducedMotionPreference, useDevice, useDimensions, useIsMobile, useMobile, useReducedMotion, useTheme, useThemeDetector, useWindowDimensions };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Hook that provides a function to open a file dialog programmatically.
3
+ *
4
+ * @returns Object containing openFileDialog function
5
+ */
6
+ declare function useFileDialog(): {
7
+ openFileDialog: () => Promise<FileList>;
8
+ };
9
+
10
+ interface DownloadOptions {
11
+ url: string;
12
+ fileName: string;
13
+ queryParams: string;
14
+ }
15
+ /**
16
+ * Hook that provides a function to download files via POST request.
17
+ *
18
+ * @returns Download function
19
+ */
20
+ declare function useDownload(): ({ url, fileName, queryParams }: DownloadOptions) => Promise<void>;
21
+ /**
22
+ * @deprecated Use useDownload instead
23
+ */
24
+ declare const useDownloadApiFile: typeof useDownload;
25
+
26
+ export { useDownload, useDownloadApiFile, useFileDialog };
@@ -0,0 +1,20 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as React from 'react';
3
+ import { ReactNode } from 'react';
4
+
5
+ type ValueChangeTrigger = "change" | "blur" | number;
6
+ interface OnValueChangedContextValue {
7
+ trigger: ValueChangeTrigger;
8
+ }
9
+ declare const OnValueChangedContext: React.Context<OnValueChangedContextValue>;
10
+ interface OnValueChangedContextProviderProps {
11
+ children: ReactNode;
12
+ trigger: ValueChangeTrigger;
13
+ }
14
+ declare const OnValueChangedContextProvider: ({ children, trigger, }: OnValueChangedContextProviderProps) => react_jsx_runtime.JSX.Element;
15
+ /** @deprecated Use OnValueChangedContext instead */
16
+ declare const OnValueChangedTriggerContext: React.Context<OnValueChangedContextValue>;
17
+ /** @deprecated Use OnValueChangedContextProvider instead */
18
+ declare const OnValueChangedTriggerContextProvider: ({ children, trigger, }: OnValueChangedContextProviderProps) => react_jsx_runtime.JSX.Element;
19
+
20
+ export { OnValueChangedContext, OnValueChangedContextProvider, type OnValueChangedContextProviderProps, type OnValueChangedContextValue, OnValueChangedTriggerContext, OnValueChangedTriggerContextProvider, type ValueChangeTrigger };
@@ -0,0 +1,14 @@
1
+ export { ChangeTrackingContext, ChangeTrackingProvider, useChangeTracking, useDelayedState, useDelayedValue, useForceUpdate, usePartialState, useQueuedState } from './state/index.js';
2
+ export { DBOptions, IndexedDBService as IndexDb, IndexDbProvider, IndexDbService, IndexedDBService, LocalStorageProvider, LocalStorageService, StorageService, indexDbContext, localStorageContext, useIndexedDB as useIndexDbService, useIndexedDB, useLocalStorage, useLocalStorageService, useLocalStorageState, useQueuedLocalStorage, useQueuedLocalStorageState, useSessionStorage, useSessionStorageState } from './storage/index.js';
3
+ export { useAfterMountedEffect as useAfterMount, useAfterMountedEffect, useMounted, useOnScreen } from './lifecycle/index.js';
4
+ export { setReducedMotionPreference, useDevice, useDimensions, useIsMobile, useMobile, useReducedMotion, useTheme, useThemeDetector, useWindowDimensions } from './device/index.js';
5
+ export { useDownload, useDownloadApiFile, useFileDialog } from './file/index.js';
6
+ export { OnValueChangedContext, OnValueChangedContextProvider, OnValueChangedContextProviderProps, OnValueChangedContextValue, OnValueChangedTriggerContext, OnValueChangedTriggerContextProvider, ValueChangeTrigger } from './form/index.js';
7
+ export { ActionType, Toast, ToastAction, ToastState, ToasterToast, actionTypes, reducer, toast, useToast } from './toast/index.js';
8
+ export { useHideOnScroll, useHidingOnScroll, useScrollDirection, useScrollPosition, useScrollRestoration } from './scroll/index.js';
9
+ export { NetworkStatus, NetworkStatus as OnlineStatus, QueuedMutation, useMutationQueue, useNetworkStatus, useOnline } from './network/index.js';
10
+ export { useRenderCount, useRenderHookCount, useStateTracker, useTrackedState } from './debug/index.js';
11
+ export { dateReviver, downloadFile, getFile, getFilePost, hasCameraAsync, isDev, isDevEnvironment, isIphone, isPwaLaunched, isUsable, parseWithDate, withComponentMemo } from './_utils/index.js';
12
+ export { CommentProvider as CommentSectionProvider, FilterProvider, genId, getComparisonOptions, getDefaultCondition, getDefaultValue, useComments as useCommentSection, useFilterContext, useOrderManager as useOrderHintOld } from '@pol-studios/features';
13
+ import 'react';
14
+ import 'react/jsx-runtime';
@@ -0,0 +1,22 @@
1
+ import React__default from 'react';
2
+
3
+ /**
4
+ * Hook that returns a function to check if the component is currently mounted.
5
+ * Useful for avoiding state updates after unmount in async operations.
6
+ *
7
+ * @returns A function that returns true if mounted, false otherwise
8
+ */
9
+ declare function useMounted(): () => boolean;
10
+
11
+ declare function useAfterMountedEffect(effect: React__default.EffectCallback, deps?: React__default.DependencyList): void;
12
+
13
+ /**
14
+ * Hook that detects if an element is visible in the viewport using IntersectionObserver.
15
+ *
16
+ * @param ref - React ref to the element to observe
17
+ * @param rootMargin - Margin around the root (default: "0px")
18
+ * @returns boolean indicating if element is visible
19
+ */
20
+ declare function useOnScreen(ref: React.RefObject<HTMLElement>, rootMargin?: string): boolean;
21
+
22
+ export { useAfterMountedEffect as useAfterMount, useAfterMountedEffect, useMounted, useOnScreen };
@@ -0,0 +1,79 @@
1
+ interface NetworkStatus {
2
+ isOnline: boolean;
3
+ isOffline: boolean;
4
+ wasOffline: boolean;
5
+ }
6
+ /**
7
+ * Hook to track network status across platforms.
8
+ * Works with TanStack Query's onlineManager for consistent offline handling.
9
+ *
10
+ * @returns Object with isOnline, isOffline, and wasOffline status
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * const { isOnline, isOffline, wasOffline } = useOnline();
15
+ *
16
+ * if (isOffline) {
17
+ * return <OfflineBanner />;
18
+ * }
19
+ *
20
+ * if (wasOffline) {
21
+ * // Show "back online" notification
22
+ * }
23
+ * ```
24
+ */
25
+ declare function useOnline(): NetworkStatus;
26
+ /**
27
+ * @deprecated Use `useOnline` instead. This alias is provided for backwards compatibility.
28
+ */
29
+ declare const useNetworkStatus: typeof useOnline;
30
+
31
+ interface QueuedMutation {
32
+ id: string;
33
+ mutationKey: string[];
34
+ mutationFn: () => Promise<any>;
35
+ variables: any;
36
+ timestamp: number;
37
+ retryCount: number;
38
+ error?: Error;
39
+ }
40
+ /**
41
+ * Hook to manage offline mutation queue.
42
+ * Queues mutations when offline and automatically retries when back online.
43
+ *
44
+ * Note: Currently stores mutations in memory only. Functions cannot be serialized,
45
+ * so mutations are lost on page refresh. Future enhancement: store mutation parameters
46
+ * and recreate mutations on retry.
47
+ *
48
+ * @returns Object with queue management functions and state
49
+ *
50
+ * @example
51
+ * ```tsx
52
+ * const {
53
+ * queueMutation,
54
+ * queuedMutations,
55
+ * queueCount,
56
+ * clearQueue
57
+ * } = useMutationQueue();
58
+ *
59
+ * // Queue a mutation for offline execution
60
+ * queueMutation(
61
+ * ['updateUser', userId],
62
+ * () => api.updateUser(userId, data),
63
+ * data
64
+ * );
65
+ *
66
+ * // Show pending count
67
+ * <Badge>{queueCount} pending</Badge>
68
+ * ```
69
+ */
70
+ declare function useMutationQueue(): {
71
+ queuedMutations: QueuedMutation[];
72
+ queueMutation: (mutationKey: string[], mutationFn: () => Promise<any>, variables: any) => void;
73
+ removeQueuedMutation: (id: string) => void;
74
+ clearQueue: () => void;
75
+ processQueue: () => Promise<void>;
76
+ queueCount: number;
77
+ };
78
+
79
+ export { type NetworkStatus, type NetworkStatus as OnlineStatus, type QueuedMutation, useMutationQueue, useNetworkStatus, useOnline };
@@ -0,0 +1,101 @@
1
+ import React__default, { RefObject } from 'react';
2
+
3
+ interface ScrollPosition {
4
+ scrollY: number;
5
+ scrollHeight: number;
6
+ }
7
+ /**
8
+ * Hook to track scroll position of an element or the window.
9
+ *
10
+ * @param elementRef - Optional ref to a scrollable element. If not provided, tracks window scroll.
11
+ * @returns Object containing current scrollY position and total scrollHeight
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * // Track window scroll
16
+ * const { scrollY, scrollHeight } = useScrollPosition();
17
+ *
18
+ * // Track element scroll
19
+ * const containerRef = useRef<HTMLDivElement>(null);
20
+ * const { scrollY, scrollHeight } = useScrollPosition(containerRef);
21
+ * ```
22
+ */
23
+ declare function useScrollPosition(elementRef?: RefObject<HTMLElement>): ScrollPosition;
24
+
25
+ /**
26
+ * Hook to detect the user's scroll direction.
27
+ *
28
+ * @param elementRef - Ref to the scrollable element to monitor
29
+ * @param threshold - Scroll position threshold for direction detection
30
+ * @returns true if user is scrolling up, false if scrolling down
31
+ *
32
+ * @example
33
+ * ```tsx
34
+ * const containerRef = useRef<HTMLDivElement>(null);
35
+ * const isScrollingUp = useScrollDirection(containerRef, 100);
36
+ *
37
+ * return (
38
+ * <div ref={containerRef}>
39
+ * <header className={isScrollingUp ? 'visible' : 'hidden'}>
40
+ * Navigation
41
+ * </header>
42
+ * </div>
43
+ * );
44
+ * ```
45
+ */
46
+ declare function useScrollDirection(elementRef: RefObject<HTMLElement>, threshold: number): boolean;
47
+
48
+ /**
49
+ * Hook to save and restore scroll position using sessionStorage.
50
+ * Useful for maintaining scroll position when navigating away and returning to a page.
51
+ *
52
+ * @param scrollAreaId - Unique identifier for this scroll area (used as storage key)
53
+ * @param ref - Optional ref to a scrollable element. If not provided, uses window scroll.
54
+ * @returns The restored scroll position (or NaN if none was saved)
55
+ *
56
+ * @example
57
+ * ```tsx
58
+ * // Restore window scroll
59
+ * useScrollRestoration("my-page");
60
+ *
61
+ * // Restore element scroll
62
+ * const containerRef = useRef<HTMLDivElement>(null);
63
+ * useScrollRestoration("my-list", containerRef);
64
+ * ```
65
+ */
66
+ declare function useScrollRestoration(scrollAreaId?: string, ref?: React__default.RefObject<any>): number;
67
+
68
+ interface UseHideOnScrollProps {
69
+ height: number;
70
+ scrollRef?: React.MutableRefObject<HTMLElement>;
71
+ initialPercentVisible?: number;
72
+ }
73
+ /**
74
+ * Hook to create a hide-on-scroll effect, typically used for headers/navigation.
75
+ * Returns the percent visibility (0-1) that can be used to animate the element.
76
+ *
77
+ * @param props.height - The height of the element being hidden
78
+ * @param props.scrollRef - Optional ref to a scrollable element. Uses window if not provided.
79
+ * @param props.initialPercentVisible - Initial visibility (default: 1, fully visible)
80
+ * @returns Percent visible from 0 to 1
81
+ *
82
+ * @example
83
+ * ```tsx
84
+ * const percentVisible = useHideOnScroll({ height: 64 });
85
+ *
86
+ * return (
87
+ * <motion.header
88
+ * style={{ transform: `translateY(${(1 - percentVisible) * -100}%)` }}
89
+ * >
90
+ * Navigation
91
+ * </motion.header>
92
+ * );
93
+ * ```
94
+ */
95
+ declare function useHideOnScroll({ height, scrollRef, initialPercentVisible }: UseHideOnScrollProps): number;
96
+ /**
97
+ * @deprecated Use `useHideOnScroll` instead. This alias is provided for backwards compatibility.
98
+ */
99
+ declare const useHidingOnScroll: typeof useHideOnScroll;
100
+
101
+ export { useHideOnScroll, useHidingOnScroll, useScrollDirection, useScrollPosition, useScrollRestoration };
@@ -0,0 +1,41 @@
1
+ import * as React$1 from 'react';
2
+ import React__default from 'react';
3
+ import * as react_jsx_runtime from 'react/jsx-runtime';
4
+
5
+ declare function usePartialState<T>(initialState: T): {
6
+ state: T;
7
+ patch: (newProps: Partial<T>) => void;
8
+ setState: React$1.Dispatch<React$1.SetStateAction<T>>;
9
+ };
10
+
11
+ declare function useQueuedState<T>(initialState: T): [
12
+ T,
13
+ (updateFn: React.SetStateAction<T>) => void
14
+ ];
15
+
16
+ declare function useDelayedValue<T>(value: T, delayInMs: number): T;
17
+ /** @deprecated Use useDelayedValue instead */
18
+ declare const useDelayedState: typeof useDelayedValue;
19
+
20
+ declare function useChangeTracking<T>(initialValue: T): {
21
+ initialValue: T;
22
+ update: (newProps: Partial<T>) => void;
23
+ value: T;
24
+ propertiesChanged: Set<string>;
25
+ };
26
+
27
+ declare function useForceUpdate(): () => void;
28
+
29
+ interface ChangeTrackingContextValue {
30
+ initialValue: any;
31
+ value: any;
32
+ onChange: (newValue: Partial<any>) => void;
33
+ propertiesChanged: Set<any>;
34
+ }
35
+ declare const ChangeTrackingContext: React__default.Context<ChangeTrackingContextValue>;
36
+ declare function ChangeTrackingProvider({ children, initialValue }: {
37
+ children: React__default.ReactNode;
38
+ initialValue?: any;
39
+ }): react_jsx_runtime.JSX.Element;
40
+
41
+ export { ChangeTrackingContext, ChangeTrackingProvider, useChangeTracking, useDelayedState, useDelayedValue, useForceUpdate, usePartialState, useQueuedState };
@@ -0,0 +1,86 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import React__default, { ReactNode } from 'react';
3
+
4
+ declare function useLocalStorage<T>(key: string, initialValue?: T | (() => T) | undefined): [T | undefined, (value: React.SetStateAction<T>) => void, boolean];
5
+ /** @deprecated Use useLocalStorage instead */
6
+ declare const useLocalStorageState: typeof useLocalStorage;
7
+
8
+ declare function useSessionStorage<T>(key: string, initialValue?: T): [T | undefined, React.Dispatch<React.SetStateAction<T | undefined>>];
9
+ /** @deprecated Use useSessionStorage instead */
10
+ declare const useSessionStorageState: typeof useSessionStorage;
11
+
12
+ interface StorageService {
13
+ getItem<T>(key: string): Promise<T | null>;
14
+ setItem<T>(key: string, value: T): Promise<void>;
15
+ removeItem(key: string): void;
16
+ addItemChangeListener(key: string, handler: (event: {
17
+ key: string;
18
+ newValue: string;
19
+ }) => any): any;
20
+ removeItemChangeListener(key: string, handler: any): any;
21
+ }
22
+ declare const localStorageContext: React__default.Context<StorageService>;
23
+ declare function LocalStorageProvider({ children, value, }: {
24
+ children: ReactNode;
25
+ value: StorageService;
26
+ }): react_jsx_runtime.JSX.Element;
27
+ interface IndexDbService {
28
+ getItem<T = any>(key: string): Promise<T | null>;
29
+ setItem<T = any>(key: string, value: T): Promise<void>;
30
+ removeItem(key: string): Promise<void>;
31
+ getAllKeys(): Promise<readonly string[]>;
32
+ }
33
+ declare const storeNames$1: readonly ["cached-urls", "metadata"];
34
+ type PolStoreName$1 = (typeof storeNames$1)[keyof typeof storeNames$1] & string;
35
+ interface DBOptions$1 {
36
+ dbName: string;
37
+ storeName: PolStoreName$1;
38
+ }
39
+ declare const indexDbContext: React__default.Context<{
40
+ services: Map<DBOptions$1, IndexDbService>;
41
+ }>;
42
+ declare function IndexDbProvider({ children, value, }: {
43
+ children: ReactNode;
44
+ value: Map<DBOptions$1, IndexDbService>;
45
+ }): react_jsx_runtime.JSX.Element;
46
+
47
+ declare function useIndexedDB(options: DBOptions$1): IndexDbService;
48
+
49
+ declare function useQueuedLocalStorage<T>(storageKey: string, initialState: T): [T, (updateFn: React.SetStateAction<T>) => void, boolean];
50
+ /** @deprecated Use useQueuedLocalStorage instead */
51
+ declare const useQueuedLocalStorageState: typeof useQueuedLocalStorage;
52
+
53
+ declare function useLocalStorageService(): StorageService;
54
+
55
+ declare class LocalStorageService implements StorageService {
56
+ constructor();
57
+ addItemChangeListener(key: string, handler: (event: {
58
+ key: string;
59
+ newValue: string;
60
+ }) => any): () => void;
61
+ removeItemChangeListener(key: string, handler: any): void;
62
+ setItem<T>(key: string, value: T): Promise<void>;
63
+ getItem<T>(key: string): Promise<T | null>;
64
+ removeItem(key: string): void;
65
+ }
66
+
67
+ interface DBOptions<T> {
68
+ dbName: string;
69
+ storeName: PolStoreName;
70
+ }
71
+ declare const storeNames: readonly ["cached-urls", "metadata"];
72
+ type PolStoreName = ((typeof storeNames)[keyof typeof storeNames]) & string;
73
+ declare class IndexedDBService<T> implements IndexDbService {
74
+ private dbName;
75
+ private storeName;
76
+ private db;
77
+ constructor(options: DBOptions<T>);
78
+ private openDB;
79
+ private getDB;
80
+ setItem<T>(key: string, record: T): Promise<void>;
81
+ getItem<T = any>(key: string): Promise<T | null>;
82
+ removeItem(key: string): Promise<void>;
83
+ getAllKeys(): Promise<readonly string[]>;
84
+ }
85
+
86
+ export { type DBOptions$1 as DBOptions, IndexedDBService as IndexDb, IndexDbProvider, type IndexDbService, IndexedDBService, LocalStorageProvider, LocalStorageService, type StorageService, indexDbContext, localStorageContext, useIndexedDB, useLocalStorage, useLocalStorageService, useLocalStorageState, useQueuedLocalStorage, useQueuedLocalStorageState, useSessionStorage, useSessionStorageState };
@@ -0,0 +1,48 @@
1
+ import * as React from 'react';
2
+
3
+ type ToasterToast = {
4
+ id: string;
5
+ title?: React.ReactNode;
6
+ description?: React.ReactNode;
7
+ className?: string;
8
+ open?: boolean;
9
+ onOpenChange?: (open: boolean) => void;
10
+ } & React.HTMLAttributes<HTMLLIElement>;
11
+ type Toast = Omit<ToasterToast, "id">;
12
+ interface ToastState {
13
+ toasts: ToasterToast[];
14
+ }
15
+ declare const actionTypes: {
16
+ readonly ADD_TOAST: "ADD_TOAST";
17
+ readonly UPDATE_TOAST: "UPDATE_TOAST";
18
+ readonly DISMISS_TOAST: "DISMISS_TOAST";
19
+ readonly REMOVE_TOAST: "REMOVE_TOAST";
20
+ };
21
+ type ActionType = typeof actionTypes;
22
+ type ToastAction = {
23
+ type: ActionType["ADD_TOAST"];
24
+ toast: ToasterToast;
25
+ } | {
26
+ type: ActionType["UPDATE_TOAST"];
27
+ toast: Partial<ToasterToast>;
28
+ } | {
29
+ type: ActionType["DISMISS_TOAST"];
30
+ toastId?: ToasterToast["id"];
31
+ } | {
32
+ type: ActionType["REMOVE_TOAST"];
33
+ toastId?: ToasterToast["id"];
34
+ };
35
+
36
+ declare const reducer: (state: ToastState, action: ToastAction) => ToastState;
37
+ declare function toast({ ...props }: Toast): {
38
+ id: string;
39
+ dismiss: () => void;
40
+ update: (props: ToasterToast) => void;
41
+ };
42
+ declare function useToast(): {
43
+ toast: typeof toast;
44
+ dismiss: (toastId?: string) => void;
45
+ toasts: ToasterToast[];
46
+ };
47
+
48
+ export { type ActionType, type Toast, type ToastAction, type ToastState, type ToasterToast, actionTypes, useToast as default, reducer, toast, useToast };
package/package.json CHANGED
@@ -1,30 +1,63 @@
1
1
  {
2
2
  "name": "@pol-studios/hooks",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "React hooks for POL applications",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",
8
8
  "types": "./dist/index.d.ts",
9
- "files": ["dist"],
9
+ "files": [
10
+ "dist"
11
+ ],
10
12
  "exports": {
11
- ".": { "import": "./dist/index.js", "types": "./dist/index.d.ts" },
12
- "./state": { "import": "./dist/state/index.js", "types": "./dist/state/index.d.ts" },
13
- "./scroll": { "import": "./dist/scroll/index.js", "types": "./dist/scroll/index.d.ts" },
14
- "./storage": { "import": "./dist/storage/index.js", "types": "./dist/storage/index.d.ts" },
15
- "./device": { "import": "./dist/device/index.js", "types": "./dist/device/index.d.ts" },
16
- "./network": { "import": "./dist/network/index.js", "types": "./dist/network/index.d.ts" },
17
- "./lifecycle": { "import": "./dist/lifecycle/index.js", "types": "./dist/lifecycle/index.d.ts" },
18
- "./file": { "import": "./dist/file/index.js", "types": "./dist/file/index.d.ts" },
19
- "./toast": { "import": "./dist/toast/index.js", "types": "./dist/toast/index.d.ts" },
20
- "./form": { "import": "./dist/form/index.js", "types": "./dist/form/index.d.ts" },
21
- "./debug": { "import": "./dist/debug/index.js", "types": "./dist/debug/index.d.ts" },
22
- "./utils": { "import": "./dist/_utils/index.js", "types": "./dist/_utils/index.d.ts" }
23
- },
24
- "scripts": {
25
- "build": "tsup",
26
- "dev": "tsup --watch",
27
- "prepublishOnly": "pnpm build"
13
+ ".": {
14
+ "import": "./dist/index.js",
15
+ "types": "./dist/index.d.ts"
16
+ },
17
+ "./state": {
18
+ "import": "./dist/state/index.js",
19
+ "types": "./dist/state/index.d.ts"
20
+ },
21
+ "./scroll": {
22
+ "import": "./dist/scroll/index.js",
23
+ "types": "./dist/scroll/index.d.ts"
24
+ },
25
+ "./storage": {
26
+ "import": "./dist/storage/index.js",
27
+ "types": "./dist/storage/index.d.ts"
28
+ },
29
+ "./device": {
30
+ "import": "./dist/device/index.js",
31
+ "types": "./dist/device/index.d.ts"
32
+ },
33
+ "./network": {
34
+ "import": "./dist/network/index.js",
35
+ "types": "./dist/network/index.d.ts"
36
+ },
37
+ "./lifecycle": {
38
+ "import": "./dist/lifecycle/index.js",
39
+ "types": "./dist/lifecycle/index.d.ts"
40
+ },
41
+ "./file": {
42
+ "import": "./dist/file/index.js",
43
+ "types": "./dist/file/index.d.ts"
44
+ },
45
+ "./toast": {
46
+ "import": "./dist/toast/index.js",
47
+ "types": "./dist/toast/index.d.ts"
48
+ },
49
+ "./form": {
50
+ "import": "./dist/form/index.js",
51
+ "types": "./dist/form/index.d.ts"
52
+ },
53
+ "./debug": {
54
+ "import": "./dist/debug/index.js",
55
+ "types": "./dist/debug/index.d.ts"
56
+ },
57
+ "./utils": {
58
+ "import": "./dist/_utils/index.js",
59
+ "types": "./dist/_utils/index.d.ts"
60
+ }
28
61
  },
29
62
  "publishConfig": {
30
63
  "access": "public"
@@ -41,10 +74,20 @@
41
74
  "lodash": "^4.17.21"
42
75
  },
43
76
  "devDependencies": {
44
- "@pol-studios/features": "workspace:*",
45
- "@pol-studios/utils": "workspace:*",
46
77
  "tsup": "^8.0.0",
47
- "typescript": "^5.0.0"
78
+ "typescript": "^5.0.0",
79
+ "@pol-studios/features": "1.0.1",
80
+ "@pol-studios/utils": "1.0.1"
48
81
  },
49
- "keywords": ["hooks", "react", "state", "storage", "toast"]
50
- }
82
+ "keywords": [
83
+ "hooks",
84
+ "react",
85
+ "state",
86
+ "storage",
87
+ "toast"
88
+ ],
89
+ "scripts": {
90
+ "build": "tsup",
91
+ "dev": "tsup --watch"
92
+ }
93
+ }