UNPKG

@lesnoypudge/utils-react

Version:

lesnoypudge's utils-react

108 lines (107 loc) 3.71 kB
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