react-simplikit 0.0.34 → 0.0.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/components/ImpressionArea/index.cjs +17 -20
- package/dist/components/ImpressionArea/index.d.cts +2 -2
- package/dist/components/SwitchCase/index.d.cts +4 -4
- package/dist/hooks/useAsyncEffect/index.d.cts +1 -1
- package/dist/hooks/useCallbackOncePerRender/index.d.cts +2 -3
- package/dist/hooks/useControlledState/index.d.cts +1 -1
- package/dist/hooks/useCounter/index.cjs +1 -1
- package/dist/hooks/useCounter/index.d.cts +3 -5
- package/dist/hooks/useDebouncedCallback/index.cjs +144 -0
- package/dist/hooks/useDebouncedCallback/index.d.cts +32 -0
- package/dist/hooks/useDoubleClick/index.d.cts +4 -4
- package/dist/hooks/useGeolocation/index.cjs +153 -0
- package/dist/hooks/useGeolocation/index.d.cts +106 -0
- package/dist/hooks/useImpressionRef/index.d.cts +2 -2
- package/dist/hooks/useIntersectionObserver/index.d.cts +2 -2
- package/dist/hooks/useIsomorphicLayoutEffect/index.d.cts +3 -3
- package/dist/hooks/useLongPress/index.cjs +123 -0
- package/dist/hooks/useLongPress/index.d.cts +70 -0
- package/dist/hooks/useMap/index.cjs +77 -0
- package/dist/hooks/useMap/index.d.cts +46 -0
- package/dist/hooks/usePrevious/index.d.cts +2 -2
- package/dist/hooks/useRefEffect/index.d.cts +1 -1
- package/dist/hooks/useStorageState/index.d.cts +1 -1
- package/dist/hooks/useThrottle/index.d.cts +1 -1
- package/dist/hooks/useVisibilityEvent/index.d.cts +1 -1
- package/dist/index.cjs +453 -98
- package/dist/index.d.cts +9 -1
- package/esm/components/ImpressionArea/index.d.ts +2 -2
- package/esm/components/ImpressionArea/index.js +17 -20
- package/esm/components/SwitchCase/index.d.ts +4 -4
- package/esm/hooks/useAsyncEffect/index.d.ts +1 -1
- package/esm/hooks/useCallbackOncePerRender/index.d.ts +2 -3
- package/esm/hooks/useControlledState/index.d.ts +1 -1
- package/esm/hooks/useCounter/index.d.ts +3 -5
- package/esm/hooks/useCounter/index.js +1 -1
- package/esm/hooks/useDebouncedCallback/index.d.ts +32 -0
- package/esm/hooks/useDebouncedCallback/index.js +117 -0
- package/esm/hooks/useDoubleClick/index.d.ts +4 -4
- package/esm/hooks/useGeolocation/index.d.ts +106 -0
- package/esm/hooks/useGeolocation/index.js +126 -0
- package/esm/hooks/useImpressionRef/index.d.ts +2 -2
- package/esm/hooks/useIntersectionObserver/index.d.ts +2 -2
- package/esm/hooks/useIsomorphicLayoutEffect/index.d.ts +3 -3
- package/esm/hooks/useLongPress/index.d.ts +70 -0
- package/esm/hooks/useLongPress/index.js +96 -0
- package/esm/hooks/useMap/index.d.ts +46 -0
- package/esm/hooks/useMap/index.js +50 -0
- package/esm/hooks/usePrevious/index.d.ts +2 -2
- package/esm/hooks/useRefEffect/index.d.ts +1 -1
- package/esm/hooks/useStorageState/index.d.ts +1 -1
- package/esm/hooks/useThrottle/index.d.ts +1 -1
- package/esm/hooks/useVisibilityEvent/index.d.ts +1 -1
- package/esm/index.d.ts +9 -1
- package/esm/index.js +421 -74
- package/package.json +2 -3
package/dist/index.d.cts
CHANGED
|
@@ -4,13 +4,20 @@ export { SwitchCase } from './components/SwitchCase/index.cjs';
|
|
|
4
4
|
export { useAsyncEffect } from './hooks/useAsyncEffect/index.cjs';
|
|
5
5
|
export { useBooleanState } from './hooks/useBooleanState/index.cjs';
|
|
6
6
|
export { useCallbackOncePerRender } from './hooks/useCallbackOncePerRender/index.cjs';
|
|
7
|
+
export { useControlledState } from './hooks/useControlledState/index.cjs';
|
|
7
8
|
export { useCounter } from './hooks/useCounter/index.cjs';
|
|
8
9
|
export { useDebounce } from './hooks/useDebounce/index.cjs';
|
|
10
|
+
export { useDebouncedCallback } from './hooks/useDebouncedCallback/index.cjs';
|
|
11
|
+
export { useDoubleClick } from './hooks/useDoubleClick/index.cjs';
|
|
12
|
+
export { useGeolocation } from './hooks/useGeolocation/index.cjs';
|
|
9
13
|
export { useImpressionRef } from './hooks/useImpressionRef/index.cjs';
|
|
10
14
|
export { useInputState } from './hooks/useInputState/index.cjs';
|
|
11
15
|
export { useIntersectionObserver } from './hooks/useIntersectionObserver/index.cjs';
|
|
12
16
|
export { useInterval } from './hooks/useInterval/index.cjs';
|
|
17
|
+
export { useIsomorphicLayoutEffect } from './hooks/useIsomorphicLayoutEffect/index.cjs';
|
|
13
18
|
export { useLoading } from './hooks/useLoading/index.cjs';
|
|
19
|
+
export { useLongPress } from './hooks/useLongPress/index.cjs';
|
|
20
|
+
export { useMap } from './hooks/useMap/index.cjs';
|
|
14
21
|
export { useOutsideClickEffect } from './hooks/useOutsideClickEffect/index.cjs';
|
|
15
22
|
export { usePreservedCallback } from './hooks/usePreservedCallback/index.cjs';
|
|
16
23
|
export { usePreservedReference } from './hooks/usePreservedReference/index.cjs';
|
|
@@ -22,6 +29,7 @@ export { useTimeout } from './hooks/useTimeout/index.cjs';
|
|
|
22
29
|
export { useToggle } from './hooks/useToggle/index.cjs';
|
|
23
30
|
export { useVisibilityEvent } from './hooks/useVisibilityEvent/index.cjs';
|
|
24
31
|
export { buildContext } from './utils/buildContext/index.cjs';
|
|
32
|
+
export { mergeProps } from './utils/mergeProps/index.cjs';
|
|
25
33
|
export { mergeRefs } from './utils/mergeRefs/index.cjs';
|
|
26
|
-
import 'react/jsx-runtime';
|
|
27
34
|
import 'react';
|
|
35
|
+
import 'react/jsx-runtime';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as react from 'react';
|
|
2
2
|
import { ElementType, Ref, ReactNode } from 'react';
|
|
3
3
|
import { UseImpressionRefOptions } from '../../hooks/useImpressionRef/index.js';
|
|
4
4
|
|
|
@@ -40,6 +40,6 @@ type Props<Tag extends ElementType> = React.ComponentPropsWithoutRef<Tag> & UseI
|
|
|
40
40
|
* );
|
|
41
41
|
* }
|
|
42
42
|
*/
|
|
43
|
-
declare
|
|
43
|
+
declare const ImpressionArea: react.ForwardRefExoticComponent<Omit<Props<ElementType>, "ref"> & react.RefAttributes<Element>>;
|
|
44
44
|
|
|
45
45
|
export { ImpressionArea };
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// src/components/ImpressionArea/ImpressionArea.tsx
|
|
2
|
+
import { forwardRef } from "react";
|
|
3
|
+
|
|
1
4
|
// src/hooks/useImpressionRef/useImpressionRef.ts
|
|
2
5
|
import { useRef as useRef4 } from "react";
|
|
3
6
|
|
|
@@ -241,26 +244,20 @@ function mergeRefs(...refs) {
|
|
|
241
244
|
|
|
242
245
|
// src/components/ImpressionArea/ImpressionArea.tsx
|
|
243
246
|
import { jsx } from "react/jsx-runtime";
|
|
244
|
-
|
|
245
|
-
as,
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
})
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
areaThreshold,
|
|
259
|
-
timeThreshold,
|
|
260
|
-
rootMargin
|
|
261
|
-
});
|
|
262
|
-
return /* @__PURE__ */ jsx(Component, { ref: mergeRefs(ref, impressionRef), ...props });
|
|
263
|
-
}
|
|
247
|
+
var ImpressionArea = forwardRef(
|
|
248
|
+
({ as, rootMargin, areaThreshold, timeThreshold, onImpressionStart, onImpressionEnd, ...props }, ref) => {
|
|
249
|
+
const Component = as ?? "div";
|
|
250
|
+
const impressionRef = useImpressionRef({
|
|
251
|
+
onImpressionStart,
|
|
252
|
+
onImpressionEnd,
|
|
253
|
+
areaThreshold,
|
|
254
|
+
timeThreshold,
|
|
255
|
+
rootMargin
|
|
256
|
+
});
|
|
257
|
+
return /* @__PURE__ */ jsx(Component, { ref: mergeRefs(ref, impressionRef), ...props });
|
|
258
|
+
}
|
|
259
|
+
);
|
|
260
|
+
ImpressionArea.displayName = "ImpressionArea";
|
|
264
261
|
export {
|
|
265
262
|
ImpressionArea
|
|
266
263
|
};
|
|
@@ -14,15 +14,15 @@ type Props<Case> = {
|
|
|
14
14
|
* similar to a `switch-case` statement. It is useful when you need to conditionally render different
|
|
15
15
|
* components depending on a specific state.
|
|
16
16
|
*
|
|
17
|
-
* @param {
|
|
17
|
+
* @param {Case} value - The value to compare against.
|
|
18
18
|
* The component associated with the matching key in `caseBy` will be rendered.
|
|
19
|
-
* @param {
|
|
19
|
+
* @param {Partial<{ [P in StringifiedValue<Case>]: () => ReactElement | null }>} caseBy - An object that maps values to
|
|
20
20
|
* components to render. The keys represent possible values, and the values are functions returning
|
|
21
21
|
* the corresponding components.
|
|
22
|
-
* @param {() =>
|
|
22
|
+
* @param {() => ReactElement | null} [defaultComponent] - The component to render if `value` does not match
|
|
23
23
|
* any key in `caseBy`.
|
|
24
24
|
*
|
|
25
|
-
* @returns {
|
|
25
|
+
* @returns {ReactElement | null} A React component that conditionally renders based on cases.
|
|
26
26
|
*
|
|
27
27
|
* @example
|
|
28
28
|
* function App() {
|
|
@@ -2,7 +2,7 @@ import { DependencyList } from 'react';
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @description
|
|
5
|
-
* `useAsyncEffect` is a
|
|
5
|
+
* `useAsyncEffect` is a React hook for handling asynchronous side effects in React components.
|
|
6
6
|
* It follows the same cleanup pattern as `useEffect` while ensuring async operations are handled safely.
|
|
7
7
|
*
|
|
8
8
|
* @param {() => Promise<void | (() => void)>} [effect] - An asynchronous function executed in the `useEffect` pattern.
|
|
@@ -2,9 +2,8 @@ import { DependencyList } from 'react';
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @description
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* be repeated, even if the component re-renders.
|
|
5
|
+
* `useCallbackOncePerRender` is a React hook that ensures a callback function is executed only once, regardless of how many times it's called.
|
|
6
|
+
* This is useful for one-time operations that should not be repeated, even if the component re-renders.
|
|
8
7
|
*
|
|
9
8
|
* @param {() => void} callback - The callback function to be executed once.
|
|
10
9
|
* @param {DependencyList} deps - Dependencies array that will trigger a new one-time execution when changed.
|
|
@@ -13,7 +13,7 @@ type UseControlledStateProps<T> = ControlledState<T> & {
|
|
|
13
13
|
};
|
|
14
14
|
/**
|
|
15
15
|
* @description
|
|
16
|
-
* `useControlledState` is a hook that allows you to control both controlled and uncontrolled states.
|
|
16
|
+
* `useControlledState` is a React hook that allows you to control both controlled and uncontrolled states.
|
|
17
17
|
* If you pass the state to `value`, it will be a controlled state, and if you pass the state to `defaultValue`, it will be an uncontrolled state.
|
|
18
18
|
* If both `value` and `defaultValue` are passed, `value` will take precedence.
|
|
19
19
|
*
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
type UseCounterOptions = {
|
|
2
|
-
initialValue?: number;
|
|
3
2
|
min?: number;
|
|
4
3
|
max?: number;
|
|
5
4
|
step?: number;
|
|
@@ -16,8 +15,8 @@ type UseCounterReturn = {
|
|
|
16
15
|
* `useCounter` is a React hook that manages a numeric counter state with increment, decrement, and reset capabilities.
|
|
17
16
|
* Optionally, you can provide minimum and maximum values to constrain the counter's range.
|
|
18
17
|
*
|
|
18
|
+
* @param {number} [initialValue=0] - Initial value for the counter. Defaults to 0.
|
|
19
19
|
* @param {UseCounterOptions} options - The options for the counter.
|
|
20
|
-
* @param {number} [options.initialValue=0] - Initial value for the counter. Defaults to 0.
|
|
21
20
|
* @param {number} [options.min] - Minimum value the counter can reach. If not provided, there is no lower limit.
|
|
22
21
|
* @param {number} [options.max] - Maximum value the counter can reach. If not provided, there is no upper limit.
|
|
23
22
|
* @param {number} [options.step=1] - Value to increment or decrement by. Defaults to 1.
|
|
@@ -33,8 +32,7 @@ type UseCounterReturn = {
|
|
|
33
32
|
* import { useCounter } from 'react-simplikit';
|
|
34
33
|
*
|
|
35
34
|
* function ShoppingCart() {
|
|
36
|
-
* const { count, increment, decrement, reset } = useCounter({
|
|
37
|
-
* initialValue: 1,
|
|
35
|
+
* const { count, increment, decrement, reset } = useCounter(1, {
|
|
38
36
|
* min: 1,
|
|
39
37
|
* max: 10,
|
|
40
38
|
* });
|
|
@@ -49,6 +47,6 @@ type UseCounterReturn = {
|
|
|
49
47
|
* );
|
|
50
48
|
* }
|
|
51
49
|
*/
|
|
52
|
-
declare function useCounter(
|
|
50
|
+
declare function useCounter(initialValue?: number, { min, max, step }?: UseCounterOptions): UseCounterReturn;
|
|
53
51
|
|
|
54
52
|
export { useCounter };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/hooks/useCounter/useCounter.ts
|
|
2
2
|
import { useCallback, useState } from "react";
|
|
3
|
-
function useCounter(
|
|
3
|
+
function useCounter(initialValue = 0, { min, max, step = 1 } = {}) {
|
|
4
4
|
const validateValue = (value) => {
|
|
5
5
|
let validatedValue = value;
|
|
6
6
|
if (min !== void 0 && validatedValue < min) {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
type DebounceOptions = {
|
|
2
|
+
leading?: boolean;
|
|
3
|
+
trailing?: boolean;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* @description
|
|
7
|
+
* `useDebouncedCallback` is a React hook that returns a debounced version of the provided callback function.
|
|
8
|
+
* It helps optimize event handling by delaying function execution and grouping multiple calls into one.
|
|
9
|
+
*
|
|
10
|
+
* Note that if both 'leading' and 'trailing' are set, the function will be called at both the start and end of the delay period. However, it must be called at least twice within debounceMs interval for this to happen, since one debounced function call cannot trigger the function twice.
|
|
11
|
+
*
|
|
12
|
+
* @param {Object} options - The options object.
|
|
13
|
+
* @param {Function} options.onChange - The callback function to debounce.
|
|
14
|
+
* @param {number} options.timeThreshold - The number of milliseconds to delay the function execution.
|
|
15
|
+
* @param {boolean} [options.leading=false] - If `true`, the function is called at the start of the sequence.
|
|
16
|
+
* @param {boolean} [options.trailing=true] - If `true`, the function is called at the end of the sequence.
|
|
17
|
+
*
|
|
18
|
+
* @returns {Function} A debounced function that delays invoking the callback.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* function SearchInput() {
|
|
22
|
+
* const [query, setQuery] = useState('');
|
|
23
|
+
* const debouncedSetQuery = useDebouncedCallback({ onChange: setQuery, timeThreshold: 100 });
|
|
24
|
+
* return <input type="text" onChange={(e) => debouncedSetQuery(e.target.value)} />;
|
|
25
|
+
* }
|
|
26
|
+
*/
|
|
27
|
+
declare function useDebouncedCallback({ onChange, timeThreshold, leading, trailing, }: DebounceOptions & {
|
|
28
|
+
onChange: (newValue: boolean) => void;
|
|
29
|
+
timeThreshold: number;
|
|
30
|
+
}): (nextValue: boolean) => void;
|
|
31
|
+
|
|
32
|
+
export { useDebouncedCallback };
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// src/hooks/useDebouncedCallback/useDebouncedCallback.ts
|
|
2
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useMemo, useRef as useRef2 } from "react";
|
|
3
|
+
|
|
4
|
+
// src/hooks/useDebounce/debounce.ts
|
|
5
|
+
function debounce(func, debounceMs, { edges = ["leading", "trailing"] } = {}) {
|
|
6
|
+
let pendingThis = void 0;
|
|
7
|
+
let pendingArgs = null;
|
|
8
|
+
const leading = edges != null && edges.includes("leading");
|
|
9
|
+
const trailing = edges == null || edges.includes("trailing");
|
|
10
|
+
const invoke = () => {
|
|
11
|
+
if (pendingArgs !== null) {
|
|
12
|
+
func.apply(pendingThis, pendingArgs);
|
|
13
|
+
pendingThis = void 0;
|
|
14
|
+
pendingArgs = null;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const onTimerEnd = () => {
|
|
18
|
+
if (trailing) {
|
|
19
|
+
invoke();
|
|
20
|
+
}
|
|
21
|
+
cancel();
|
|
22
|
+
};
|
|
23
|
+
let timeoutId = null;
|
|
24
|
+
const schedule = () => {
|
|
25
|
+
if (timeoutId != null) {
|
|
26
|
+
clearTimeout(timeoutId);
|
|
27
|
+
}
|
|
28
|
+
timeoutId = setTimeout(() => {
|
|
29
|
+
timeoutId = null;
|
|
30
|
+
onTimerEnd();
|
|
31
|
+
}, debounceMs);
|
|
32
|
+
};
|
|
33
|
+
const cancelTimer = () => {
|
|
34
|
+
if (timeoutId !== null) {
|
|
35
|
+
clearTimeout(timeoutId);
|
|
36
|
+
timeoutId = null;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const cancel = () => {
|
|
40
|
+
cancelTimer();
|
|
41
|
+
pendingThis = void 0;
|
|
42
|
+
pendingArgs = null;
|
|
43
|
+
};
|
|
44
|
+
const debounced = function(...args) {
|
|
45
|
+
pendingThis = this;
|
|
46
|
+
pendingArgs = args;
|
|
47
|
+
const isFirstCall = timeoutId == null;
|
|
48
|
+
schedule();
|
|
49
|
+
if (leading && isFirstCall) {
|
|
50
|
+
invoke();
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
debounced.cancel = cancel;
|
|
54
|
+
return debounced;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/hooks/usePreservedCallback/usePreservedCallback.ts
|
|
58
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
59
|
+
function usePreservedCallback(callback) {
|
|
60
|
+
const callbackRef = useRef(callback);
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
callbackRef.current = callback;
|
|
63
|
+
}, [callback]);
|
|
64
|
+
return useCallback((...args) => {
|
|
65
|
+
return callbackRef.current(...args);
|
|
66
|
+
}, []);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/hooks/useDebouncedCallback/useDebouncedCallback.ts
|
|
70
|
+
function useDebouncedCallback({
|
|
71
|
+
onChange,
|
|
72
|
+
timeThreshold,
|
|
73
|
+
leading = false,
|
|
74
|
+
trailing = true
|
|
75
|
+
}) {
|
|
76
|
+
const handleChange = usePreservedCallback(onChange);
|
|
77
|
+
const ref = useRef2({ value: false, clearPreviousDebounce: () => {
|
|
78
|
+
} });
|
|
79
|
+
useEffect2(() => {
|
|
80
|
+
const current = ref.current;
|
|
81
|
+
return () => {
|
|
82
|
+
current.clearPreviousDebounce();
|
|
83
|
+
};
|
|
84
|
+
}, []);
|
|
85
|
+
const edges = useMemo(() => {
|
|
86
|
+
const _edges = [];
|
|
87
|
+
if (leading) {
|
|
88
|
+
_edges.push("leading");
|
|
89
|
+
}
|
|
90
|
+
if (trailing) {
|
|
91
|
+
_edges.push("trailing");
|
|
92
|
+
}
|
|
93
|
+
return _edges;
|
|
94
|
+
}, [leading, trailing]);
|
|
95
|
+
return useCallback2(
|
|
96
|
+
(nextValue) => {
|
|
97
|
+
if (nextValue === ref.current.value) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const debounced = debounce(
|
|
101
|
+
() => {
|
|
102
|
+
handleChange(nextValue);
|
|
103
|
+
ref.current.value = nextValue;
|
|
104
|
+
},
|
|
105
|
+
timeThreshold,
|
|
106
|
+
{ edges }
|
|
107
|
+
);
|
|
108
|
+
ref.current.clearPreviousDebounce();
|
|
109
|
+
debounced();
|
|
110
|
+
ref.current.clearPreviousDebounce = debounced.cancel;
|
|
111
|
+
},
|
|
112
|
+
[handleChange, timeThreshold, edges]
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
export {
|
|
116
|
+
useDebouncedCallback
|
|
117
|
+
};
|
|
@@ -11,10 +11,10 @@ type UseDoubleClickProps<E extends HTMLElement> = {
|
|
|
11
11
|
* It delays the single click callback execution for a specified time, and cancels it if a second click (i.e. a double click) occurs within that time.
|
|
12
12
|
*
|
|
13
13
|
* @template {HTMLElement} E - The specific type of HTMLElement to be used with this hook (e.g., HTMLButtonElement, HTMLDivElement).
|
|
14
|
-
* @param {Object}
|
|
15
|
-
* @param {number} [
|
|
16
|
-
* @param {(event: MouseEvent<E>) => void} [
|
|
17
|
-
* @param {(event: MouseEvent<E>) => void}
|
|
14
|
+
* @param {Object} props - Configuration options for click handling.
|
|
15
|
+
* @param {number} [props.delay=250] - The number of milliseconds to wait before triggering the single click callback. Defaults to 250ms.
|
|
16
|
+
* @param {(event: MouseEvent<E>) => void} [props.click] - The callback function to be executed on a single click.
|
|
17
|
+
* @param {(event: MouseEvent<E>) => void} props.doubleClick - The callback function to be executed on a double click. Required.
|
|
18
18
|
*
|
|
19
19
|
* @returns {(event: MouseEvent<E>) => void} A click handler function to attach to an element's `onClick` event.
|
|
20
20
|
*
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
declare class CustomGeoLocationError extends Error {
|
|
2
|
+
code: number;
|
|
3
|
+
constructor({ code, message }: {
|
|
4
|
+
message: string;
|
|
5
|
+
code: number;
|
|
6
|
+
});
|
|
7
|
+
}
|
|
8
|
+
type GeolocationData = {
|
|
9
|
+
latitude: number;
|
|
10
|
+
longitude: number;
|
|
11
|
+
accuracy: number;
|
|
12
|
+
altitude: number | null;
|
|
13
|
+
altitudeAccuracy: number | null;
|
|
14
|
+
heading: number | null;
|
|
15
|
+
speed: number | null;
|
|
16
|
+
timestamp: number;
|
|
17
|
+
};
|
|
18
|
+
declare const GeolocationMountBehavior: {
|
|
19
|
+
readonly GET: "get";
|
|
20
|
+
readonly WATCH: "watch";
|
|
21
|
+
};
|
|
22
|
+
type GeolocationMountBehaviorType = (typeof GeolocationMountBehavior)[keyof typeof GeolocationMountBehavior];
|
|
23
|
+
type GeolocationOptions = {
|
|
24
|
+
mountBehavior?: GeolocationMountBehaviorType;
|
|
25
|
+
} & PositionOptions;
|
|
26
|
+
/**
|
|
27
|
+
* @description
|
|
28
|
+
* `useGeolocation` is a React hook that retrieves and tracks the user's geographical location.
|
|
29
|
+
* It uses the browser's `Geolocation API` to support both one-time position retrieval and continuous location tracking.
|
|
30
|
+
*
|
|
31
|
+
* @param {GeolocationOptions} [options] - Geolocation options configuration
|
|
32
|
+
* @param {GeolocationMountBehaviorType} [options.mountBehavior] - How the hook behaves on mount:
|
|
33
|
+
* -- If not provided, no automatic location fetching occurs
|
|
34
|
+
* -- `get`: automatically fetches location once when component mounts
|
|
35
|
+
* -- `watch`: automatically starts tracking location changes when component mounts
|
|
36
|
+
* @param {boolean} [options.enableHighAccuracy=false] - If true, provides more accurate position information (increases battery consumption)
|
|
37
|
+
* @param {number} [options.maximumAge=0] - Maximum age in milliseconds of a cached position that is acceptable to return
|
|
38
|
+
* @param {number} [options.timeout=Infinity] - Maximum time (in milliseconds) allowed for the location request
|
|
39
|
+
*
|
|
40
|
+
* @returns {Object} Object containing location data and related functions
|
|
41
|
+
* - loading `boolean` - Whether location data is currently being fetched;
|
|
42
|
+
* - error `CustomGeoLocationError|null` - Error object if an error occurred, or null
|
|
43
|
+
* The hook uses standard Geolocation API error codes (`1-3`) and adds a custom code (`0`)
|
|
44
|
+
* : `0` - Geolocation is not supported by the environment
|
|
45
|
+
* : `1` - User denied permission to access geolocation
|
|
46
|
+
* : `2` - Position unavailable
|
|
47
|
+
* : `3` - Timeout - geolocation request took too long;
|
|
48
|
+
* - data `GeolocationData|null` - Location data object or null
|
|
49
|
+
* : latitude `number` - The latitude in decimal degrees
|
|
50
|
+
* : longitude `number` - The longitude in decimal degrees
|
|
51
|
+
* : accuracy `number` - The accuracy of position in meters
|
|
52
|
+
* : altitude `number|null` - The altitude in meters above the WGS84 ellipsoid
|
|
53
|
+
* : altitudeAccuracy `number|null` - The altitude accuracy in meters
|
|
54
|
+
* : heading `number|null` - The heading in degrees clockwise from true north
|
|
55
|
+
* : speed `number|null` - The speed in meters per second
|
|
56
|
+
* : timestamp `number` - The time when the position was retrieved;
|
|
57
|
+
* - getCurrentPosition `Function` - Function to get the current position once;
|
|
58
|
+
* - startTracking `Function` - Function to start tracking location changes;
|
|
59
|
+
* - stopTracking `Function` - Function to stop tracking location;
|
|
60
|
+
* - isTracking `boolean` - Whether location tracking is currently active;
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* // Basic usage
|
|
64
|
+
* const {
|
|
65
|
+
* loading,
|
|
66
|
+
* error,
|
|
67
|
+
* data,
|
|
68
|
+
* getCurrentPosition
|
|
69
|
+
* } = useGeolocation();
|
|
70
|
+
*
|
|
71
|
+
* // Automatically fetch location when component mounts
|
|
72
|
+
* const {
|
|
73
|
+
* loading,
|
|
74
|
+
* error,
|
|
75
|
+
* data
|
|
76
|
+
* } = useGeolocation({ mountBehavior: 'get' });
|
|
77
|
+
*
|
|
78
|
+
* // Location tracking
|
|
79
|
+
* const {
|
|
80
|
+
* loading,
|
|
81
|
+
* error,
|
|
82
|
+
* data,
|
|
83
|
+
* startTracking,
|
|
84
|
+
* stopTracking,
|
|
85
|
+
* isTracking
|
|
86
|
+
* } = useGeolocation();
|
|
87
|
+
*
|
|
88
|
+
* const handleStartTracking = () => {
|
|
89
|
+
* startTracking();
|
|
90
|
+
* };
|
|
91
|
+
*
|
|
92
|
+
* const handleStopTracking = () => {
|
|
93
|
+
* stopTracking();
|
|
94
|
+
* };
|
|
95
|
+
*/
|
|
96
|
+
declare function useGeolocation(options?: GeolocationOptions): {
|
|
97
|
+
getCurrentPosition: () => void;
|
|
98
|
+
startTracking: () => void;
|
|
99
|
+
stopTracking: () => void;
|
|
100
|
+
isTracking: boolean;
|
|
101
|
+
loading: boolean;
|
|
102
|
+
error: CustomGeoLocationError | null;
|
|
103
|
+
data: GeolocationData | null;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export { useGeolocation };
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// src/hooks/useGeolocation/useGeolocation.ts
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
+
var CustomGeoLocationError = class extends Error {
|
|
4
|
+
code;
|
|
5
|
+
constructor({ code, message }) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "CustomGeoLocationError";
|
|
8
|
+
this.code = code;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
var GeolocationMountBehavior = {
|
|
12
|
+
GET: "get",
|
|
13
|
+
WATCH: "watch"
|
|
14
|
+
};
|
|
15
|
+
function useGeolocation(options) {
|
|
16
|
+
const [state, setState] = useState({
|
|
17
|
+
loading: !!options?.mountBehavior,
|
|
18
|
+
error: null,
|
|
19
|
+
data: null
|
|
20
|
+
});
|
|
21
|
+
const [isTracking, setIsTracking] = useState(false);
|
|
22
|
+
const watchIdRef = useRef(null);
|
|
23
|
+
const checkGeolocationSupport = useCallback(() => {
|
|
24
|
+
if (typeof window === "undefined" || navigator.geolocation === void 0) {
|
|
25
|
+
setState((prev) => ({
|
|
26
|
+
...prev,
|
|
27
|
+
loading: false,
|
|
28
|
+
error: new CustomGeoLocationError({
|
|
29
|
+
code: 0,
|
|
30
|
+
message: "Geolocation is not supported by this environment."
|
|
31
|
+
})
|
|
32
|
+
}));
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
return true;
|
|
36
|
+
}, []);
|
|
37
|
+
const handleSuccess = useCallback((position) => {
|
|
38
|
+
const { coords } = position;
|
|
39
|
+
setState((prev) => ({
|
|
40
|
+
...prev,
|
|
41
|
+
loading: false,
|
|
42
|
+
error: null,
|
|
43
|
+
data: {
|
|
44
|
+
latitude: coords.latitude,
|
|
45
|
+
longitude: coords.longitude,
|
|
46
|
+
accuracy: coords.accuracy,
|
|
47
|
+
altitude: coords.altitude,
|
|
48
|
+
altitudeAccuracy: coords.altitudeAccuracy,
|
|
49
|
+
heading: coords.heading,
|
|
50
|
+
speed: coords.speed,
|
|
51
|
+
timestamp: position.timestamp
|
|
52
|
+
}
|
|
53
|
+
}));
|
|
54
|
+
}, []);
|
|
55
|
+
const handleError = useCallback((error) => {
|
|
56
|
+
const { code, message } = error;
|
|
57
|
+
setState((prev) => ({
|
|
58
|
+
...prev,
|
|
59
|
+
loading: false,
|
|
60
|
+
error: new CustomGeoLocationError({ code, message })
|
|
61
|
+
}));
|
|
62
|
+
}, []);
|
|
63
|
+
const getGeolocationOptions = useCallback(
|
|
64
|
+
() => ({
|
|
65
|
+
enableHighAccuracy: options?.enableHighAccuracy,
|
|
66
|
+
maximumAge: options?.maximumAge,
|
|
67
|
+
timeout: options?.timeout
|
|
68
|
+
}),
|
|
69
|
+
[options?.enableHighAccuracy, options?.maximumAge, options?.timeout]
|
|
70
|
+
);
|
|
71
|
+
const getCurrentPosition = useCallback(() => {
|
|
72
|
+
if (!checkGeolocationSupport()) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
setState((prev) => ({ ...prev, loading: true }));
|
|
76
|
+
navigator.geolocation.getCurrentPosition(handleSuccess, handleError, getGeolocationOptions());
|
|
77
|
+
}, [handleSuccess, handleError, getGeolocationOptions, checkGeolocationSupport]);
|
|
78
|
+
const startTracking = useCallback(() => {
|
|
79
|
+
if (!checkGeolocationSupport()) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (watchIdRef.current !== null) {
|
|
83
|
+
navigator.geolocation.clearWatch(watchIdRef.current);
|
|
84
|
+
}
|
|
85
|
+
setState((prev) => ({ ...prev, loading: true }));
|
|
86
|
+
watchIdRef.current = navigator.geolocation.watchPosition(
|
|
87
|
+
(position) => {
|
|
88
|
+
setIsTracking(true);
|
|
89
|
+
handleSuccess(position);
|
|
90
|
+
},
|
|
91
|
+
handleError,
|
|
92
|
+
getGeolocationOptions()
|
|
93
|
+
);
|
|
94
|
+
}, [handleSuccess, handleError, getGeolocationOptions, checkGeolocationSupport]);
|
|
95
|
+
const stopTracking = useCallback(() => {
|
|
96
|
+
if (watchIdRef.current === null) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
navigator.geolocation.clearWatch(watchIdRef.current);
|
|
100
|
+
watchIdRef.current = null;
|
|
101
|
+
setIsTracking(false);
|
|
102
|
+
}, []);
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
if (options?.mountBehavior === GeolocationMountBehavior.WATCH) {
|
|
105
|
+
startTracking();
|
|
106
|
+
} else if (options?.mountBehavior === GeolocationMountBehavior.GET) {
|
|
107
|
+
getCurrentPosition();
|
|
108
|
+
}
|
|
109
|
+
return () => {
|
|
110
|
+
if (watchIdRef.current !== null) {
|
|
111
|
+
navigator.geolocation.clearWatch(watchIdRef.current);
|
|
112
|
+
watchIdRef.current = null;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}, [options?.mountBehavior, getCurrentPosition, startTracking]);
|
|
116
|
+
return {
|
|
117
|
+
...state,
|
|
118
|
+
getCurrentPosition,
|
|
119
|
+
startTracking,
|
|
120
|
+
stopTracking,
|
|
121
|
+
isTracking
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
export {
|
|
125
|
+
useGeolocation
|
|
126
|
+
};
|
|
@@ -7,8 +7,8 @@ type UseImpressionRefOptions = Partial<{
|
|
|
7
7
|
}>;
|
|
8
8
|
/**
|
|
9
9
|
* @description
|
|
10
|
-
* `useImpressionRef` is a
|
|
11
|
-
*
|
|
10
|
+
* `useImpressionRef` is a React hook that measures the time a specific DOM element is visible on the screen and executes callbacks when the element enters or exits the viewport.
|
|
11
|
+
* It uses `IntersectionObserver` and the `Visibility API` to track the element's visibility.
|
|
12
12
|
*
|
|
13
13
|
* @param {UseImpressionRefOptions} options - Options for tracking the element's visibility.
|
|
14
14
|
* @param {() => void} [options.onImpressionStart] - Callback function executed when the element enters the view
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @description
|
|
3
|
-
* `useIntersectionObserver` is a
|
|
4
|
-
*
|
|
3
|
+
* `useIntersectionObserver` is a React hook that detects whether a specific DOM element is visible on the screen.
|
|
4
|
+
* It uses the `IntersectionObserver` API to execute a callback when the element enters or exits the viewport.
|
|
5
5
|
*
|
|
6
6
|
* @param {(entry: IntersectionObserverEntry) => void} callback - A callback function that is executed when the visibility of the element changes.
|
|
7
7
|
* You can check `entry.isIntersecting` to determine if the element is in view.
|
|
@@ -2,11 +2,11 @@ import { useEffect } from 'react';
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @description
|
|
5
|
-
*
|
|
5
|
+
* `useIsomorphicLayoutEffect` is a React hook that provides the behavior of `useLayoutEffect` without triggering warnings during server-side rendering.
|
|
6
|
+
* During SSR, there is no DOM to synchronously measure or mutate, so React warns about using `useLayoutEffect`.
|
|
6
7
|
*
|
|
7
|
-
* This hook
|
|
8
|
+
* This hook runs synchronously after DOM updates but before paint, making it ideal for:
|
|
8
9
|
*
|
|
9
|
-
* It runs synchronously after DOM updates but before paint, making it ideal for:
|
|
10
10
|
* - Measuring DOM elements after render
|
|
11
11
|
* - Applying DOM changes before paint
|
|
12
12
|
* - Preventing UI flashes or layout shifts
|