airyhooks 0.1.0-beta.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.
@@ -0,0 +1,1096 @@
1
+ // AUTO-GENERATED FILE - DO NOT EDIT DIRECTLY
2
+ // Generated by: pnpm --filter @airyhooks/hooks build:templates
3
+ // Source: packages/hooks/src/*/use*.ts
4
+ const templates = {
5
+ useBoolean: `import { useCallback, useState } from "react";
6
+
7
+ /**
8
+ * Alias for useToggle with boolean semantics.
9
+ *
10
+ * @param initialValue - Initial boolean value (default: false)
11
+ * @returns Tuple of [value, { setTrue, setFalse, toggle }]
12
+ *
13
+ * @example
14
+ * const [isEnabled, handlers] = useBoolean(false);
15
+ *
16
+ * return (
17
+ * <>
18
+ * <button onClick={handlers.toggle}>Toggle</button>
19
+ * <button onClick={handlers.setTrue}>Enable</button>
20
+ * <button onClick={handlers.setFalse}>Disable</button>
21
+ * </>
22
+ * );
23
+ */
24
+ export function useBoolean(initialValue = false): [
25
+ boolean,
26
+ {
27
+ setFalse: () => void;
28
+ setTrue: () => void;
29
+ toggle: () => void;
30
+ },
31
+ ] {
32
+ const [value, toggle, setValue] = useToggle(initialValue);
33
+
34
+ return [
35
+ value,
36
+ {
37
+ setFalse: useCallback(() => {
38
+ setValue(false);
39
+ }, [setValue]),
40
+ setTrue: useCallback(() => {
41
+ setValue(true);
42
+ }, [setValue]),
43
+ toggle,
44
+ },
45
+ ];
46
+ }
47
+
48
+ /**
49
+ * Toggle a boolean value with a callback.
50
+ *
51
+ * @param initialValue - Initial boolean value (default: false)
52
+ * @returns Tuple of [value, toggle, setValue]
53
+ *
54
+ * @example
55
+ * const [isOpen, toggle] = useToggle(false);
56
+ *
57
+ * return (
58
+ * <>
59
+ * <button onClick={toggle}>Toggle</button>
60
+ * {isOpen && <div>Content</div>}
61
+ * </>
62
+ * );
63
+ */
64
+ export function useToggle(
65
+ initialValue = false,
66
+ ): [boolean, () => void, (value: boolean) => void] {
67
+ const [value, setValue] = useState(initialValue);
68
+
69
+ const toggle = useCallback(() => {
70
+ setValue((prev) => !prev);
71
+ }, []);
72
+
73
+ return [value, toggle, setValue];
74
+ }
75
+ `,
76
+ useClickAway: `import { useEffect } from "react";
77
+
78
+ /**
79
+ * Detects clicks outside of a target element.
80
+ *
81
+ * @param ref - React ref to the target element
82
+ * @param callback - Function to call when click outside is detected
83
+ *
84
+ * @example
85
+ * const ref = useRef<HTMLDivElement>(null);
86
+ *
87
+ * useClickAway(ref, () => {
88
+ * setIsOpen(false);
89
+ * });
90
+ *
91
+ * return <div ref={ref}>Content</div>;
92
+ */
93
+ export function useClickAway<T extends HTMLElement>(
94
+ ref: React.RefObject<null | T>,
95
+ callback: () => void,
96
+ ): void {
97
+ useEffect(() => {
98
+ const handleClickOutside = (event: MouseEvent) => {
99
+ const element = ref.current;
100
+ if (element && !element.contains(event.target as Node)) {
101
+ callback();
102
+ }
103
+ };
104
+
105
+ document.addEventListener("mousedown", handleClickOutside);
106
+ return () => {
107
+ document.removeEventListener("mousedown", handleClickOutside);
108
+ };
109
+ }, [ref, callback]);
110
+ }
111
+ `,
112
+ useCounter: `import { useCallback, useState } from "react";
113
+
114
+ /**
115
+ * Manages numeric state with increment, decrement, reset, and set methods.
116
+ *
117
+ * @param initialValue - Initial numeric value (default: 0)
118
+ * @returns Tuple of [value, { increment, decrement, reset, set }]
119
+ *
120
+ * @example
121
+ * const [count, { increment, decrement, reset }] = useCounter(0);
122
+ *
123
+ * return (
124
+ * <>
125
+ * <p>Count: {count}</p>
126
+ * <button onClick={() => increment()}>+1</button>
127
+ * <button onClick={() => decrement()}>-1</button>
128
+ * <button onClick={() => increment(5)}>+5</button>
129
+ * <button onClick={() => reset()}>Reset</button>
130
+ * </>
131
+ * );
132
+ */
133
+ export function useCounter(
134
+ initialValue = 0,
135
+ ): [
136
+ number,
137
+ {
138
+ increment: (amount?: number) => void;
139
+ decrement: (amount?: number) => void;
140
+ reset: () => void;
141
+ set: (value: number | ((prev: number) => number)) => void;
142
+ },
143
+ ] {
144
+ const [count, setCount] = useState<number>(initialValue);
145
+
146
+ const increment = useCallback((amount = 1) => {
147
+ setCount((prev) => prev + amount);
148
+ }, []);
149
+
150
+ const decrement = useCallback((amount = 1) => {
151
+ setCount((prev) => prev - amount);
152
+ }, []);
153
+
154
+ const reset = useCallback(() => {
155
+ setCount(initialValue);
156
+ }, [initialValue]);
157
+
158
+ const set = useCallback((value: number | ((prev: number) => number)) => {
159
+ setCount(value);
160
+ }, []);
161
+
162
+ return [
163
+ count,
164
+ {
165
+ increment,
166
+ decrement,
167
+ reset,
168
+ set,
169
+ },
170
+ ];
171
+ }
172
+ `,
173
+ useDebounce: `import { useEffect, useState } from "react";
174
+
175
+ /**
176
+ * Debounces a value by delaying updates until after the specified delay.
177
+ *
178
+ * @param value - The value to debounce
179
+ * @param delay - The delay in milliseconds (default: 500ms)
180
+ * @returns The debounced value
181
+ *
182
+ * @example
183
+ * const [search, setSearch] = useState("");
184
+ * const debouncedSearch = useDebounce(search, 300);
185
+ *
186
+ * useEffect(() => {
187
+ * // This effect runs 300ms after the user stops typing
188
+ * fetchResults(debouncedSearch);
189
+ * }, [debouncedSearch]);
190
+ */
191
+ export function useDebounce<T>(value: T, delay = 500): T {
192
+ const [debouncedValue, setDebouncedValue] = useState<T>(value);
193
+
194
+ useEffect(() => {
195
+ const timer = setTimeout(() => {
196
+ setDebouncedValue(value);
197
+ }, delay);
198
+
199
+ return () => {
200
+ clearTimeout(timer);
201
+ };
202
+ }, [value, delay]);
203
+
204
+ return debouncedValue;
205
+ }
206
+ `,
207
+ useHover: `import { useCallback, useRef, useState } from "react";
208
+
209
+ /**
210
+ * Tracks mouse hover state on a DOM element via ref.
211
+ *
212
+ * @returns Tuple of [isHovered, ref]
213
+ *
214
+ * @example
215
+ * const [isHovered, ref] = useHover();
216
+ *
217
+ * return (
218
+ * <div
219
+ * ref={ref}
220
+ * style={{
221
+ * backgroundColor: isHovered ? "blue" : "gray",
222
+ * }}
223
+ * >
224
+ * Hover me!
225
+ * </div>
226
+ * );
227
+ */
228
+ export function useHover<T extends HTMLElement = HTMLElement>(): [
229
+ boolean,
230
+ React.RefObject<T>,
231
+ ] {
232
+ const ref = useRef<T>(null);
233
+ const [isHovered, setIsHovered] = useState(false);
234
+
235
+ const handleMouseEnter = useCallback(() => {
236
+ setIsHovered(true);
237
+ }, []);
238
+
239
+ const handleMouseLeave = useCallback(() => {
240
+ setIsHovered(false);
241
+ }, []);
242
+
243
+ // Attach event listeners to the ref
244
+ const setRef = useCallback(
245
+ (element: T | null) => {
246
+ if (ref.current) {
247
+ ref.current.removeEventListener("mouseenter", handleMouseEnter);
248
+ ref.current.removeEventListener("mouseleave", handleMouseLeave);
249
+ }
250
+
251
+ if (element) {
252
+ element.addEventListener("mouseenter", handleMouseEnter);
253
+ element.addEventListener("mouseleave", handleMouseLeave);
254
+ }
255
+
256
+ (ref as React.MutableRefObject<T | null>).current = element;
257
+ },
258
+ [handleMouseEnter, handleMouseLeave],
259
+ );
260
+
261
+ // Return a proxy ref that updates the internal ref
262
+ return [
263
+ isHovered,
264
+ {
265
+ current: ref.current,
266
+ get current() {
267
+ return ref.current;
268
+ },
269
+ set current(element: T | null) {
270
+ setRef(element);
271
+ },
272
+ } as React.RefObject<T>,
273
+ ];
274
+ }
275
+ `,
276
+ useInterval: `import { useEffect } from "react";
277
+
278
+ /**
279
+ * Re-renders component at specified interval.
280
+ *
281
+ * @param callback - Function to call on each interval
282
+ * @param delay - Interval delay in milliseconds (null to pause)
283
+ *
284
+ * @example
285
+ * useInterval(() => {
286
+ * setTime(new Date());
287
+ * }, 1000);
288
+ */
289
+ export function useInterval(callback: () => void, delay: null | number): void {
290
+ useEffect(() => {
291
+ if (delay === null) return;
292
+
293
+ const interval = setInterval(callback, delay);
294
+ return () => {
295
+ clearInterval(interval);
296
+ };
297
+ }, [callback, delay]);
298
+ }
299
+
300
+ /**
301
+ * Re-renders component after a timeout.
302
+ *
303
+ * @param callback - Function to call after timeout
304
+ * @param delay - Timeout delay in milliseconds
305
+ *
306
+ * @example
307
+ * useTimeout(() => {
308
+ * console.log("Timeout completed");
309
+ * }, 2000);
310
+ */
311
+ export function useTimeout(callback: () => void, delay: number): void {
312
+ useEffect(() => {
313
+ const timeout = setTimeout(callback, delay);
314
+ return () => {
315
+ clearTimeout(timeout);
316
+ };
317
+ }, [callback, delay]);
318
+ }
319
+ `,
320
+ useKeyPress: `import { useEffect, useState } from "react";
321
+
322
+ /**
323
+ * Detects if a specific keyboard key is currently pressed.
324
+ *
325
+ * @param targetKey - The key to detect (e.g., "Enter", "ArrowUp", " " for space)
326
+ * @returns Whether the key is currently pressed
327
+ *
328
+ * @example
329
+ * const isEnterPressed = useKeyPress("Enter");
330
+ * const isArrowUpPressed = useKeyPress("ArrowUp");
331
+ *
332
+ * return (
333
+ * <div>
334
+ * <p>Enter pressed: {isEnterPressed ? "Yes" : "No"}</p>
335
+ * <p>Arrow Up pressed: {isArrowUpPressed ? "Yes" : "No"}</p>
336
+ * </div>
337
+ * );
338
+ */
339
+ export function useKeyPress(targetKey: string): boolean {
340
+ const [isKeyPressed, setIsKeyPressed] = useState(false);
341
+
342
+ useEffect(() => {
343
+ const handleKeyDown = (event: KeyboardEvent) => {
344
+ if (event.key === targetKey) {
345
+ setIsKeyPressed(true);
346
+ }
347
+ };
348
+
349
+ const handleKeyUp = (event: KeyboardEvent) => {
350
+ if (event.key === targetKey) {
351
+ setIsKeyPressed(false);
352
+ }
353
+ };
354
+
355
+ window.addEventListener("keydown", handleKeyDown);
356
+ window.addEventListener("keyup", handleKeyUp);
357
+
358
+ return () => {
359
+ window.removeEventListener("keydown", handleKeyDown);
360
+ window.removeEventListener("keyup", handleKeyUp);
361
+ };
362
+ }, [targetKey]);
363
+
364
+ return isKeyPressed;
365
+ }
366
+ `,
367
+ useLocalStorage: `import { useCallback, useEffect, useState } from "react";
368
+
369
+ /**
370
+ * Syncs state with localStorage, persisting across browser sessions.
371
+ *
372
+ * @param key - The localStorage key
373
+ * @param initialValue - The initial value (used if no stored value exists)
374
+ * @returns A tuple of [value, setValue, removeValue]
375
+ *
376
+ * @example
377
+ * const [theme, setTheme, removeTheme] = useLocalStorage("theme", "light");
378
+ *
379
+ * // Update the theme (automatically persisted)
380
+ * setTheme("dark");
381
+ *
382
+ * // Remove from localStorage
383
+ * removeTheme();
384
+ */
385
+ export function useLocalStorage<T>(
386
+ key: string,
387
+ initialValue: T,
388
+ ): [T, (value: ((prev: T) => T) | T) => void, () => void] {
389
+ // Get initial value from localStorage or use provided initial value
390
+ const [storedValue, setStoredValue] = useState<T>(() => {
391
+ if (typeof window === "undefined") {
392
+ return initialValue;
393
+ }
394
+
395
+ try {
396
+ const item = window.localStorage.getItem(key);
397
+ return item ? (JSON.parse(item) as T) : initialValue;
398
+ } catch (error) {
399
+ console.warn(\`Error reading localStorage key "\${key}":\`, error);
400
+ return initialValue;
401
+ }
402
+ });
403
+
404
+ // Update localStorage when value changes
405
+ const setValue = useCallback(
406
+ (value: ((prev: T) => T) | T) => {
407
+ try {
408
+ setStoredValue((prev) => {
409
+ const valueToStore = value instanceof Function ? value(prev) : value;
410
+ if (typeof window !== "undefined") {
411
+ window.localStorage.setItem(key, JSON.stringify(valueToStore));
412
+ }
413
+ return valueToStore;
414
+ });
415
+ } catch (error) {
416
+ console.warn(\`Error setting localStorage key "\${key}":\`, error);
417
+ }
418
+ },
419
+ [key],
420
+ );
421
+
422
+ // Remove from localStorage
423
+ const removeValue = useCallback(() => {
424
+ try {
425
+ if (typeof window !== "undefined") {
426
+ window.localStorage.removeItem(key);
427
+ }
428
+ setStoredValue(initialValue);
429
+ } catch (error) {
430
+ console.warn(\`Error removing localStorage key "\${key}":\`, error);
431
+ }
432
+ }, [key, initialValue]);
433
+
434
+ // Listen for changes in other tabs/windows
435
+ useEffect(() => {
436
+ const handleStorageChange = (event: StorageEvent) => {
437
+ if (event.key === key && event.newValue !== null) {
438
+ try {
439
+ setStoredValue(JSON.parse(event.newValue) as T);
440
+ } catch (error) {
441
+ console.warn(\`Error parsing localStorage key "\${key}":\`, error);
442
+ }
443
+ }
444
+ };
445
+
446
+ window.addEventListener("storage", handleStorageChange);
447
+ return () => {
448
+ window.removeEventListener("storage", handleStorageChange);
449
+ };
450
+ }, [key]);
451
+
452
+ return [storedValue, setValue, removeValue];
453
+ }
454
+ `,
455
+ useMedia: `import { useEffect, useState } from "react";
456
+
457
+ /**
458
+ * Reacts to CSS media query changes.
459
+ *
460
+ * @param query - CSS media query string (e.g., "(max-width: 768px)")
461
+ * @returns Whether the media query matches
462
+ *
463
+ * @example
464
+ * const isMobile = useMedia("(max-width: 768px)");
465
+ * const isDarkMode = useMedia("(prefers-color-scheme: dark)");
466
+ *
467
+ * return (
468
+ * <div>
469
+ * <p>Is mobile: {isMobile ? "Yes" : "No"}</p>
470
+ * <p>Dark mode: {isDarkMode ? "Yes" : "No"}</p>
471
+ * </div>
472
+ * );
473
+ */
474
+ export function useMedia(query: string): boolean {
475
+ const [matches, setMatches] = useState(false);
476
+
477
+ useEffect(() => {
478
+ // Check if window is defined (SSR safety)
479
+ if (typeof window === "undefined") {
480
+ return;
481
+ }
482
+
483
+ try {
484
+ const mediaQueryList = window.matchMedia(query);
485
+
486
+ // Set initial value
487
+ setMatches(mediaQueryList.matches);
488
+
489
+ // Create listener function
490
+ const handleChange = (e: MediaQueryListEvent) => {
491
+ setMatches(e.matches);
492
+ };
493
+
494
+ // Modern browsers use addEventListener
495
+ if (mediaQueryList.addEventListener) {
496
+ mediaQueryList.addEventListener("change", handleChange);
497
+ return () => {
498
+ mediaQueryList.removeEventListener("change", handleChange);
499
+ };
500
+ } else {
501
+ // Fallback for older browsers
502
+ mediaQueryList.addListener(handleChange);
503
+ return () => {
504
+ mediaQueryList.removeListener(handleChange);
505
+ };
506
+ }
507
+ } catch (error) {
508
+ console.warn(\`Invalid media query: "\${query}"\`, error);
509
+ return;
510
+ }
511
+ }, [query]);
512
+
513
+ return matches;
514
+ }
515
+ `,
516
+ useMount: `import { useEffect, useRef } from "react";
517
+
518
+ /**
519
+ * Calls a callback on component mount.
520
+ *
521
+ * @param callback - Function to call on mount
522
+ *
523
+ * @example
524
+ * useMount(() => {
525
+ * console.log("Component mounted");
526
+ * // Initialize resources
527
+ * });
528
+ */
529
+ export function useMount(callback: () => void): void {
530
+ useEffect(callback, []);
531
+ }
532
+
533
+ /**
534
+ * Calls a callback on component unmount.
535
+ *
536
+ * @param callback - Function to call on unmount
537
+ *
538
+ * @example
539
+ * useUnmount(() => {
540
+ * console.log("Component unmounting");
541
+ * // Cleanup resources
542
+ * });
543
+ */
544
+ export function useUnmount(callback: () => void): void {
545
+ const callbackRef = useRef(callback);
546
+
547
+ useEffect(() => {
548
+ callbackRef.current = callback;
549
+ }, [callback]);
550
+
551
+ useEffect(() => {
552
+ return () => {
553
+ callbackRef.current();
554
+ };
555
+ }, []);
556
+ }
557
+ `,
558
+ usePrevious: `import { useEffect, useRef } from "react";
559
+
560
+ /**
561
+ * Tracks the previous value or prop.
562
+ *
563
+ * @param value - The current value to track
564
+ * @returns The previous value from the last render
565
+ *
566
+ * @example
567
+ * const [count, setCount] = useState(0);
568
+ * const prevCount = usePrevious(count);
569
+ *
570
+ * useEffect(() => {
571
+ * console.log(\`Current: \${count}, Previous: \${prevCount}\`);
572
+ * }, [count, prevCount]);
573
+ */
574
+ export function usePrevious<T>(value: T): T | undefined {
575
+ const ref = useRef<T | undefined>(undefined);
576
+
577
+ useEffect(() => {
578
+ ref.current = value;
579
+ }, [value]);
580
+
581
+ return ref.current;
582
+ }
583
+ `,
584
+ useScroll: `import { useCallback, useEffect, useRef, useState } from "react";
585
+
586
+ interface ScrollPosition {
587
+ x: number;
588
+ y: number;
589
+ }
590
+
591
+ /**
592
+ * Tracks scroll position of an element or the window.
593
+ *
594
+ * @param ref - Optional ref to an element. If not provided, tracks window scroll
595
+ * @returns Object with x and y scroll positions
596
+ *
597
+ * @example
598
+ * // Track window scroll
599
+ * const windowScroll = useScroll();
600
+ * console.log(windowScroll.x, windowScroll.y);
601
+ *
602
+ * // Track element scroll
603
+ * const [ref, elementScroll] = useScroll<HTMLDivElement>();
604
+ * return <div ref={ref}>Content</div>;
605
+ */
606
+ export function useScroll(
607
+ ref?: React.RefObject<HTMLElement>,
608
+ ): ScrollPosition {
609
+ const [scroll, setScroll] = useState<ScrollPosition>({ x: 0, y: 0 });
610
+ const internalRef = useRef<HTMLElement | null>(null);
611
+
612
+ const handleScroll = useCallback(() => {
613
+ if (ref?.current) {
614
+ setScroll({
615
+ x: ref.current.scrollLeft,
616
+ y: ref.current.scrollTop,
617
+ });
618
+ } else if (typeof window !== "undefined") {
619
+ setScroll({
620
+ x: window.scrollX,
621
+ y: window.scrollY,
622
+ });
623
+ }
624
+ }, [ref]);
625
+
626
+ useEffect(() => {
627
+ // Set initial scroll position
628
+ handleScroll();
629
+
630
+ if (ref?.current) {
631
+ // Listen to element scroll
632
+ const target = ref.current;
633
+ target.addEventListener("scroll", handleScroll);
634
+ return () => {
635
+ target.removeEventListener("scroll", handleScroll);
636
+ };
637
+ } else if (typeof window !== "undefined") {
638
+ // Listen to window scroll
639
+ window.addEventListener("scroll", handleScroll);
640
+ return () => {
641
+ window.removeEventListener("scroll", handleScroll);
642
+ };
643
+ }
644
+ }, [ref, handleScroll]);
645
+
646
+ return scroll;
647
+ }
648
+
649
+ /**
650
+ * Tracks scroll position with ref attachment for element scroll.
651
+ *
652
+ * @returns Tuple of [ref, scrollPosition]
653
+ *
654
+ * @example
655
+ * const [ref, scroll] = useScrollElement<HTMLDivElement>();
656
+ *
657
+ * return (
658
+ * <div
659
+ * ref={ref}
660
+ * style={{ height: "200px", overflow: "auto" }}
661
+ * >
662
+ * <p>Scroll position: X: {scroll.x}, Y: {scroll.y}</p>
663
+ * {/* long content */}
664
+ * </div>
665
+ * );
666
+ */
667
+ export function useScrollElement<T extends HTMLElement = HTMLElement>(): [
668
+ React.RefObject<T>,
669
+ ScrollPosition,
670
+ ] {
671
+ const ref = useRef<T>(null);
672
+ const scroll = useScroll(ref);
673
+
674
+ return [ref, scroll];
675
+ }
676
+ `,
677
+ useScrollElement: `import { useCallback, useEffect, useRef, useState } from "react";
678
+
679
+ interface ScrollPosition {
680
+ x: number;
681
+ y: number;
682
+ }
683
+
684
+ /**
685
+ * Tracks scroll position of an element or the window.
686
+ *
687
+ * @param ref - Optional ref to an element. If not provided, tracks window scroll
688
+ * @returns Object with x and y scroll positions
689
+ *
690
+ * @example
691
+ * // Track window scroll
692
+ * const windowScroll = useScroll();
693
+ * console.log(windowScroll.x, windowScroll.y);
694
+ *
695
+ * // Track element scroll
696
+ * const [ref, elementScroll] = useScroll<HTMLDivElement>();
697
+ * return <div ref={ref}>Content</div>;
698
+ */
699
+ export function useScroll(
700
+ ref?: React.RefObject<HTMLElement>,
701
+ ): ScrollPosition {
702
+ const [scroll, setScroll] = useState<ScrollPosition>({ x: 0, y: 0 });
703
+ const internalRef = useRef<HTMLElement | null>(null);
704
+
705
+ const handleScroll = useCallback(() => {
706
+ if (ref?.current) {
707
+ setScroll({
708
+ x: ref.current.scrollLeft,
709
+ y: ref.current.scrollTop,
710
+ });
711
+ } else if (typeof window !== "undefined") {
712
+ setScroll({
713
+ x: window.scrollX,
714
+ y: window.scrollY,
715
+ });
716
+ }
717
+ }, [ref]);
718
+
719
+ useEffect(() => {
720
+ // Set initial scroll position
721
+ handleScroll();
722
+
723
+ if (ref?.current) {
724
+ // Listen to element scroll
725
+ const target = ref.current;
726
+ target.addEventListener("scroll", handleScroll);
727
+ return () => {
728
+ target.removeEventListener("scroll", handleScroll);
729
+ };
730
+ } else if (typeof window !== "undefined") {
731
+ // Listen to window scroll
732
+ window.addEventListener("scroll", handleScroll);
733
+ return () => {
734
+ window.removeEventListener("scroll", handleScroll);
735
+ };
736
+ }
737
+ }, [ref, handleScroll]);
738
+
739
+ return scroll;
740
+ }
741
+
742
+ /**
743
+ * Tracks scroll position with ref attachment for element scroll.
744
+ *
745
+ * @returns Tuple of [ref, scrollPosition]
746
+ *
747
+ * @example
748
+ * const [ref, scroll] = useScrollElement<HTMLDivElement>();
749
+ *
750
+ * return (
751
+ * <div
752
+ * ref={ref}
753
+ * style={{ height: "200px", overflow: "auto" }}
754
+ * >
755
+ * <p>Scroll position: X: {scroll.x}, Y: {scroll.y}</p>
756
+ * {/* long content */}
757
+ * </div>
758
+ * );
759
+ */
760
+ export function useScrollElement<T extends HTMLElement = HTMLElement>(): [
761
+ React.RefObject<T>,
762
+ ScrollPosition,
763
+ ] {
764
+ const ref = useRef<T>(null);
765
+ const scroll = useScroll(ref);
766
+
767
+ return [ref, scroll];
768
+ }
769
+ `,
770
+ useSessionStorage: `import { useCallback, useState } from "react";
771
+
772
+ /**
773
+ * Syncs state with sessionStorage, persisting only for the current session.
774
+ *
775
+ * @param key - The sessionStorage key
776
+ * @param initialValue - The initial value (used if no stored value exists)
777
+ * @returns A tuple of [value, setValue, removeValue]
778
+ *
779
+ * @example
780
+ * const [sessionData, setSessionData, removeSessionData] = useSessionStorage("session", "default");
781
+ *
782
+ * // Update the session data (automatically persisted)
783
+ * setSessionData("newData");
784
+ *
785
+ * // Remove from sessionStorage
786
+ * removeSessionData();
787
+ */
788
+ export function useSessionStorage<T>(
789
+ key: string,
790
+ initialValue: T,
791
+ ): [T, (value: ((prev: T) => T) | T) => void, () => void] {
792
+ // Get initial value from sessionStorage or use provided initial value
793
+ const [storedValue, setStoredValue] = useState<T>(() => {
794
+ if (typeof window === "undefined") {
795
+ return initialValue;
796
+ }
797
+
798
+ try {
799
+ const item = window.sessionStorage.getItem(key);
800
+ return item ? (JSON.parse(item) as T) : initialValue;
801
+ } catch (error) {
802
+ console.warn(\`Error reading sessionStorage key "\${key}":\`, error);
803
+ return initialValue;
804
+ }
805
+ });
806
+
807
+ // Update sessionStorage when value changes
808
+ const setValue = useCallback(
809
+ (value: ((prev: T) => T) | T) => {
810
+ try {
811
+ setStoredValue((prev) => {
812
+ const valueToStore = value instanceof Function ? value(prev) : value;
813
+ if (typeof window !== "undefined") {
814
+ window.sessionStorage.setItem(key, JSON.stringify(valueToStore));
815
+ }
816
+ return valueToStore;
817
+ });
818
+ } catch (error) {
819
+ console.warn(\`Error setting sessionStorage key "\${key}":\`, error);
820
+ }
821
+ },
822
+ [key],
823
+ );
824
+
825
+ // Remove from sessionStorage
826
+ const removeValue = useCallback(() => {
827
+ try {
828
+ if (typeof window !== "undefined") {
829
+ window.sessionStorage.removeItem(key);
830
+ }
831
+ setStoredValue(initialValue);
832
+ } catch (error) {
833
+ console.warn(\`Error removing sessionStorage key "\${key}":\`, error);
834
+ }
835
+ }, [key, initialValue]);
836
+
837
+ return [storedValue, setValue, removeValue];
838
+ }
839
+ `,
840
+ useThrottle: `import { useEffect, useRef, useState } from "react";
841
+
842
+ /**
843
+ * Throttles a value to update at most once per specified interval.
844
+ *
845
+ * @param value - The value to throttle
846
+ * @param interval - The throttle interval in milliseconds (default: 500ms)
847
+ * @returns The throttled value
848
+ *
849
+ * @example
850
+ * const [position, setPosition] = useState({ x: 0, y: 0 });
851
+ * const throttledPosition = useThrottle(position, 100);
852
+ *
853
+ * useEffect(() => {
854
+ * // This effect runs at most every 100ms
855
+ * updateCursor(throttledPosition);
856
+ * }, [throttledPosition]);
857
+ */
858
+ export function useThrottle<T>(value: T, interval = 500): T {
859
+ const [throttledValue, setThrottledValue] = useState<T>(value);
860
+ const lastUpdated = useRef<number>(Date.now());
861
+
862
+ useEffect(() => {
863
+ const now = Date.now();
864
+ const elapsed = now - lastUpdated.current;
865
+
866
+ if (elapsed >= interval) {
867
+ lastUpdated.current = now;
868
+ setThrottledValue(value);
869
+ } else {
870
+ const timer = setTimeout(() => {
871
+ lastUpdated.current = Date.now();
872
+ setThrottledValue(value);
873
+ }, interval - elapsed);
874
+
875
+ return () => {
876
+ clearTimeout(timer);
877
+ };
878
+ }
879
+ }, [value, interval]);
880
+
881
+ return throttledValue;
882
+ }
883
+ `,
884
+ useTimeout: `import { useEffect } from "react";
885
+
886
+ /**
887
+ * Re-renders component at specified interval.
888
+ *
889
+ * @param callback - Function to call on each interval
890
+ * @param delay - Interval delay in milliseconds (null to pause)
891
+ *
892
+ * @example
893
+ * useInterval(() => {
894
+ * setTime(new Date());
895
+ * }, 1000);
896
+ */
897
+ export function useInterval(callback: () => void, delay: null | number): void {
898
+ useEffect(() => {
899
+ if (delay === null) return;
900
+
901
+ const interval = setInterval(callback, delay);
902
+ return () => {
903
+ clearInterval(interval);
904
+ };
905
+ }, [callback, delay]);
906
+ }
907
+
908
+ /**
909
+ * Re-renders component after a timeout.
910
+ *
911
+ * @param callback - Function to call after timeout
912
+ * @param delay - Timeout delay in milliseconds
913
+ *
914
+ * @example
915
+ * useTimeout(() => {
916
+ * console.log("Timeout completed");
917
+ * }, 2000);
918
+ */
919
+ export function useTimeout(callback: () => void, delay: number): void {
920
+ useEffect(() => {
921
+ const timeout = setTimeout(callback, delay);
922
+ return () => {
923
+ clearTimeout(timeout);
924
+ };
925
+ }, [callback, delay]);
926
+ }
927
+ `,
928
+ useToggle: `import { useCallback, useState } from "react";
929
+
930
+ /**
931
+ * Alias for useToggle with boolean semantics.
932
+ *
933
+ * @param initialValue - Initial boolean value (default: false)
934
+ * @returns Tuple of [value, { setTrue, setFalse, toggle }]
935
+ *
936
+ * @example
937
+ * const [isEnabled, handlers] = useBoolean(false);
938
+ *
939
+ * return (
940
+ * <>
941
+ * <button onClick={handlers.toggle}>Toggle</button>
942
+ * <button onClick={handlers.setTrue}>Enable</button>
943
+ * <button onClick={handlers.setFalse}>Disable</button>
944
+ * </>
945
+ * );
946
+ */
947
+ export function useBoolean(initialValue = false): [
948
+ boolean,
949
+ {
950
+ setFalse: () => void;
951
+ setTrue: () => void;
952
+ toggle: () => void;
953
+ },
954
+ ] {
955
+ const [value, toggle, setValue] = useToggle(initialValue);
956
+
957
+ return [
958
+ value,
959
+ {
960
+ setFalse: useCallback(() => {
961
+ setValue(false);
962
+ }, [setValue]),
963
+ setTrue: useCallback(() => {
964
+ setValue(true);
965
+ }, [setValue]),
966
+ toggle,
967
+ },
968
+ ];
969
+ }
970
+
971
+ /**
972
+ * Toggle a boolean value with a callback.
973
+ *
974
+ * @param initialValue - Initial boolean value (default: false)
975
+ * @returns Tuple of [value, toggle, setValue]
976
+ *
977
+ * @example
978
+ * const [isOpen, toggle] = useToggle(false);
979
+ *
980
+ * return (
981
+ * <>
982
+ * <button onClick={toggle}>Toggle</button>
983
+ * {isOpen && <div>Content</div>}
984
+ * </>
985
+ * );
986
+ */
987
+ export function useToggle(
988
+ initialValue = false,
989
+ ): [boolean, () => void, (value: boolean) => void] {
990
+ const [value, setValue] = useState(initialValue);
991
+
992
+ const toggle = useCallback(() => {
993
+ setValue((prev) => !prev);
994
+ }, []);
995
+
996
+ return [value, toggle, setValue];
997
+ }
998
+ `,
999
+ useUnmount: `import { useEffect, useRef } from "react";
1000
+
1001
+ /**
1002
+ * Calls a callback on component mount.
1003
+ *
1004
+ * @param callback - Function to call on mount
1005
+ *
1006
+ * @example
1007
+ * useMount(() => {
1008
+ * console.log("Component mounted");
1009
+ * // Initialize resources
1010
+ * });
1011
+ */
1012
+ export function useMount(callback: () => void): void {
1013
+ useEffect(callback, []);
1014
+ }
1015
+
1016
+ /**
1017
+ * Calls a callback on component unmount.
1018
+ *
1019
+ * @param callback - Function to call on unmount
1020
+ *
1021
+ * @example
1022
+ * useUnmount(() => {
1023
+ * console.log("Component unmounting");
1024
+ * // Cleanup resources
1025
+ * });
1026
+ */
1027
+ export function useUnmount(callback: () => void): void {
1028
+ const callbackRef = useRef(callback);
1029
+
1030
+ useEffect(() => {
1031
+ callbackRef.current = callback;
1032
+ }, [callback]);
1033
+
1034
+ useEffect(() => {
1035
+ return () => {
1036
+ callbackRef.current();
1037
+ };
1038
+ }, []);
1039
+ }
1040
+ `,
1041
+ useWindowSize: `import { useEffect, useState } from "react";
1042
+
1043
+ interface WindowSize {
1044
+ height: number | undefined;
1045
+ width: number | undefined;
1046
+ }
1047
+
1048
+ /**
1049
+ * Tracks window dimensions.
1050
+ *
1051
+ * @returns Object with width and height of the window
1052
+ *
1053
+ * @example
1054
+ * const { width, height } = useWindowSize();
1055
+ *
1056
+ * return (
1057
+ * <div>
1058
+ * Window size: {width}x{height}
1059
+ * </div>
1060
+ * );
1061
+ */
1062
+ export function useWindowSize(): WindowSize {
1063
+ const [windowSize, setWindowSize] = useState<WindowSize>({
1064
+ height: undefined,
1065
+ width: undefined,
1066
+ });
1067
+
1068
+ useEffect(() => {
1069
+ const handleResize = () => {
1070
+ setWindowSize({
1071
+ height: window.innerHeight,
1072
+ width: window.innerWidth,
1073
+ });
1074
+ };
1075
+
1076
+ // Call once on mount
1077
+ handleResize();
1078
+
1079
+ window.addEventListener("resize", handleResize);
1080
+ return () => {
1081
+ window.removeEventListener("resize", handleResize);
1082
+ };
1083
+ }, []);
1084
+
1085
+ return windowSize;
1086
+ }
1087
+ `,
1088
+ };
1089
+ export function getHookTemplate(hookName) {
1090
+ const template = templates[hookName];
1091
+ if (!template) {
1092
+ throw new Error(`Template for hook "${hookName}" not found`);
1093
+ }
1094
+ return template;
1095
+ }
1096
+ //# sourceMappingURL=get-hook-template.js.map