@sohanemon/utils 6.2.8 → 6.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.
- package/dist/components.d.ts +84 -0
- package/dist/components.js +1 -0
- package/dist/hooks-hkNH7WgA.js +1 -0
- package/dist/hooks.d.ts +309 -0
- package/dist/hooks.js +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +665 -0
- package/dist/index.d.ts +665 -2
- package/dist/index.js +1 -2
- package/package.json +49 -32
- package/dist/components/html-injector.d.ts +0 -50
- package/dist/components/html-injector.js +0 -108
- package/dist/components/index.d.ts +0 -5
- package/dist/components/index.js +0 -7
- package/dist/components/media-wrapper.d.ts +0 -10
- package/dist/components/media-wrapper.js +0 -14
- package/dist/components/responsive-indicator.d.ts +0 -2
- package/dist/components/responsive-indicator.js +0 -68
- package/dist/components/scrollable-marker.d.ts +0 -1
- package/dist/components/scrollable-marker.js +0 -56
- package/dist/functions/cookie.d.ts +0 -6
- package/dist/functions/cookie.js +0 -22
- package/dist/functions/deepmerge.d.ts +0 -59
- package/dist/functions/deepmerge.js +0 -116
- package/dist/functions/hydrate.d.ts +0 -15
- package/dist/functions/hydrate.js +0 -31
- package/dist/functions/index.d.ts +0 -8
- package/dist/functions/index.js +0 -8
- package/dist/functions/object.d.ts +0 -93
- package/dist/functions/object.js +0 -67
- package/dist/functions/poll.d.ts +0 -38
- package/dist/functions/poll.js +0 -69
- package/dist/functions/schedule.d.ts +0 -12
- package/dist/functions/schedule.js +0 -29
- package/dist/functions/shield.d.ts +0 -18
- package/dist/functions/shield.js +0 -15
- package/dist/functions/utils.d.ts +0 -243
- package/dist/functions/utils.js +0 -439
- package/dist/hooks/action.d.ts +0 -20
- package/dist/hooks/action.js +0 -84
- package/dist/hooks/async.d.ts +0 -30
- package/dist/hooks/async.js +0 -82
- package/dist/hooks/index.d.ts +0 -192
- package/dist/hooks/index.js +0 -533
- package/dist/hooks/schedule.d.ts +0 -36
- package/dist/hooks/schedule.js +0 -68
- package/dist/types/gates.d.ts +0 -133
- package/dist/types/gates.js +0 -1
- package/dist/types/guards.d.ts +0 -6
- package/dist/types/guards.js +0 -29
- package/dist/types/index.d.ts +0 -3
- package/dist/types/index.js +0 -3
- package/dist/types/utilities.d.ts +0 -62
- package/dist/types/utilities.js +0 -1
package/dist/hooks/index.js
DELETED
|
@@ -1,533 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import * as React from 'react';
|
|
3
|
-
import { copyToClipboard } from '../functions';
|
|
4
|
-
export * from './action';
|
|
5
|
-
export * from './async';
|
|
6
|
-
export * from './schedule';
|
|
7
|
-
/**
|
|
8
|
-
* Hook to detect clicks outside of a referenced element.
|
|
9
|
-
* @param callback - A function to invoke when a click outside is detected.
|
|
10
|
-
* @returns A React ref object to attach to the target element.
|
|
11
|
-
*/
|
|
12
|
-
export const useClickOutside = (callback = () => alert('clicked outside')) => {
|
|
13
|
-
const ref = React.useRef(null);
|
|
14
|
-
const listener = (e) => {
|
|
15
|
-
if (ref.current && !ref.current.contains(e.target)) {
|
|
16
|
-
callback();
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
React.useEffect(() => {
|
|
20
|
-
document.addEventListener('mousedown', listener);
|
|
21
|
-
document.addEventListener('touchstart', listener);
|
|
22
|
-
return () => {
|
|
23
|
-
document.removeEventListener('mousedown', listener);
|
|
24
|
-
document.removeEventListener('touchstart', listener);
|
|
25
|
-
};
|
|
26
|
-
});
|
|
27
|
-
return ref;
|
|
28
|
-
};
|
|
29
|
-
/**
|
|
30
|
-
* Hook to match a media query based on Tailwind CSS breakpoints or custom queries.
|
|
31
|
-
* @param tailwindBreakpoint - The Tailwind breakpoint or custom query string.
|
|
32
|
-
* @returns A boolean indicating whether the media query matches.
|
|
33
|
-
*/
|
|
34
|
-
export function useMediaQuery(tailwindBreakpoint) {
|
|
35
|
-
const parsedQuery = React.useMemo(() => {
|
|
36
|
-
switch (tailwindBreakpoint) {
|
|
37
|
-
case 'sm':
|
|
38
|
-
return '(min-width: 640px)';
|
|
39
|
-
case 'md':
|
|
40
|
-
return '(min-width: 768px)';
|
|
41
|
-
case 'lg':
|
|
42
|
-
return '(min-width: 1024px)';
|
|
43
|
-
case 'xl':
|
|
44
|
-
return '(min-width: 1280px)';
|
|
45
|
-
case '2xl':
|
|
46
|
-
return '(min-width: 1536px)';
|
|
47
|
-
default:
|
|
48
|
-
return tailwindBreakpoint;
|
|
49
|
-
}
|
|
50
|
-
}, [tailwindBreakpoint]);
|
|
51
|
-
const getMatches = (parsedQuery) => {
|
|
52
|
-
if (typeof window !== 'undefined') {
|
|
53
|
-
return window.matchMedia(parsedQuery).matches;
|
|
54
|
-
}
|
|
55
|
-
return false;
|
|
56
|
-
};
|
|
57
|
-
const [matches, setMatches] = React.useState(getMatches(parsedQuery));
|
|
58
|
-
const handleChange = () => {
|
|
59
|
-
setMatches(getMatches(parsedQuery));
|
|
60
|
-
};
|
|
61
|
-
useIsomorphicEffect(() => {
|
|
62
|
-
const matchMedia = window.matchMedia(parsedQuery);
|
|
63
|
-
handleChange();
|
|
64
|
-
matchMedia.addEventListener('change', handleChange);
|
|
65
|
-
return () => {
|
|
66
|
-
matchMedia.removeEventListener('change', handleChange);
|
|
67
|
-
};
|
|
68
|
-
}, [parsedQuery]);
|
|
69
|
-
return matches;
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Runs an effect only once when the component mounts.
|
|
73
|
-
* @param effect - The effect callback function.
|
|
74
|
-
*/
|
|
75
|
-
export function useEffectOnce(effect) {
|
|
76
|
-
React.useEffect(effect, []);
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Runs an effect only when dependencies update, excluding the initial render.
|
|
80
|
-
* @param effect - The effect callback function.
|
|
81
|
-
* @param deps - Dependency array for the effect.
|
|
82
|
-
*/
|
|
83
|
-
export function useUpdateEffect(effect, deps) {
|
|
84
|
-
const isInitialMount = React.useRef(true);
|
|
85
|
-
// biome-ignore lint/correctness/useExhaustiveDependencies: only update specific
|
|
86
|
-
React.useEffect(() => {
|
|
87
|
-
if (isInitialMount.current) {
|
|
88
|
-
isInitialMount.current = false;
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
return effect();
|
|
92
|
-
}
|
|
93
|
-
}, deps);
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Debounces a state value with a specified delay.
|
|
97
|
-
* @param state - The state value to debounce.
|
|
98
|
-
* @param delay - The debounce delay in milliseconds (default: 500ms).
|
|
99
|
-
* @returns The debounced state value.
|
|
100
|
-
*/
|
|
101
|
-
export function useDebounce(state, delay = 500) {
|
|
102
|
-
const [debouncedState, setDebouncedState] = React.useState(state);
|
|
103
|
-
React.useEffect(() => {
|
|
104
|
-
const timer = setTimeout(() => setDebouncedState(state), delay);
|
|
105
|
-
return () => {
|
|
106
|
-
clearTimeout(timer);
|
|
107
|
-
};
|
|
108
|
-
}, [state, delay]);
|
|
109
|
-
return debouncedState;
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* Hook to handle effects with layout synchronization in the browser.
|
|
113
|
-
*/
|
|
114
|
-
export const useIsomorphicEffect = typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect;
|
|
115
|
-
/**
|
|
116
|
-
* Hook to invoke a callback after a specified timeout.
|
|
117
|
-
* @param callback - The callback function to invoke.
|
|
118
|
-
* @param delay - The timeout delay in milliseconds (default: 1000ms).
|
|
119
|
-
*/
|
|
120
|
-
export function useTimeout(callback, delay = 1000) {
|
|
121
|
-
const savedCallback = React.useRef(callback);
|
|
122
|
-
useIsomorphicEffect(() => {
|
|
123
|
-
savedCallback.current = callback;
|
|
124
|
-
}, [callback]);
|
|
125
|
-
React.useEffect(() => {
|
|
126
|
-
if (!delay && delay !== 0) {
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
const id = setTimeout(() => savedCallback.current(), delay);
|
|
130
|
-
return () => clearTimeout(id);
|
|
131
|
-
}, [delay]);
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Hook to add and remove a window event listener.
|
|
135
|
-
* @param type - The type of the event to listen for.
|
|
136
|
-
* @param listener - The event listener callback.
|
|
137
|
-
* @param options - Options for the event listener.
|
|
138
|
-
*/
|
|
139
|
-
export function useWindowEvent(type, listener, options) {
|
|
140
|
-
React.useEffect(() => {
|
|
141
|
-
window.addEventListener(type, listener, options);
|
|
142
|
-
return () => window.removeEventListener(type, listener, options);
|
|
143
|
-
}, [type, listener, options]);
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* Hook to persist state in session storage.
|
|
147
|
-
* @param key - The key for session storage.
|
|
148
|
-
* @param defaultValue - The default value if no value is found in session storage.
|
|
149
|
-
* @returns A tuple of the stored value and an updater function.
|
|
150
|
-
*/
|
|
151
|
-
export const useSessionStorage = (key, defaultValue) => {
|
|
152
|
-
const [storedValue, setStoredValue] = React.useState(defaultValue);
|
|
153
|
-
React.useEffect(() => {
|
|
154
|
-
const value = sessionStorage.getItem(key);
|
|
155
|
-
if (value) {
|
|
156
|
-
setStoredValue(JSON.parse(value));
|
|
157
|
-
}
|
|
158
|
-
}, [key]);
|
|
159
|
-
const updateStoredValue = React.useCallback((valueOrFn) => {
|
|
160
|
-
setStoredValue((prev) => {
|
|
161
|
-
const newValue = typeof valueOrFn === 'function'
|
|
162
|
-
? valueOrFn(prev)
|
|
163
|
-
: valueOrFn;
|
|
164
|
-
sessionStorage.setItem(key, JSON.stringify(newValue));
|
|
165
|
-
return newValue;
|
|
166
|
-
});
|
|
167
|
-
}, [key]);
|
|
168
|
-
return [storedValue, updateStoredValue];
|
|
169
|
-
};
|
|
170
|
-
/**
|
|
171
|
-
* Hook to persist state in local storage.
|
|
172
|
-
* @param key - The key for local storage.
|
|
173
|
-
* @param defaultValue - The default value if no value is found in local storage.
|
|
174
|
-
* @returns A tuple of the stored value and an updater function.
|
|
175
|
-
*/
|
|
176
|
-
export const useLocalStorage = (key, defaultValue) => {
|
|
177
|
-
const [storedValue, setStoredValue] = React.useState(defaultValue);
|
|
178
|
-
React.useEffect(() => {
|
|
179
|
-
const value = localStorage.getItem(key);
|
|
180
|
-
if (value) {
|
|
181
|
-
setStoredValue(JSON.parse(value));
|
|
182
|
-
}
|
|
183
|
-
}, [key]);
|
|
184
|
-
const updateStoredValue = React.useCallback((valueOrFn) => {
|
|
185
|
-
let newValue;
|
|
186
|
-
if (typeof valueOrFn === 'function') {
|
|
187
|
-
newValue = valueOrFn(storedValue);
|
|
188
|
-
}
|
|
189
|
-
else {
|
|
190
|
-
newValue = valueOrFn;
|
|
191
|
-
}
|
|
192
|
-
localStorage.setItem(key, JSON.stringify(newValue));
|
|
193
|
-
setStoredValue(newValue);
|
|
194
|
-
}, [key]);
|
|
195
|
-
return [storedValue, updateStoredValue];
|
|
196
|
-
};
|
|
197
|
-
/**
|
|
198
|
-
* Hook to manage URL parameters as state.
|
|
199
|
-
* @param key - The URL parameter key.
|
|
200
|
-
* @param defaultValue - The default value if the parameter is not present.
|
|
201
|
-
* @returns A tuple of the parameter value and a setter function.
|
|
202
|
-
*/
|
|
203
|
-
export const useUrlParams = (key, defaultValue) => {
|
|
204
|
-
const [value, setValue] = React.useState(defaultValue);
|
|
205
|
-
React.useEffect(() => {
|
|
206
|
-
const params = new URLSearchParams(window.location.search);
|
|
207
|
-
const paramValue = params.get(key);
|
|
208
|
-
if (paramValue !== null) {
|
|
209
|
-
setValue(paramValue);
|
|
210
|
-
}
|
|
211
|
-
}, [key]);
|
|
212
|
-
const updateValue = (newValue) => {
|
|
213
|
-
const params = new URLSearchParams(window.location.search);
|
|
214
|
-
params.set(key, String(newValue));
|
|
215
|
-
window.history.pushState({}, '', `${window.location.pathname}?${params}`);
|
|
216
|
-
setValue(newValue);
|
|
217
|
-
};
|
|
218
|
-
return [value, updateValue];
|
|
219
|
-
};
|
|
220
|
-
/**
|
|
221
|
-
* Hook to select a DOM element by CSS selector.
|
|
222
|
-
* @param selector - The CSS selector string.
|
|
223
|
-
* @returns The selected DOM element or null if not found.
|
|
224
|
-
*/
|
|
225
|
-
export const useQuerySelector = (selector) => {
|
|
226
|
-
const [element, setElement] = React.useState(null);
|
|
227
|
-
const elementRef = React.useRef(null);
|
|
228
|
-
React.useLayoutEffect(() => {
|
|
229
|
-
const referenceElement = document.querySelector(selector);
|
|
230
|
-
if (!referenceElement)
|
|
231
|
-
return;
|
|
232
|
-
if (elementRef.current !== referenceElement) {
|
|
233
|
-
elementRef.current = referenceElement;
|
|
234
|
-
setElement(referenceElement);
|
|
235
|
-
}
|
|
236
|
-
const resizeObserver = new ResizeObserver(() => {
|
|
237
|
-
if (elementRef.current !== referenceElement) {
|
|
238
|
-
elementRef.current = referenceElement;
|
|
239
|
-
setElement(referenceElement);
|
|
240
|
-
}
|
|
241
|
-
});
|
|
242
|
-
resizeObserver.observe(referenceElement);
|
|
243
|
-
return () => {
|
|
244
|
-
resizeObserver.disconnect();
|
|
245
|
-
};
|
|
246
|
-
}, [selector]);
|
|
247
|
-
return element;
|
|
248
|
-
};
|
|
249
|
-
/**
|
|
250
|
-
* Hook to detect if the code is running on the client side.
|
|
251
|
-
* @returns A boolean indicating whether the code is running on the client.
|
|
252
|
-
*/
|
|
253
|
-
export function useIsClient() {
|
|
254
|
-
const [isClient, setIsClient] = React.useState(false);
|
|
255
|
-
React.useEffect(() => {
|
|
256
|
-
setIsClient(true);
|
|
257
|
-
}, []);
|
|
258
|
-
return isClient;
|
|
259
|
-
}
|
|
260
|
-
/**
|
|
261
|
-
* Hook to lock scroll by disabling body overflow.
|
|
262
|
-
*/
|
|
263
|
-
export function useLockScroll() {
|
|
264
|
-
React.useLayoutEffect(() => {
|
|
265
|
-
const originalStyle = window.getComputedStyle(document.body).overflow;
|
|
266
|
-
document.body.style.overflow = 'hidden';
|
|
267
|
-
return () => {
|
|
268
|
-
document.body.style.overflow = originalStyle;
|
|
269
|
-
};
|
|
270
|
-
}, []);
|
|
271
|
-
}
|
|
272
|
-
/**
|
|
273
|
-
* Hook to copy text to the clipboard and track the copy status.
|
|
274
|
-
* @param timeout - The timeout duration in milliseconds to reset the copy status (default: 2000ms).
|
|
275
|
-
* @returns An object with the `isCopied` state and `copy` function.
|
|
276
|
-
*/
|
|
277
|
-
export function useCopyToClipboard({ timeout = 2000 }) {
|
|
278
|
-
const [isCopied, setIsCopied] = React.useState(false);
|
|
279
|
-
const copy = (value) => {
|
|
280
|
-
copyToClipboard(value, () => {
|
|
281
|
-
setIsCopied(true);
|
|
282
|
-
setTimeout(() => {
|
|
283
|
-
setIsCopied(false);
|
|
284
|
-
}, timeout);
|
|
285
|
-
});
|
|
286
|
-
};
|
|
287
|
-
return { isCopied, copy };
|
|
288
|
-
}
|
|
289
|
-
/**
|
|
290
|
-
*
|
|
291
|
-
* Hook to calculate the height of an element based on viewport and other block heights.
|
|
292
|
-
* @param params - Configuration object for height calculation.
|
|
293
|
-
* @returns The calculated height.
|
|
294
|
-
*/
|
|
295
|
-
export const useHeightCalculation = ({ blockIds = [], margin = 0, substract = true, dynamic = false, }) => {
|
|
296
|
-
const [height, setTableHeight] = React.useState(500);
|
|
297
|
-
const handleResize = () => {
|
|
298
|
-
const blockHeight = blockIds.reduce((prevHeight, id) => prevHeight + (document.getElementById(id)?.clientHeight || 0), 0);
|
|
299
|
-
const height = substract
|
|
300
|
-
? window.innerHeight - blockHeight - margin
|
|
301
|
-
: blockHeight + margin;
|
|
302
|
-
setTableHeight(height);
|
|
303
|
-
};
|
|
304
|
-
useIsomorphicEffect(() => {
|
|
305
|
-
handleResize();
|
|
306
|
-
if (!dynamic)
|
|
307
|
-
return;
|
|
308
|
-
if (typeof dynamic === 'string') {
|
|
309
|
-
const resizableElement = document.getElementById(dynamic);
|
|
310
|
-
const resizeObserver = new ResizeObserver((entries) => {
|
|
311
|
-
for (const _ of entries)
|
|
312
|
-
handleResize();
|
|
313
|
-
});
|
|
314
|
-
if (resizableElement)
|
|
315
|
-
resizeObserver.observe(resizableElement);
|
|
316
|
-
return () => resizeObserver?.disconnect();
|
|
317
|
-
}
|
|
318
|
-
window.addEventListener('resize', handleResize);
|
|
319
|
-
return () => window.removeEventListener('resize', handleResize);
|
|
320
|
-
}, []);
|
|
321
|
-
return height;
|
|
322
|
-
};
|
|
323
|
-
/**
|
|
324
|
-
* Hook to calculate dimensions (height and width) of an element based on viewport and other block dimensions.
|
|
325
|
-
* @param params - Configuration object for dimension calculation.
|
|
326
|
-
* @returns An object containing the calculated height and width.
|
|
327
|
-
*/
|
|
328
|
-
export const useDomCalculation = ({ blockIds = [], margin = 0, substract = true, dynamic = false, onChange, }) => {
|
|
329
|
-
const [dimensions, setDimensions] = React.useState({
|
|
330
|
-
height: 500,
|
|
331
|
-
width: 500,
|
|
332
|
-
});
|
|
333
|
-
const handleCalculation = React.useCallback(() => {
|
|
334
|
-
const blocksHeight = blockIds.reduce((prevHeight, id) => prevHeight + (document.getElementById(id)?.clientHeight || 0), 0);
|
|
335
|
-
const blocksWidth = blockIds.reduce((prevWidth, id) => prevWidth + (document.getElementById(id)?.clientWidth || 0), 0);
|
|
336
|
-
const height = substract
|
|
337
|
-
? window.innerHeight - blocksHeight - margin
|
|
338
|
-
: blocksHeight + margin;
|
|
339
|
-
const width = substract
|
|
340
|
-
? window.innerWidth - blocksWidth - margin
|
|
341
|
-
: blocksWidth + margin;
|
|
342
|
-
setDimensions((prev) => {
|
|
343
|
-
// Only update state if dimensions have actually changed
|
|
344
|
-
if (prev.height === height && prev.width === width) {
|
|
345
|
-
return prev;
|
|
346
|
-
}
|
|
347
|
-
return { height, width };
|
|
348
|
-
});
|
|
349
|
-
onChange?.({
|
|
350
|
-
blocksWidth,
|
|
351
|
-
blocksHeight,
|
|
352
|
-
remainingWidth: width,
|
|
353
|
-
remainingHeight: height,
|
|
354
|
-
});
|
|
355
|
-
}, [blockIds, margin, substract, onChange]);
|
|
356
|
-
useIsomorphicEffect(() => {
|
|
357
|
-
handleCalculation();
|
|
358
|
-
const cleanups = [];
|
|
359
|
-
if (blockIds.length > 0) {
|
|
360
|
-
const observer = new MutationObserver((mutations) => {
|
|
361
|
-
let shouldRecalculate = false;
|
|
362
|
-
for (const mutation of mutations) {
|
|
363
|
-
for (const node of mutation.addedNodes) {
|
|
364
|
-
if (node instanceof Element) {
|
|
365
|
-
if (blockIds.includes(node.id) ||
|
|
366
|
-
blockIds.some((id) => node.querySelector(`#${CSS.escape(id)}`))) {
|
|
367
|
-
shouldRecalculate = true;
|
|
368
|
-
break;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
if (shouldRecalculate)
|
|
373
|
-
break;
|
|
374
|
-
for (const node of mutation.removedNodes) {
|
|
375
|
-
if (node instanceof Element) {
|
|
376
|
-
if (blockIds.includes(node.id) ||
|
|
377
|
-
blockIds.some((id) => node.querySelector(`#${CSS.escape(id)}`))) {
|
|
378
|
-
shouldRecalculate = true;
|
|
379
|
-
break;
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
if (shouldRecalculate)
|
|
384
|
-
break;
|
|
385
|
-
}
|
|
386
|
-
if (shouldRecalculate) {
|
|
387
|
-
handleCalculation();
|
|
388
|
-
}
|
|
389
|
-
});
|
|
390
|
-
observer.observe(document.body, {
|
|
391
|
-
childList: true,
|
|
392
|
-
subtree: true,
|
|
393
|
-
});
|
|
394
|
-
cleanups.push(() => observer.disconnect());
|
|
395
|
-
}
|
|
396
|
-
if (dynamic) {
|
|
397
|
-
if (typeof dynamic === 'string') {
|
|
398
|
-
const resizableElement = document.getElementById(dynamic);
|
|
399
|
-
if (resizableElement) {
|
|
400
|
-
const resizeObserver = new ResizeObserver(handleCalculation);
|
|
401
|
-
resizeObserver.observe(resizableElement);
|
|
402
|
-
cleanups.push(() => resizeObserver.unobserve(resizableElement));
|
|
403
|
-
cleanups.push(() => resizeObserver.disconnect());
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
else {
|
|
407
|
-
window.addEventListener('resize', handleCalculation);
|
|
408
|
-
cleanups.push(() => window.removeEventListener('resize', handleCalculation));
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
return () => {
|
|
412
|
-
for (const cleanup of cleanups) {
|
|
413
|
-
cleanup();
|
|
414
|
-
}
|
|
415
|
-
};
|
|
416
|
-
}, [handleCalculation, dynamic, blockIds.join(',')]);
|
|
417
|
-
return dimensions;
|
|
418
|
-
};
|
|
419
|
-
/**
|
|
420
|
-
* Hook to detect if the user is scrolling.
|
|
421
|
-
* @returns An object containing the isScrolling state and a ref to the scrollable container.
|
|
422
|
-
*/
|
|
423
|
-
export const useIsScrolling = () => {
|
|
424
|
-
const [isScrolling, setIsScrolling] = React.useState(false);
|
|
425
|
-
const scrollTimerRef = React.useRef(null);
|
|
426
|
-
const scrollableContainerRef = React.useRef(null);
|
|
427
|
-
useEffectOnce(() => {
|
|
428
|
-
const mainElement = scrollableContainerRef.current;
|
|
429
|
-
if (!mainElement)
|
|
430
|
-
return;
|
|
431
|
-
const handleScroll = () => {
|
|
432
|
-
setIsScrolling(true);
|
|
433
|
-
if (scrollTimerRef.current) {
|
|
434
|
-
clearTimeout(scrollTimerRef.current);
|
|
435
|
-
}
|
|
436
|
-
// Set a timeout to mark scrolling as finished after delay
|
|
437
|
-
scrollTimerRef.current = setTimeout(() => {
|
|
438
|
-
setIsScrolling(false);
|
|
439
|
-
}, 150);
|
|
440
|
-
};
|
|
441
|
-
handleScroll();
|
|
442
|
-
mainElement.addEventListener('scroll', handleScroll);
|
|
443
|
-
return () => {
|
|
444
|
-
mainElement.removeEventListener('scroll', handleScroll);
|
|
445
|
-
if (scrollTimerRef.current) {
|
|
446
|
-
clearTimeout(scrollTimerRef.current);
|
|
447
|
-
}
|
|
448
|
-
};
|
|
449
|
-
});
|
|
450
|
-
return {
|
|
451
|
-
isScrolling,
|
|
452
|
-
scrollableContainerRef,
|
|
453
|
-
};
|
|
454
|
-
};
|
|
455
|
-
/**
|
|
456
|
-
* Hook to detect if the user is at the top of the page.
|
|
457
|
-
* @returns An object containing the isAtTop state and a ref to the scrollable container.
|
|
458
|
-
*/
|
|
459
|
-
export const useIsAtTop = ({ offset } = {}) => {
|
|
460
|
-
const [isAtTop, setIsAtTop] = React.useState(true);
|
|
461
|
-
const scrollableContainerRef = React.useRef(null);
|
|
462
|
-
useEffectOnce(() => {
|
|
463
|
-
const mainElement = scrollableContainerRef.current;
|
|
464
|
-
if (!mainElement)
|
|
465
|
-
return;
|
|
466
|
-
const handleScroll = () => {
|
|
467
|
-
const scrolled = mainElement.scrollTop > (offset ?? 10);
|
|
468
|
-
setIsAtTop(!scrolled);
|
|
469
|
-
};
|
|
470
|
-
handleScroll();
|
|
471
|
-
mainElement.addEventListener('scroll', handleScroll);
|
|
472
|
-
return () => {
|
|
473
|
-
mainElement.removeEventListener('scroll', handleScroll);
|
|
474
|
-
};
|
|
475
|
-
});
|
|
476
|
-
return { scrollableContainerRef, isAtTop };
|
|
477
|
-
};
|
|
478
|
-
/**
|
|
479
|
-
* React hook that tracks when an element enters or leaves the viewport
|
|
480
|
-
* using the Intersection Observer API.
|
|
481
|
-
*
|
|
482
|
-
* @example
|
|
483
|
-
* ```tsx
|
|
484
|
-
* const { ref, isIntersecting } = useIntersection({
|
|
485
|
-
* threshold: 0.1,
|
|
486
|
-
* onInteractionStart: () => console.log('👀 Element entered viewport'),
|
|
487
|
-
* onInteractionEnd: () => console.log('🙈 Element left viewport'),
|
|
488
|
-
* });
|
|
489
|
-
*
|
|
490
|
-
* return <div ref={ref}>Watch me</div>;
|
|
491
|
-
* ```
|
|
492
|
-
*
|
|
493
|
-
* @param options - Configuration for the intersection observer.
|
|
494
|
-
* @returns Object containing:
|
|
495
|
-
* - `ref`: React ref to attach to the observed element.
|
|
496
|
-
* - `isIntersecting`: Whether the element is currently visible.
|
|
497
|
-
*/
|
|
498
|
-
export const useIntersection = ({ threshold = 0.1, root = null, rootMargin, onInteractionStart, onInteractionEnd, } = {}) => {
|
|
499
|
-
const [isIntersecting, setIsIntersecting] = React.useState(false);
|
|
500
|
-
const ref = React.useRef(null);
|
|
501
|
-
React.useEffect(() => {
|
|
502
|
-
if (!ref.current)
|
|
503
|
-
return;
|
|
504
|
-
const observer = new IntersectionObserver((entries) => {
|
|
505
|
-
for (const entry of entries) {
|
|
506
|
-
if (entry.isIntersecting) {
|
|
507
|
-
if (!isIntersecting) {
|
|
508
|
-
onInteractionStart?.();
|
|
509
|
-
setIsIntersecting(true);
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
else {
|
|
513
|
-
if (isIntersecting) {
|
|
514
|
-
onInteractionEnd?.();
|
|
515
|
-
setIsIntersecting(false);
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
}, { threshold, root, rootMargin });
|
|
520
|
-
observer.observe(ref.current);
|
|
521
|
-
return () => {
|
|
522
|
-
observer.disconnect();
|
|
523
|
-
};
|
|
524
|
-
}, [
|
|
525
|
-
threshold,
|
|
526
|
-
root,
|
|
527
|
-
rootMargin,
|
|
528
|
-
onInteractionStart,
|
|
529
|
-
onInteractionEnd,
|
|
530
|
-
isIntersecting,
|
|
531
|
-
]);
|
|
532
|
-
return { ref, isIntersecting };
|
|
533
|
-
};
|
package/dist/hooks/schedule.d.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
import { ScheduleOpts } from '../functions';
|
|
3
|
-
import { Task } from '../functions/schedule';
|
|
4
|
-
/**
|
|
5
|
-
* useSchedule — run non-urgent work later, without blocking UI.
|
|
6
|
-
*/
|
|
7
|
-
export declare function useSchedule(options?: ScheduleOpts): (task: Task) => void;
|
|
8
|
-
/**
|
|
9
|
-
* useScheduledEffect — Runs a non-urgent task in a React component without blocking UI rendering.
|
|
10
|
-
*
|
|
11
|
-
* This hook is like `useEffect`, but the provided task is executed
|
|
12
|
-
* with low priority using `requestIdleCallback` (if available)
|
|
13
|
-
* or a fallback scheduler. Useful for heavy computations, logging,
|
|
14
|
-
* analytics, or background work that doesn't need to block render.
|
|
15
|
-
*
|
|
16
|
-
* @param {Function} effect - The function to run later. Can be synchronous or return a Promise.
|
|
17
|
-
* @param {React.DependencyList[]} deps - Dependency array; task will re-run whenever these change.
|
|
18
|
-
* @param {ScheduleOpts} [options] - Optional scheduling options.
|
|
19
|
-
* @param {number} [options.timeout] - Max time (ms) to wait before executing the task. Defaults to 10000.
|
|
20
|
-
*
|
|
21
|
-
* @example
|
|
22
|
-
* ```tsx
|
|
23
|
-
* import React from 'react';
|
|
24
|
-
* import { useScheduledEffect } from './hooks/useScheduledEffect';
|
|
25
|
-
*
|
|
26
|
-
* function MyComponent({ userId }: { userId: string }) {
|
|
27
|
-
* useScheduledEffect(() => {
|
|
28
|
-
* // non-blocking analytics or heavy work
|
|
29
|
-
* console.log('Sending analytics for user:', userId);
|
|
30
|
-
* }, [userId], { timeout: 5000 });
|
|
31
|
-
*
|
|
32
|
-
* return <div>Component loaded. Task will run later 😎</div>;
|
|
33
|
-
* }
|
|
34
|
-
* ```
|
|
35
|
-
*/
|
|
36
|
-
export declare function useScheduledEffect(effect: () => void | (() => void), deps?: React.DependencyList, options?: ScheduleOpts): void;
|
package/dist/hooks/schedule.js
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
import { schedule as _schedule } from '../functions/schedule';
|
|
3
|
-
/**
|
|
4
|
-
* useSchedule — run non-urgent work later, without blocking UI.
|
|
5
|
-
*/
|
|
6
|
-
export function useSchedule(options = {}) {
|
|
7
|
-
const { timeout = 10000 } = options;
|
|
8
|
-
const schedule = React.useCallback((task) => {
|
|
9
|
-
const exec = () => {
|
|
10
|
-
try {
|
|
11
|
-
React.startTransition(() => {
|
|
12
|
-
task();
|
|
13
|
-
});
|
|
14
|
-
}
|
|
15
|
-
catch (err) {
|
|
16
|
-
console.log('⚡[schedule.tsx] Failed: ', err);
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
if ('requestIdleCallback' in window) {
|
|
20
|
-
requestIdleCallback(exec, { timeout });
|
|
21
|
-
}
|
|
22
|
-
else {
|
|
23
|
-
_schedule(exec);
|
|
24
|
-
}
|
|
25
|
-
}, [timeout]);
|
|
26
|
-
return schedule;
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* useScheduledEffect — Runs a non-urgent task in a React component without blocking UI rendering.
|
|
30
|
-
*
|
|
31
|
-
* This hook is like `useEffect`, but the provided task is executed
|
|
32
|
-
* with low priority using `requestIdleCallback` (if available)
|
|
33
|
-
* or a fallback scheduler. Useful for heavy computations, logging,
|
|
34
|
-
* analytics, or background work that doesn't need to block render.
|
|
35
|
-
*
|
|
36
|
-
* @param {Function} effect - The function to run later. Can be synchronous or return a Promise.
|
|
37
|
-
* @param {React.DependencyList[]} deps - Dependency array; task will re-run whenever these change.
|
|
38
|
-
* @param {ScheduleOpts} [options] - Optional scheduling options.
|
|
39
|
-
* @param {number} [options.timeout] - Max time (ms) to wait before executing the task. Defaults to 10000.
|
|
40
|
-
*
|
|
41
|
-
* @example
|
|
42
|
-
* ```tsx
|
|
43
|
-
* import React from 'react';
|
|
44
|
-
* import { useScheduledEffect } from './hooks/useScheduledEffect';
|
|
45
|
-
*
|
|
46
|
-
* function MyComponent({ userId }: { userId: string }) {
|
|
47
|
-
* useScheduledEffect(() => {
|
|
48
|
-
* // non-blocking analytics or heavy work
|
|
49
|
-
* console.log('Sending analytics for user:', userId);
|
|
50
|
-
* }, [userId], { timeout: 5000 });
|
|
51
|
-
*
|
|
52
|
-
* return <div>Component loaded. Task will run later 😎</div>;
|
|
53
|
-
* }
|
|
54
|
-
* ```
|
|
55
|
-
*/
|
|
56
|
-
export function useScheduledEffect(effect, deps = [], options = {}) {
|
|
57
|
-
const schedule = useSchedule(options);
|
|
58
|
-
React.useEffect(() => {
|
|
59
|
-
let cleanup;
|
|
60
|
-
schedule(() => {
|
|
61
|
-
cleanup = effect();
|
|
62
|
-
});
|
|
63
|
-
return () => {
|
|
64
|
-
if (typeof cleanup === 'function')
|
|
65
|
-
cleanup?.();
|
|
66
|
-
};
|
|
67
|
-
}, [schedule, ...deps]);
|
|
68
|
-
}
|