@lesnoypudge/utils-react
Version:
lesnoypudge's utils-react
108 lines (107 loc) • 3.71 kB
JavaScript
import { useRefManager } from "../useRefManager/useRefManager.js";
import { useEventListener } from "../useEventListener/useEventListener.js";
import { useState, useEffect } from "react";
import { noop, combinedFunction } from "@lesnoypudge/utils";
import { FocusVisibleManager } from "@lesnoypudge/utils-web";
import { useFunction } from "../useFunction/useFunction.js";
import { useConst } from "../useConst/useConst.js";
import { mutate } from "../../utils/mutate/mutate.js";
const useIsFocused = (elementRef, options) => {
const {
within = false,
stateless = false,
visible = false,
onBlur = noop,
onFocus = noop
} = options ?? {};
const focusManager = useConst(() => new FocusVisibleManager());
const [isFocused, setIsFocused] = useState(false);
const isFocusedRef = useRefManager(isFocused);
const inEvent = within ? "focusin" : "focus";
const outEvent = within ? "focusout" : "blur";
const withStateUpdate = !stateless;
const eventTypeToFlag = {
base: !visible && !within,
visibleWithin: visible && within,
visible: visible && !within,
within: !visible && within
};
const getShouldUpdateState = (e) => {
const observable = elementRef.current;
if (!observable) return false;
if (!e.target) return false;
if (visible) return false;
const relatedTarget = e.relatedTarget;
const isDifferent = observable !== relatedTarget;
if (eventTypeToFlag.base) {
return isDifferent;
}
if (eventTypeToFlag.within) {
const notContains = !observable.contains(relatedTarget);
return notContains;
}
return false;
};
const updateState = (newState) => {
if (isFocusedRef.current === newState) return;
withStateUpdate && setIsFocused(newState);
mutate(isFocusedRef, "current", newState);
};
const getShouldUpdateVisibleState = (direction, element) => {
const observable = elementRef.current;
if (!observable) return false;
const isIn = direction === "in";
const isSame = observable === element;
const shouldUpdateState = isIn ? !isFocusedRef.current : isFocusedRef.current;
const shouldResetFocus = isFocusedRef.current && !focusManager.isValidFocusTarget(element);
if (shouldResetFocus) return true;
if (eventTypeToFlag.visible) {
if (isIn) {
return shouldUpdateState && isSame;
}
return shouldUpdateState && !isSame;
}
if (eventTypeToFlag.visibleWithin) {
const isContains = focusManager.isValidFocusTarget(element) && observable.contains(element);
if (isIn) {
return shouldUpdateState && isContains;
}
return shouldUpdateState && !isContains;
}
return false;
};
const handleVisibleEvents = useFunction(() => {
return combinedFunction(focusManager.onIn((element) => {
if (!getShouldUpdateVisibleState("in", element)) return;
updateState(true);
onFocus(element);
}), focusManager.onOut((e) => {
if (!getShouldUpdateVisibleState("out", e.relatedTarget)) return;
updateState(false);
onBlur(e);
}));
});
useEffect(() => {
focusManager.start();
return combinedFunction(handleVisibleEvents(), focusManager.clean);
}, [handleVisibleEvents, focusManager]);
useEventListener(elementRef, inEvent, (e) => {
if (!focusManager.isValidFocusTarget(e.target)) return;
if (!getShouldUpdateState(e)) return;
updateState(true);
onFocus(e.target);
});
useEventListener(elementRef, outEvent, (e) => {
if (!getShouldUpdateState(e)) return;
updateState(false);
onBlur(e);
});
return {
isFocused,
isFocusedRef
};
};
export {
useIsFocused
};
//# sourceMappingURL=useIsFocused.js.map