Files
Tom Moor 0139b91b5d chore: Replace lodash with es-toolkit (#12281)
* chore: Replace lodash with es-toolkit

Migrate all direct lodash imports to es-toolkit/compat for a smaller,
faster, lodash-compatible utility library. Transitive lodash usage from
other packages remains unchanged.

* fix: Restore isPlainObject semantics in CanCan policy

The lodash migration aliased `isObject` to `lodash/isPlainObject` and
the codemod incorrectly mapped the local name to es-toolkit's `isObject`,
which also returns true for arrays and functions. This caused condition
objects in policy definitions to be skipped, breaking authorization
checks across the codebase.

* fix: Restore unicode-aware length counting in validators

es-toolkit/compat's size() returns string.length, while lodash's _.size()
counts unicode code points. Switch to [...value].length to preserve the
previous behavior so multi-byte characters like emoji count as one.
2026-05-06 21:03:47 -04:00

65 lines
1.5 KiB
TypeScript

import { throttle } from "es-toolkit/compat";
import { useState, useRef, useCallback, useEffect } from "react";
import { Minute } from "@shared/utils/time";
import useIsMounted from "./useIsMounted";
const activityEvents = [
"click",
"mousemove",
"keydown",
"DOMMouseScroll",
"mousewheel",
"mousedown",
"touchstart",
"touchmove",
"focus",
];
/**
* Hook to detect user idle state.
*
* @param timeToIdle The time in ms until idle
* @param events The events to listen to
* @returns boolean if the user is idle
*/
export default function useIdle(
timeToIdle: number = 3 * Minute.ms,
events = activityEvents
) {
const isMounted = useIsMounted();
const [isIdle, setIsIdle] = useState(false);
const timeout = useRef<ReturnType<typeof setTimeout>>();
const onActivity = useCallback(() => {
if (timeout.current) {
clearTimeout(timeout.current);
}
timeout.current = setTimeout(() => {
if (isMounted()) {
setIsIdle(true);
}
}, timeToIdle);
}, [isMounted, timeToIdle]);
useEffect(() => {
const handleUserActivityEvent = throttle(() => {
if (isMounted()) {
setIsIdle(false);
onActivity();
}
}, 1000);
events.forEach((eventName) =>
window.addEventListener(eventName, handleUserActivityEvent)
);
return () => {
events.forEach((eventName) =>
window.removeEventListener(eventName, handleUserActivityEvent)
);
};
}, [events, isMounted, onActivity]);
return isIdle;
}