@tarsis/toolkit 0.7.3 → 0.7.4

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/dist/hooks.cjs CHANGED
@@ -283,44 +283,30 @@ var useInterval = (callback, delay = null, deps = DEFAULT_DEPENDENCIES$1) => {
283
283
  //#endregion
284
284
  //#region src/hooks/useMatchMedia.ts
285
285
  var hasMatchMedia = () => {
286
- return typeof window?.matchMedia === "function";
286
+ return typeof window !== "undefined" && typeof window.matchMedia === "function";
287
+ };
288
+ var getMatchMedia = (query) => {
289
+ if (!hasMatchMedia()) return null;
290
+ try {
291
+ return window.matchMedia(query);
292
+ } catch {
293
+ return null;
294
+ }
287
295
  };
288
296
  var useMatchMedia = (query) => {
289
- const [matches, setMatches] = (0, react.useState)(() => {
290
- if (!hasMatchMedia()) return null;
291
- try {
292
- return window.matchMedia(query).matches;
293
- } catch {
294
- return null;
295
- }
296
- });
297
- const matchMedia = (0, react.useMemo)(() => {
298
- if (!hasMatchMedia()) return null;
299
- try {
300
- return window.matchMedia(query);
301
- } catch {
302
- return null;
303
- }
304
- }, [query]);
305
- const handleChange = (0, react.useCallback)((event) => {
306
- setMatches(event.matches);
307
- }, []);
308
- (0, react.useEffect)(() => {
309
- if (!matchMedia) return;
310
- requestAnimationFrame(() => {
311
- setMatches(matchMedia.matches);
312
- });
297
+ return (0, react.useSyncExternalStore)((0, react.useCallback)((onStoreChange) => {
298
+ const matchMedia = getMatchMedia(query);
299
+ if (!matchMedia) return () => {};
313
300
  if (matchMedia.addEventListener) {
314
- matchMedia.addEventListener("change", handleChange);
315
- return () => matchMedia.removeEventListener("change", handleChange);
301
+ matchMedia.addEventListener("change", onStoreChange);
302
+ return () => matchMedia.removeEventListener("change", onStoreChange);
316
303
  }
317
- const legacyHandler = (event) => {
318
- handleChange(event);
319
- };
304
+ const legacyHandler = () => onStoreChange();
320
305
  matchMedia.addListener(legacyHandler);
321
306
  return () => matchMedia.removeListener(legacyHandler);
322
- }, [matchMedia, handleChange]);
323
- return matches;
307
+ }, [query]), (0, react.useCallback)(() => {
308
+ return getMatchMedia(query)?.matches ?? null;
309
+ }, [query]), () => null);
324
310
  };
325
311
  //#endregion
326
312
  //#region src/hooks/useOklch.ts
package/dist/hooks.d.ts CHANGED
@@ -1,291 +1 @@
1
- import { default as default_2 } from 'react';
2
- import { DependencyList } from 'react';
3
- import { Ref } from 'react';
4
- import { RefCallback } from 'react';
5
- import { RefObject } from 'react';
6
-
7
- declare type FormFieldAria = {
8
- 'aria-describedby'?: string;
9
- 'aria-invalid'?: boolean;
10
- 'aria-errormessage'?: string;
11
- id: string;
12
- };
13
-
14
- export declare type FormFieldIds = {
15
- fieldId: string;
16
- labelId: string;
17
- errorId: string;
18
- hintId: string;
19
- descriptionId: string;
20
- };
21
-
22
- declare type GsapCallable = (...args: unknown[]) => unknown;
23
-
24
- declare type GsapCleanup = () => void;
25
-
26
- export declare type GsapContextLike = {
27
- add(methodName: string, func: GsapCallable, scope?: Element | string | object): GsapCallable;
28
- add<T extends GsapCallable>(func: T, scope?: Element | string | object): ReturnType<T>;
29
- ignore(func: GsapCallable): void;
30
- kill(revert?: boolean): void;
31
- revert(config?: object): void;
32
- clear(): void;
33
- isReverted: boolean;
34
- };
35
-
36
- export declare function mergeRefs<T>(...refs: PossibleRef<T>[]): RefCallback<T>;
37
-
38
- declare type PossibleRef<T> = Ref<T> | undefined | null;
39
-
40
- export declare function useAnimatedText(text: string, delimiter?: 'letter' | 'word'): string;
41
-
42
- export declare const useBowser: () => UseBowserResult | null;
43
-
44
- declare interface UseBowserResult {
45
- isMobile: boolean;
46
- isDesktop: boolean;
47
- isMacOS: boolean;
48
- isIOS: boolean;
49
- isSafari: boolean;
50
- isFirefox: boolean;
51
- isChrome: boolean;
52
- }
53
-
54
- export declare function useControllableState<T>(controlledValue: T | undefined, defaultValue: T, onChange?: (value: T) => void): [T, (next: T | ((prev: T) => T)) => void];
55
-
56
- /**
57
- * Custom hook that debounces a callback function to delay its execution until after
58
- * a specified wait time has elapsed since the last time it was invoked.
59
- *
60
- * Debouncing ensures that the callback is only executed once after a series of rapid calls,
61
- * waiting for a pause in the calls before executing.
62
- *
63
- * @param callback - The function to debounce
64
- * @param delay - The delay time (in milliseconds) to wait before executing the callback
65
- * @param options - Optional configuration object
66
- * @param options.maxWait - Maximum time before function must be invoked
67
- * @param options.leading - If true, invoke on the leading edge
68
- * @returns A debounced version of the callback function with a `cancel` method
69
- *
70
- * @example
71
- * ```tsx
72
- * const debouncedSearch = useDebounce((query: string) => {
73
- * console.log('Searching for:', query)
74
- * }, 300)
75
- *
76
- * return <input onChange={(e) => debouncedSearch(e.target.value)} />
77
- * ```
78
- *
79
- * @example
80
- * ```tsx
81
- * // With maxWait and leading options
82
- * const debouncedSearch = useDebounce(
83
- * (query: string) => console.log('Searching for:', query),
84
- * 300,
85
- * { maxWait: 1000, leading: true }
86
- * )
87
- * ```
88
- *
89
- * @example
90
- * ```tsx
91
- * // Cancel pending debounced call
92
- * const debouncedSearch = useDebounce((query: string) => {
93
- * console.log('Searching for:', query)
94
- * }, 300)
95
- *
96
- * debouncedSearch('test')
97
- * debouncedSearch.cancel() // Cancels the pending call
98
- * ```
99
- */
100
- export declare const useDebounce: <T extends (...args: any[]) => any>(callback: T, delay: number, { maxWait, leading }?: {
101
- maxWait?: number;
102
- leading?: boolean;
103
- }) => T & {
104
- cancel: VoidFunction;
105
- };
106
-
107
- /**
108
- * A React hook that implements the useEffectEvent pattern from React RFC.
109
- * This hook allows you to define an event handler that can access the latest props/state
110
- * without triggering effect dependency lists when the handler changes.
111
- * @see https://github.com/reactjs/rfcs/blob/useevent/text/0000-useevent.md#internal-implementation
112
- * @react https://react.dev/learn/separating-events-from-effects
113
- */
114
- export declare function useEffectEvent<TArgs extends any[], TReturn extends undefined>(callback: undefined): (...args: TArgs) => TReturn;
115
-
116
- export declare function useEffectEvent<TArgs extends any[], TReturn>(callback: (...args: TArgs) => TReturn): (...args: TArgs) => TReturn;
117
-
118
- export declare function useEffectEvent<TArgs extends any[], TReturn>(callback: ((...args: TArgs) => TReturn) | undefined): (...args: TArgs) => TReturn | undefined;
119
-
120
- export declare function useFormField(options?: UseFormFieldOptions): UseFormFieldReturn;
121
-
122
- export declare type UseFormFieldOptions = {
123
- id?: string;
124
- hasError?: boolean;
125
- hasHint?: boolean;
126
- };
127
-
128
- export declare type UseFormFieldReturn = {
129
- ids: FormFieldIds;
130
- labelProps: {
131
- htmlFor: string;
132
- id: string;
133
- };
134
- fieldProps: FormFieldAria;
135
- errorProps: {
136
- id: string;
137
- role: 'alert';
138
- };
139
- hintProps: {
140
- id: string;
141
- };
142
- };
143
-
144
- /**
145
- * Creates a GSAP context scoped to the given ref.
146
- * All animations created inside the callback are automatically
147
- * reverted on unmount — preventing DOM leak bugs.
148
- */
149
- export declare function useGsapContext(scope: RefObject<HTMLElement | null>, callback: (ctx: GsapContextLike) => void | GsapCleanup, deps?: unknown[]): RefObject<GsapContextLike | null>;
150
-
151
- /**
152
- * Custom hook that manages an interval with automatic cleanup.
153
- * Provides a reliable way to handle intervals that are properly cleaned up
154
- * on component unmount or when dependencies change.
155
- *
156
- * @param callback - The function to execute on each interval
157
- * @param delay - The delay in milliseconds (null to clear the interval)
158
- * @param deps - Optional dependency array. If provided, interval restarts when deps change.
159
- * If not provided, callback updates via ref without restarting interval.
160
- *
161
- * @example
162
- * ```tsx
163
- * // Runs continuously, callback updates via ref
164
- * useInterval(() => {
165
- * console.log('This runs every second')
166
- * }, 1000)
167
- *
168
- * // Restarts interval when count changes
169
- * useInterval(() => {
170
- * console.log('Count:', count)
171
- * }, 1000, [count])
172
- * ```
173
- */
174
- export declare const useInterval: (callback: VoidFunction, delay?: number | null, deps?: DependencyList) => void;
175
-
176
- export declare const useLiveRef: <T>(value: T) => RefObject<T>;
177
-
178
- export declare const useMatchMedia: (query: string) => boolean | null;
179
-
180
- export declare function useMergeRefs<T>(...refs: PossibleRef<T>[]): RefCallback<T>;
181
-
182
- export declare const useOklch: () => void;
183
-
184
- /**
185
- * Custom hook that detects clicks outside of a specified element and triggers a callback.
186
- *
187
- * Useful for implementing dropdown menus, modals, or any component that should close
188
- * when the user clicks outside of it.
189
- *
190
- * @param ref - React ref object pointing to the element to monitor
191
- * @param callback - Function to call when a click occurs outside the element
192
- * @param isActive - Whether the outside click detection should be active (default: true)
193
- *
194
- * @example
195
- * ```tsx
196
- * const ref = useRef<HTMLDivElement>(null)
197
- * const [isOpen, setIsOpen] = useState(false)
198
- *
199
- * useOutsideClick(ref, () => setIsOpen(false), isOpen)
200
- *
201
- * return <div ref={ref}>...</div>
202
- * ```
203
- */
204
- export declare const useOutsideClick: <T extends HTMLElement = HTMLElement>(ref: RefObject<T | null>, callback: VoidFunction, isActive?: boolean) => void;
205
-
206
- export declare const usePreviousRender: <T>(value: T) => T | undefined;
207
-
208
- export declare const usePreviousState: <T>(value: T) => T | undefined;
209
-
210
- /**
211
- * Custom hook that provides a requestAnimationFrame-based callback execution.
212
- * Useful for performance-critical animations and continuous updates that should
213
- * be synchronized with the browser's refresh rate.
214
- *
215
- * The callback will be executed on the next animation frame, and can be
216
- * controlled with an enabled flag for conditional execution.
217
- *
218
- * @param callback - The function to execute on each animation frame
219
- * @param enabled - Whether the animation loop should be active (default: true)
220
- * @returns A function to manually trigger the animation frame callback
221
- *
222
- * @example
223
- * ```tsx
224
- * const animate = useRaf(() => {
225
- * // Animation logic here
226
- * console.log('Animation frame')
227
- * }, isAnimating)
228
- *
229
- * // Manually trigger
230
- * animate()
231
- * ```
232
- */
233
- export declare const useRaf: (callback: VoidFunction, enabled?: boolean) => VoidFunction;
234
-
235
- export declare function useReducedMotion(): boolean;
236
-
237
- /**
238
- * Custom hook that throttles a callback function to limit how often it can be invoked.
239
- *
240
- * Throttling ensures that the callback is executed at most once per specified time interval,
241
- * regardless of how many times the throttled function is called.
242
- *
243
- * @param callback - The function to throttle
244
- * @param delay - The minimum time interval (in milliseconds) between executions
245
- * @returns A throttled version of the callback function
246
- *
247
- * @example
248
- * ```tsx
249
- * const throttledClick = useThrottle(() => {
250
- * console.log('Button clicked')
251
- * }, 500)
252
- *
253
- * return <button onClick={throttledClick}>Click me</button>
254
- * ```
255
- */
256
- export declare const useThrottle: <T extends (...args: any[]) => any>(callback: T, delay: number) => T;
257
-
258
- /**
259
- * Custom hook that manages a timeout with automatic cleanup.
260
- * Provides a reliable way to handle timeouts that are properly cleaned up
261
- * on component unmount or when dependencies change.
262
- *
263
- * @param callback - The function to execute after the delay
264
- * @param delay - The delay in milliseconds (null to clear the timeout)
265
- * @param deps - Optional dependency array. If provided, timeout restarts when deps change.
266
- * If not provided, callback updates via ref without restarting timeout.
267
- *
268
- * @example
269
- * ```tsx
270
- * // Runs once, callback updates via ref
271
- * useTimeout(() => {
272
- * console.log('This runs after 1 second')
273
- * }, 1000)
274
- *
275
- * // Restarts timeout when count changes
276
- * useTimeout(() => {
277
- * console.log('Count:', count)
278
- * }, 1000, [count])
279
- * ```
280
- */
281
- export declare const useTimeout: (callback: VoidFunction, delay?: number | null, deps?: DependencyList) => void;
282
-
283
- /**
284
- * Universal layout effect that uses useLayoutEffect in browser environments
285
- * and falls back to useEffect in SSR environments
286
- */
287
- export declare const useUniversalLayoutEffect: typeof default_2.useEffect;
288
-
289
- export declare const useWindowReady: () => boolean;
290
-
291
1
  export { }
package/dist/hooks.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { n as useMergeRefs, t as mergeRefs } from "./useMergeRefs-BM2-gSLn.js";
2
- import React, { useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
2
+ import React, { useCallback, useEffect, useId, useMemo, useRef, useState, useSyncExternalStore } from "react";
3
3
  import gsap from "gsap";
4
4
  import { animate, useMotionValue } from "motion/react";
5
5
  import Bowser from "bowser";
@@ -278,44 +278,30 @@ var useInterval = (callback, delay = null, deps = DEFAULT_DEPENDENCIES$1) => {
278
278
  //#endregion
279
279
  //#region src/hooks/useMatchMedia.ts
280
280
  var hasMatchMedia = () => {
281
- return typeof window?.matchMedia === "function";
281
+ return typeof window !== "undefined" && typeof window.matchMedia === "function";
282
+ };
283
+ var getMatchMedia = (query) => {
284
+ if (!hasMatchMedia()) return null;
285
+ try {
286
+ return window.matchMedia(query);
287
+ } catch {
288
+ return null;
289
+ }
282
290
  };
283
291
  var useMatchMedia = (query) => {
284
- const [matches, setMatches] = useState(() => {
285
- if (!hasMatchMedia()) return null;
286
- try {
287
- return window.matchMedia(query).matches;
288
- } catch {
289
- return null;
290
- }
291
- });
292
- const matchMedia = useMemo(() => {
293
- if (!hasMatchMedia()) return null;
294
- try {
295
- return window.matchMedia(query);
296
- } catch {
297
- return null;
298
- }
299
- }, [query]);
300
- const handleChange = useCallback((event) => {
301
- setMatches(event.matches);
302
- }, []);
303
- useEffect(() => {
304
- if (!matchMedia) return;
305
- requestAnimationFrame(() => {
306
- setMatches(matchMedia.matches);
307
- });
292
+ return useSyncExternalStore(useCallback((onStoreChange) => {
293
+ const matchMedia = getMatchMedia(query);
294
+ if (!matchMedia) return () => {};
308
295
  if (matchMedia.addEventListener) {
309
- matchMedia.addEventListener("change", handleChange);
310
- return () => matchMedia.removeEventListener("change", handleChange);
296
+ matchMedia.addEventListener("change", onStoreChange);
297
+ return () => matchMedia.removeEventListener("change", onStoreChange);
311
298
  }
312
- const legacyHandler = (event) => {
313
- handleChange(event);
314
- };
299
+ const legacyHandler = () => onStoreChange();
315
300
  matchMedia.addListener(legacyHandler);
316
301
  return () => matchMedia.removeListener(legacyHandler);
317
- }, [matchMedia, handleChange]);
318
- return matches;
302
+ }, [query]), useCallback(() => {
303
+ return getMatchMedia(query)?.matches ?? null;
304
+ }, [query]), () => null);
319
305
  };
320
306
  //#endregion
321
307
  //#region src/hooks/useOklch.ts