@journ/lift 0.1.1 → 0.2.1
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 +39 -9
- package/dist/env-C0PNnbwt.js +4 -0
- package/dist/logger-Dse1660l.js +33 -0
- package/dist/prefers-reduced-motion.d.ts +3 -2
- package/dist/prefers-reduced-motion.js +1 -3
- package/dist/required-context.d.ts +3 -2
- package/dist/required-context.js +2 -2
- package/dist/try-catch-D4d3N9_W.js +29 -0
- package/dist/types-p0KNRcj3.d.ts +4 -0
- package/dist/use-battery.d.ts +12 -0
- package/dist/use-battery.js +64 -0
- package/dist/use-browser.d.ts +6 -0
- package/dist/use-browser.js +51 -0
- package/dist/use-callback-ref.d.ts +5 -0
- package/dist/use-callback-ref.js +11 -0
- package/dist/use-controllable-state.d.ts +3 -2
- package/dist/use-controllable-state.js +6 -4
- package/dist/use-debounce-state.d.ts +9 -0
- package/dist/use-debounce-state.js +28 -0
- package/dist/use-debounced-callback.d.ts +16 -0
- package/dist/use-debounced-callback.js +68 -0
- package/dist/use-debounced-value.d.ts +3 -2
- package/dist/use-debounced-value.js +3 -3
- package/dist/use-did-update.d.ts +5 -0
- package/dist/use-did-update.js +24 -0
- package/dist/use-disclosure.d.ts +15 -0
- package/dist/use-disclosure.js +16 -0
- package/dist/use-draft.d.ts +7 -6
- package/dist/use-draft.js +2 -2
- package/dist/use-haptic.d.ts +8 -0
- package/dist/use-haptic.js +84 -0
- package/dist/use-headroom.d.ts +18 -0
- package/dist/use-headroom.js +65 -0
- package/dist/use-idle-queue.d.ts +28 -0
- package/dist/use-idle-queue.js +221 -0
- package/dist/use-intersection-observer.d.ts +17 -0
- package/dist/use-intersection-observer.js +50 -0
- package/dist/use-isomorphic-effect.d.ts +5 -0
- package/dist/use-isomorphic-effect.js +5 -0
- package/dist/use-keybindings.d.ts +14 -0
- package/dist/use-keybindings.js +76 -0
- package/dist/use-local-storage.d.ts +13 -0
- package/dist/use-local-storage.js +76 -0
- package/dist/use-logger.d.ts +4 -0
- package/dist/use-logger.js +17 -0
- package/dist/use-long-press.d.ts +12 -0
- package/dist/use-long-press.js +43 -0
- package/dist/use-map.d.ts +4 -0
- package/dist/use-map.js +30 -0
- package/dist/use-measure.d.ts +8 -0
- package/dist/use-measure.js +27 -0
- package/dist/use-media-query.d.ts +14 -0
- package/dist/use-media-query.js +24 -0
- package/dist/use-mouse.d.ts +18 -0
- package/dist/use-mouse.js +40 -0
- package/dist/use-network.d.ts +17 -0
- package/dist/use-network.js +38 -0
- package/dist/use-page-leave.d.ts +4 -0
- package/dist/use-page-leave.js +11 -0
- package/dist/use-pagination.d.ts +19 -0
- package/dist/use-pagination.js +55 -0
- package/dist/use-reduced-motion.d.ts +4 -0
- package/dist/use-reduced-motion.js +5 -0
- package/dist/use-scroll-direction.d.ts +5 -0
- package/dist/use-scroll-direction.js +33 -0
- package/dist/use-uncontrolled.d.ts +11 -0
- package/dist/use-uncontrolled.js +21 -0
- package/dist/use-validated-state.d.ts +10 -0
- package/dist/use-validated-state.js +22 -0
- package/dist/use-window-event.d.ts +5 -0
- package/dist/use-window-event.js +11 -0
- package/dist/use-window-scroll.d.ts +10 -0
- package/dist/use-window-scroll.js +33 -0
- package/package.json +149 -9
- package/dist/index.d.ts +0 -6
- package/dist/index.js +0 -6
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { n as tryCatchSync } from "./try-catch-D4d3N9_W.js";
|
|
2
|
+
import { t as logger } from "./logger-Dse1660l.js";
|
|
3
|
+
import { useCallback, useMemo, useSyncExternalStore } from "react";
|
|
4
|
+
//#region src/use-local-storage.ts
|
|
5
|
+
const serializeJSON = (value, hookName) => {
|
|
6
|
+
const result = tryCatchSync(() => JSON.stringify(value));
|
|
7
|
+
if (!result.success) throw new Error(`${hookName}: Failed to serialize the value`);
|
|
8
|
+
return result.data;
|
|
9
|
+
};
|
|
10
|
+
const deserializeJSON = (value) => {
|
|
11
|
+
const result = tryCatchSync(() => JSON.parse(value));
|
|
12
|
+
return result.success ? result.data : value;
|
|
13
|
+
};
|
|
14
|
+
const createStorageHandler = (type, hookName) => {
|
|
15
|
+
const attempt = (fn, failure, fallback) => {
|
|
16
|
+
const result = tryCatchSync(fn);
|
|
17
|
+
if (result.success) return result.data;
|
|
18
|
+
logger.warn(`${hookName}: ${failure}`);
|
|
19
|
+
return fallback;
|
|
20
|
+
};
|
|
21
|
+
const getItem = (key) => attempt(() => window[type].getItem(key), "Failed to get value from storage", null);
|
|
22
|
+
const setItem = (key, value) => attempt(() => window[type].setItem(key, value), "Failed to set value to storage", void 0);
|
|
23
|
+
const removeItem = (key) => attempt(() => window[type].removeItem(key), "Failed to remove value from storage", void 0);
|
|
24
|
+
return {
|
|
25
|
+
getItem,
|
|
26
|
+
setItem,
|
|
27
|
+
removeItem
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
const getServerSnapshot = () => null;
|
|
31
|
+
const createUseStorage = (type, hookName) => {
|
|
32
|
+
const eventName = type === "localStorage" ? "local-storage" : "session-storage";
|
|
33
|
+
const { getItem, setItem, removeItem } = createStorageHandler(type, hookName);
|
|
34
|
+
const subscribe = (callback) => {
|
|
35
|
+
window.addEventListener("storage", callback);
|
|
36
|
+
window.addEventListener(eventName, callback);
|
|
37
|
+
return () => {
|
|
38
|
+
window.removeEventListener("storage", callback);
|
|
39
|
+
window.removeEventListener(eventName, callback);
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
const defaultSerialize = (value) => serializeJSON(value, hookName);
|
|
43
|
+
const notify = () => window.dispatchEvent(new Event(eventName));
|
|
44
|
+
return ({ key, defaultValue, deserialize = deserializeJSON, serialize = defaultSerialize }) => {
|
|
45
|
+
const getSnapshot = useCallback(() => getItem(key), [key]);
|
|
46
|
+
const raw = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
47
|
+
const stored = useMemo(() => raw === null ? void 0 : deserialize(raw), [raw, deserialize]);
|
|
48
|
+
const setStorageValue = useCallback((val) => {
|
|
49
|
+
let next;
|
|
50
|
+
if (val instanceof Function) {
|
|
51
|
+
const current = getItem(key);
|
|
52
|
+
next = val(current === null ? defaultValue : deserialize(current));
|
|
53
|
+
} else next = val;
|
|
54
|
+
setItem(key, serialize(next));
|
|
55
|
+
notify();
|
|
56
|
+
}, [
|
|
57
|
+
key,
|
|
58
|
+
defaultValue,
|
|
59
|
+
deserialize,
|
|
60
|
+
serialize
|
|
61
|
+
]);
|
|
62
|
+
const removeStorageValue = useCallback(() => {
|
|
63
|
+
removeItem(key);
|
|
64
|
+
notify();
|
|
65
|
+
}, [key]);
|
|
66
|
+
return [
|
|
67
|
+
raw === null ? defaultValue : stored,
|
|
68
|
+
setStorageValue,
|
|
69
|
+
removeStorageValue
|
|
70
|
+
];
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
const useLocalStorage = createUseStorage("localStorage", "use-local-storage");
|
|
74
|
+
const useSessionStorage = createUseStorage("sessionStorage", "use-session-storage");
|
|
75
|
+
//#endregion
|
|
76
|
+
export { useLocalStorage, useSessionStorage };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useDidUpdate } from "./use-did-update.js";
|
|
2
|
+
import { t as logger } from "./logger-Dse1660l.js";
|
|
3
|
+
import { useEffect, useEffectEvent } from "react";
|
|
4
|
+
//#region src/use-logger.ts
|
|
5
|
+
const useLogger = (componentName, props) => {
|
|
6
|
+
const logMount = useEffectEvent(() => logger.log(`${componentName} mounted`, ...props));
|
|
7
|
+
const logUnmount = useEffectEvent(() => logger.log(`${componentName} unmounted`));
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
logMount();
|
|
10
|
+
return () => logUnmount();
|
|
11
|
+
}, []);
|
|
12
|
+
useDidUpdate(() => {
|
|
13
|
+
logger.log(`${componentName} updated`, ...props);
|
|
14
|
+
}, props);
|
|
15
|
+
};
|
|
16
|
+
//#endregion
|
|
17
|
+
export { useLogger };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { PointerEvent, SyntheticEvent } from "react";
|
|
2
|
+
//#region src/use-long-press.d.ts
|
|
3
|
+
type LongPressHandlers = {
|
|
4
|
+
readonly onPointerDown: (event: PointerEvent) => void;
|
|
5
|
+
readonly onPointerMove: (event: PointerEvent) => void;
|
|
6
|
+
readonly onPointerUp: () => void;
|
|
7
|
+
readonly onPointerCancel: () => void;
|
|
8
|
+
readonly onContextMenu: (event: SyntheticEvent) => void;
|
|
9
|
+
};
|
|
10
|
+
declare const useLongPress: (onLongPress: () => void, disabled?: boolean) => LongPressHandlers;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { useLongPress };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
2
|
+
//#region src/use-long-press.ts
|
|
3
|
+
const HOLD_MS = 500;
|
|
4
|
+
const SLOP_PX = 10;
|
|
5
|
+
const useLongPress = (onLongPress, disabled = false) => {
|
|
6
|
+
const timer = useRef(null);
|
|
7
|
+
const origin = useRef(null);
|
|
8
|
+
const cancel = useCallback(() => {
|
|
9
|
+
if (timer.current !== null) {
|
|
10
|
+
clearTimeout(timer.current);
|
|
11
|
+
timer.current = null;
|
|
12
|
+
}
|
|
13
|
+
origin.current = null;
|
|
14
|
+
}, []);
|
|
15
|
+
useEffect(() => cancel, [cancel]);
|
|
16
|
+
return {
|
|
17
|
+
onPointerDown: useCallback((event) => {
|
|
18
|
+
if (disabled) return;
|
|
19
|
+
cancel();
|
|
20
|
+
origin.current = {
|
|
21
|
+
x: event.clientX,
|
|
22
|
+
y: event.clientY
|
|
23
|
+
};
|
|
24
|
+
timer.current = setTimeout(() => {
|
|
25
|
+
timer.current = null;
|
|
26
|
+
onLongPress();
|
|
27
|
+
}, HOLD_MS);
|
|
28
|
+
}, [
|
|
29
|
+
disabled,
|
|
30
|
+
onLongPress,
|
|
31
|
+
cancel
|
|
32
|
+
]),
|
|
33
|
+
onPointerMove: useCallback((event) => {
|
|
34
|
+
if (!origin.current) return;
|
|
35
|
+
if (Math.abs(event.clientX - origin.current.x) > SLOP_PX || Math.abs(event.clientY - origin.current.y) > SLOP_PX) cancel();
|
|
36
|
+
}, [cancel]),
|
|
37
|
+
onPointerUp: cancel,
|
|
38
|
+
onPointerCancel: cancel,
|
|
39
|
+
onContextMenu: useCallback((event) => event.preventDefault(), [])
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
//#endregion
|
|
43
|
+
export { useLongPress };
|
package/dist/use-map.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useMemo, useRef, useState } from "react";
|
|
2
|
+
//#region src/use-map.ts
|
|
3
|
+
const useMap = (initialState) => {
|
|
4
|
+
const [map, setMap] = useState(() => new Map(initialState));
|
|
5
|
+
const latest = useRef(map);
|
|
6
|
+
return useMemo(() => {
|
|
7
|
+
const view = new Map(map);
|
|
8
|
+
const commit = (next) => {
|
|
9
|
+
latest.current = next;
|
|
10
|
+
setMap(next);
|
|
11
|
+
};
|
|
12
|
+
view.set = (key, value) => {
|
|
13
|
+
commit(new Map(latest.current).set(key, value));
|
|
14
|
+
return view;
|
|
15
|
+
};
|
|
16
|
+
view.delete = (key) => {
|
|
17
|
+
const existed = latest.current.has(key);
|
|
18
|
+
if (existed) {
|
|
19
|
+
const next = new Map(latest.current);
|
|
20
|
+
next.delete(key);
|
|
21
|
+
commit(next);
|
|
22
|
+
}
|
|
23
|
+
return existed;
|
|
24
|
+
};
|
|
25
|
+
view.clear = () => commit(/* @__PURE__ */ new Map());
|
|
26
|
+
return view;
|
|
27
|
+
}, [map]);
|
|
28
|
+
};
|
|
29
|
+
//#endregion
|
|
30
|
+
export { useMap };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useCallback, useRef, useState } from "react";
|
|
2
|
+
//#region src/use-measure.ts
|
|
3
|
+
const EMPTY = {
|
|
4
|
+
width: null,
|
|
5
|
+
height: null
|
|
6
|
+
};
|
|
7
|
+
const useMeasure = () => {
|
|
8
|
+
const [dimensions, setDimensions] = useState(EMPTY);
|
|
9
|
+
const observerRef = useRef(null);
|
|
10
|
+
return [useCallback((node) => {
|
|
11
|
+
observerRef.current?.disconnect();
|
|
12
|
+
observerRef.current = null;
|
|
13
|
+
if (node?.nodeType !== Node.ELEMENT_NODE) return;
|
|
14
|
+
const observer = new ResizeObserver(([entry]) => {
|
|
15
|
+
if (!entry?.borderBoxSize) return;
|
|
16
|
+
const box = entry.borderBoxSize[0];
|
|
17
|
+
setDimensions(box ? {
|
|
18
|
+
width: box.inlineSize,
|
|
19
|
+
height: box.blockSize
|
|
20
|
+
} : EMPTY);
|
|
21
|
+
});
|
|
22
|
+
observer.observe(node);
|
|
23
|
+
observerRef.current = observer;
|
|
24
|
+
}, []), dimensions];
|
|
25
|
+
};
|
|
26
|
+
//#endregion
|
|
27
|
+
export { useMeasure };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/use-media-query.d.ts
|
|
2
|
+
declare const breakpoints: {
|
|
3
|
+
readonly sm: "(min-width: 40rem)";
|
|
4
|
+
readonly md: "(min-width: 48rem)";
|
|
5
|
+
readonly "max-md": "(width < 48rem)";
|
|
6
|
+
readonly lg: "(min-width: 64rem)";
|
|
7
|
+
readonly "max-lg": "(width < 64rem)";
|
|
8
|
+
};
|
|
9
|
+
type Breakpoint = keyof typeof breakpoints;
|
|
10
|
+
type BreakpointValue = (typeof breakpoints)[Breakpoint];
|
|
11
|
+
declare const useMediaQuery: (query: BreakpointValue | string) => boolean;
|
|
12
|
+
declare const useBreakpoint: (breakpoint: Breakpoint) => boolean;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { type Breakpoint, breakpoints, useBreakpoint, useMediaQuery };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useCallback, useSyncExternalStore } from "react";
|
|
2
|
+
//#region src/use-media-query.ts
|
|
3
|
+
const breakpoints = {
|
|
4
|
+
sm: "(min-width: 40rem)",
|
|
5
|
+
md: "(min-width: 48rem)",
|
|
6
|
+
"max-md": "(width < 48rem)",
|
|
7
|
+
lg: "(min-width: 64rem)",
|
|
8
|
+
"max-lg": "(width < 64rem)"
|
|
9
|
+
};
|
|
10
|
+
const getServerSnapshot = () => {
|
|
11
|
+
throw new Error("useMediaQuery is a client-only hook");
|
|
12
|
+
};
|
|
13
|
+
const useMediaQuery = (query) => {
|
|
14
|
+
const subscribe = useCallback((callback) => {
|
|
15
|
+
const mediaQueryList = window.matchMedia(query);
|
|
16
|
+
mediaQueryList.addEventListener("change", callback);
|
|
17
|
+
return () => mediaQueryList.removeEventListener("change", callback);
|
|
18
|
+
}, [query]);
|
|
19
|
+
const getSnapshot = useCallback(() => window.matchMedia(query).matches, [query]);
|
|
20
|
+
return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
21
|
+
};
|
|
22
|
+
const useBreakpoint = (breakpoint) => useMediaQuery(breakpoints[breakpoint]);
|
|
23
|
+
//#endregion
|
|
24
|
+
export { breakpoints, useBreakpoint, useMediaQuery };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { RefCallback } from "react";
|
|
2
|
+
//#region src/use-mouse.d.ts
|
|
3
|
+
type UseMouseOptions = {
|
|
4
|
+
resetOnExit?: boolean;
|
|
5
|
+
};
|
|
6
|
+
type UseMouseReturnValue<T extends HTMLElement = HTMLElement> = {
|
|
7
|
+
ref: RefCallback<T>;
|
|
8
|
+
x: number;
|
|
9
|
+
y: number;
|
|
10
|
+
};
|
|
11
|
+
type UseMousePositionReturnValue = {
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
};
|
|
15
|
+
declare const useMouse: <T extends HTMLElement = HTMLElement>({ resetOnExit }?: UseMouseOptions) => UseMouseReturnValue<T>;
|
|
16
|
+
declare const useMousePosition: () => UseMousePositionReturnValue;
|
|
17
|
+
//#endregion
|
|
18
|
+
export { type UseMouseOptions, type UseMousePositionReturnValue, type UseMouseReturnValue, useMouse, useMousePosition };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { useWindowEvent } from "./use-window-event.js";
|
|
2
|
+
import { useCallback, useState } from "react";
|
|
3
|
+
//#region src/use-mouse.ts
|
|
4
|
+
const ORIGIN = {
|
|
5
|
+
x: 0,
|
|
6
|
+
y: 0
|
|
7
|
+
};
|
|
8
|
+
const useMouse = ({ resetOnExit = false } = {}) => {
|
|
9
|
+
const [position, setPosition] = useState(ORIGIN);
|
|
10
|
+
return {
|
|
11
|
+
ref: useCallback((node) => {
|
|
12
|
+
if (!node) return;
|
|
13
|
+
const trackPosition = (event) => {
|
|
14
|
+
const rect = node.getBoundingClientRect();
|
|
15
|
+
setPosition({
|
|
16
|
+
x: Math.max(0, Math.round(event.clientX - rect.left)),
|
|
17
|
+
y: Math.max(0, Math.round(event.clientY - rect.top))
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
const resetPosition = () => setPosition(ORIGIN);
|
|
21
|
+
node.addEventListener("mousemove", trackPosition);
|
|
22
|
+
if (resetOnExit) node.addEventListener("mouseleave", resetPosition);
|
|
23
|
+
return () => {
|
|
24
|
+
node.removeEventListener("mousemove", trackPosition);
|
|
25
|
+
node.removeEventListener("mouseleave", resetPosition);
|
|
26
|
+
};
|
|
27
|
+
}, [resetOnExit]),
|
|
28
|
+
...position
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
const useMousePosition = () => {
|
|
32
|
+
const [position, setPosition] = useState(ORIGIN);
|
|
33
|
+
useWindowEvent("mousemove", (event) => setPosition({
|
|
34
|
+
x: event.clientX,
|
|
35
|
+
y: event.clientY
|
|
36
|
+
}));
|
|
37
|
+
return position;
|
|
38
|
+
};
|
|
39
|
+
//#endregion
|
|
40
|
+
export { useMouse, useMousePosition };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/use-network.d.ts
|
|
2
|
+
type EffectiveType = "slow-2g" | "2g" | "3g" | "4g";
|
|
3
|
+
type ConnectionType = "bluetooth" | "cellular" | "ethernet" | "wifi" | "wimax" | "none" | "other" | "unknown";
|
|
4
|
+
type NetworkStatus = {
|
|
5
|
+
downlink?: number;
|
|
6
|
+
downlinkMax?: number;
|
|
7
|
+
effectiveType?: EffectiveType;
|
|
8
|
+
rtt?: number;
|
|
9
|
+
saveData?: boolean;
|
|
10
|
+
type?: ConnectionType;
|
|
11
|
+
};
|
|
12
|
+
type NetworkState = NetworkStatus & {
|
|
13
|
+
online: boolean;
|
|
14
|
+
};
|
|
15
|
+
declare const useNetwork: () => NetworkState;
|
|
16
|
+
//#endregion
|
|
17
|
+
export { useNetwork };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { useMemo, useSyncExternalStore } from "react";
|
|
2
|
+
//#region src/use-network.ts
|
|
3
|
+
const getConnection = () => {
|
|
4
|
+
if (typeof navigator === "undefined") return;
|
|
5
|
+
const nav = navigator;
|
|
6
|
+
return nav.connection ?? nav.mozConnection ?? nav.webkitConnection;
|
|
7
|
+
};
|
|
8
|
+
const readNetworkState = () => {
|
|
9
|
+
const connection = getConnection();
|
|
10
|
+
return {
|
|
11
|
+
online: typeof navigator !== "undefined" ? navigator.onLine : true,
|
|
12
|
+
downlink: connection?.downlink,
|
|
13
|
+
downlinkMax: connection?.downlinkMax,
|
|
14
|
+
effectiveType: connection?.effectiveType,
|
|
15
|
+
rtt: connection?.rtt,
|
|
16
|
+
saveData: connection?.saveData,
|
|
17
|
+
type: connection?.type
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
const subscribe = (callback) => {
|
|
21
|
+
const connection = getConnection();
|
|
22
|
+
window.addEventListener("online", callback);
|
|
23
|
+
window.addEventListener("offline", callback);
|
|
24
|
+
connection?.addEventListener("change", callback);
|
|
25
|
+
return () => {
|
|
26
|
+
window.removeEventListener("online", callback);
|
|
27
|
+
window.removeEventListener("offline", callback);
|
|
28
|
+
connection?.removeEventListener("change", callback);
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
const getSnapshot = () => JSON.stringify(readNetworkState());
|
|
32
|
+
const getServerSnapshot = () => JSON.stringify({ online: true });
|
|
33
|
+
const useNetwork = () => {
|
|
34
|
+
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
35
|
+
return useMemo(() => JSON.parse(snapshot), [snapshot]);
|
|
36
|
+
};
|
|
37
|
+
//#endregion
|
|
38
|
+
export { useNetwork };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { useEffect, useEffectEvent } from "react";
|
|
2
|
+
//#region src/use-page-leave.ts
|
|
3
|
+
const usePageLeave = (onPageLeave) => {
|
|
4
|
+
const onPageLeaveEvent = useEffectEvent(onPageLeave);
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
document.documentElement.addEventListener("mouseleave", onPageLeaveEvent);
|
|
7
|
+
return () => document.documentElement.removeEventListener("mouseleave", onPageLeaveEvent);
|
|
8
|
+
}, []);
|
|
9
|
+
};
|
|
10
|
+
//#endregion
|
|
11
|
+
export { usePageLeave };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/use-pagination.d.ts
|
|
2
|
+
declare const DOTS: "...";
|
|
3
|
+
type PaginationItem = number | typeof DOTS;
|
|
4
|
+
type UsePaginationOptions = {
|
|
5
|
+
initialPage?: number;
|
|
6
|
+
page?: number;
|
|
7
|
+
total: number;
|
|
8
|
+
siblings?: number;
|
|
9
|
+
boundaries?: number;
|
|
10
|
+
onChange?: (page: number) => void;
|
|
11
|
+
};
|
|
12
|
+
type UsePaginationReturnValue = {
|
|
13
|
+
range: PaginationItem[];
|
|
14
|
+
active: number;
|
|
15
|
+
setPage: (page: number) => void;
|
|
16
|
+
};
|
|
17
|
+
declare const usePagination: ({ total, siblings, boundaries, page, initialPage, onChange }: UsePaginationOptions) => UsePaginationReturnValue;
|
|
18
|
+
//#endregion
|
|
19
|
+
export { DOTS, usePagination };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { useUncontrolled } from "./use-uncontrolled.js";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
//#region src/use-pagination.ts
|
|
4
|
+
const DOTS = "...";
|
|
5
|
+
const range = (start, end) => Array.from({ length: end - start + 1 }, (_, index) => index + start);
|
|
6
|
+
const usePagination = ({ total, siblings = 1, boundaries = 1, page, initialPage = 1, onChange }) => {
|
|
7
|
+
const totalPages = Math.max(Math.trunc(total), 0);
|
|
8
|
+
const [activePage, setPage] = useUncontrolled({
|
|
9
|
+
value: page,
|
|
10
|
+
defaultValue: initialPage,
|
|
11
|
+
finalValue: initialPage,
|
|
12
|
+
onChange
|
|
13
|
+
});
|
|
14
|
+
return {
|
|
15
|
+
range: useMemo(() => {
|
|
16
|
+
if (siblings * 2 + 3 + boundaries * 2 >= totalPages) return range(1, totalPages);
|
|
17
|
+
const leftSiblingIndex = Math.max(activePage - siblings, boundaries);
|
|
18
|
+
const rightSiblingIndex = Math.min(activePage + siblings, totalPages - boundaries);
|
|
19
|
+
const shouldShowLeftDots = leftSiblingIndex > boundaries + 2;
|
|
20
|
+
const shouldShowRightDots = rightSiblingIndex < totalPages - (boundaries + 1);
|
|
21
|
+
if (!shouldShowLeftDots && shouldShowRightDots) {
|
|
22
|
+
const leftItemCount = siblings * 2 + boundaries + 2;
|
|
23
|
+
return [
|
|
24
|
+
...range(1, leftItemCount),
|
|
25
|
+
"...",
|
|
26
|
+
...range(totalPages - (boundaries - 1), totalPages)
|
|
27
|
+
];
|
|
28
|
+
}
|
|
29
|
+
if (shouldShowLeftDots && !shouldShowRightDots) {
|
|
30
|
+
const rightItemCount = boundaries + 1 + 2 * siblings;
|
|
31
|
+
return [
|
|
32
|
+
...range(1, boundaries),
|
|
33
|
+
"...",
|
|
34
|
+
...range(totalPages - rightItemCount, totalPages)
|
|
35
|
+
];
|
|
36
|
+
}
|
|
37
|
+
return [
|
|
38
|
+
...range(1, boundaries),
|
|
39
|
+
"...",
|
|
40
|
+
...range(leftSiblingIndex, rightSiblingIndex),
|
|
41
|
+
"...",
|
|
42
|
+
...range(totalPages - boundaries + 1, totalPages)
|
|
43
|
+
];
|
|
44
|
+
}, [
|
|
45
|
+
totalPages,
|
|
46
|
+
siblings,
|
|
47
|
+
activePage,
|
|
48
|
+
boundaries
|
|
49
|
+
]),
|
|
50
|
+
active: activePage,
|
|
51
|
+
setPage
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
//#endregion
|
|
55
|
+
export { DOTS, usePagination };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { useEffect, useEffectEvent, useRef, useState } from "react";
|
|
2
|
+
//#region src/use-scroll-direction.ts
|
|
3
|
+
const useScrollDirection = () => {
|
|
4
|
+
const lastScrollTopRef = useRef(0);
|
|
5
|
+
const [scrollDirection, setScrollDirection] = useState("unknown");
|
|
6
|
+
const isResizingRef = useRef(false);
|
|
7
|
+
const resizeTimerRef = useRef(void 0);
|
|
8
|
+
const handleScroll = useEffectEvent(() => {
|
|
9
|
+
if (isResizingRef.current) return;
|
|
10
|
+
const currentScrollTop = window.scrollY || document.documentElement.scrollTop;
|
|
11
|
+
setScrollDirection(currentScrollTop < lastScrollTopRef.current ? "up" : "down");
|
|
12
|
+
lastScrollTopRef.current = currentScrollTop;
|
|
13
|
+
});
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const handleResize = () => {
|
|
16
|
+
isResizingRef.current = true;
|
|
17
|
+
window.clearTimeout(resizeTimerRef.current);
|
|
18
|
+
resizeTimerRef.current = window.setTimeout(() => {
|
|
19
|
+
isResizingRef.current = false;
|
|
20
|
+
}, 300);
|
|
21
|
+
};
|
|
22
|
+
window.addEventListener("scroll", handleScroll);
|
|
23
|
+
window.addEventListener("resize", handleResize);
|
|
24
|
+
return () => {
|
|
25
|
+
window.removeEventListener("scroll", handleScroll);
|
|
26
|
+
window.removeEventListener("resize", handleResize);
|
|
27
|
+
clearTimeout(resizeTimerRef.current);
|
|
28
|
+
};
|
|
29
|
+
}, []);
|
|
30
|
+
return scrollDirection;
|
|
31
|
+
};
|
|
32
|
+
//#endregion
|
|
33
|
+
export { useScrollDirection };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//#region src/use-uncontrolled.d.ts
|
|
2
|
+
type UseUncontrolledOptions<T> = {
|
|
3
|
+
value?: T;
|
|
4
|
+
defaultValue?: T;
|
|
5
|
+
finalValue?: T;
|
|
6
|
+
onChange?: (value: T, ...payload: unknown[]) => void;
|
|
7
|
+
};
|
|
8
|
+
type UseUncontrolledReturnValue<T> = [T, (value: T, ...payload: unknown[]) => void, boolean];
|
|
9
|
+
declare const useUncontrolled: <T>({ value, defaultValue, finalValue, onChange }: UseUncontrolledOptions<T>) => UseUncontrolledReturnValue<T>;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { type UseUncontrolledOptions, type UseUncontrolledReturnValue, useUncontrolled };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
//#region src/use-uncontrolled.ts
|
|
3
|
+
const useUncontrolled = ({ value, defaultValue, finalValue, onChange = () => {} }) => {
|
|
4
|
+
const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue !== void 0 ? defaultValue : finalValue);
|
|
5
|
+
const handleUncontrolledChange = (val, ...payload) => {
|
|
6
|
+
setUncontrolledValue(val);
|
|
7
|
+
onChange(val, ...payload);
|
|
8
|
+
};
|
|
9
|
+
if (value !== void 0) return [
|
|
10
|
+
value,
|
|
11
|
+
onChange,
|
|
12
|
+
true
|
|
13
|
+
];
|
|
14
|
+
return [
|
|
15
|
+
uncontrolledValue,
|
|
16
|
+
handleUncontrolledChange,
|
|
17
|
+
false
|
|
18
|
+
];
|
|
19
|
+
};
|
|
20
|
+
//#endregion
|
|
21
|
+
export { useUncontrolled };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/use-validated-state.d.ts
|
|
2
|
+
type UseValidatedStateValue<T> = {
|
|
3
|
+
value: T;
|
|
4
|
+
lastValidValue: T | undefined;
|
|
5
|
+
valid: boolean;
|
|
6
|
+
};
|
|
7
|
+
type UseValidatedStateReturnValue<T> = [UseValidatedStateValue<T>, (value: T) => void];
|
|
8
|
+
declare const useValidatedState: <T>(initialValue: T, validate: (value: T) => boolean, initialValidationState?: boolean) => UseValidatedStateReturnValue<T>;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { type UseValidatedStateReturnValue, type UseValidatedStateValue, useValidatedState };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useCallback, useState } from "react";
|
|
2
|
+
//#region src/use-validated-state.ts
|
|
3
|
+
const useValidatedState = (initialValue, validate, initialValidationState) => {
|
|
4
|
+
const [state, setState] = useState(() => {
|
|
5
|
+
const valid = validate(initialValue);
|
|
6
|
+
return {
|
|
7
|
+
value: initialValue,
|
|
8
|
+
lastValidValue: valid ? initialValue : void 0,
|
|
9
|
+
valid: initialValidationState ?? valid
|
|
10
|
+
};
|
|
11
|
+
});
|
|
12
|
+
return [state, useCallback((value) => {
|
|
13
|
+
const valid = validate(value);
|
|
14
|
+
setState((previous) => ({
|
|
15
|
+
value,
|
|
16
|
+
lastValidValue: valid ? value : previous.lastValidValue,
|
|
17
|
+
valid
|
|
18
|
+
}));
|
|
19
|
+
}, [validate])];
|
|
20
|
+
};
|
|
21
|
+
//#endregion
|
|
22
|
+
export { useValidatedState };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
//#region src/use-window-event.d.ts
|
|
2
|
+
type WindowEventListener<K extends string> = K extends keyof WindowEventMap ? (event: WindowEventMap[K]) => void : (event: CustomEvent) => void;
|
|
3
|
+
declare const useWindowEvent: <K extends string>(type: K, listener: WindowEventListener<K>, options?: boolean | AddEventListenerOptions) => void;
|
|
4
|
+
//#endregion
|
|
5
|
+
export { useWindowEvent };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { useEffect, useEffectEvent } from "react";
|
|
2
|
+
//#region src/use-window-event.ts
|
|
3
|
+
const useWindowEvent = (type, listener, options) => {
|
|
4
|
+
const onEvent = useEffectEvent((event) => listener(event));
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
window.addEventListener(type, onEvent, options);
|
|
7
|
+
return () => window.removeEventListener(type, onEvent, options);
|
|
8
|
+
}, [type, options]);
|
|
9
|
+
};
|
|
10
|
+
//#endregion
|
|
11
|
+
export { useWindowEvent };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/use-window-scroll.d.ts
|
|
2
|
+
type UseWindowScrollPosition = {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
};
|
|
6
|
+
type UseWindowScrollTo = (position: Partial<UseWindowScrollPosition>) => void;
|
|
7
|
+
type UseWindowScrollReturnValue = [UseWindowScrollPosition, UseWindowScrollTo];
|
|
8
|
+
declare const useWindowScroll: () => UseWindowScrollReturnValue;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { type UseWindowScrollPosition, type UseWindowScrollReturnValue, type UseWindowScrollTo, useWindowScroll };
|