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