UNPKG

@restart/hooks

Version:

A set of utility and general-purpose React hooks.

74 lines (72 loc) 2.63 kB
"use strict"; exports.__esModule = true; exports.default = useFocusManager; var _react = require("react"); var _useEventCallback = _interopRequireDefault(require("./useEventCallback")); var _useMounted = _interopRequireDefault(require("./useMounted")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /** * useFocusManager provides a way to track and manage focus as it moves around * a container element. An `onChange` is fired when focus enters or leaves the * element, but not when it moves around inside the element, similar to * `pointerenter` and `pointerleave` DOM events. * * ```tsx * const [focused, setFocusState] = useState(false) * * const { onBlur, onFocus } = useFocusManager({ * onChange: nextFocused => setFocusState(nextFocused) * }) * * return ( * <div tabIndex="-1" onFocus={onFocus} onBlur={onBlur}> * {String(focused)} * <input /> * <input /> * * <button>A button</button> * </div> * ``` * * @returns a memoized FocusController containing event handlers */ function useFocusManager(opts) { const isMounted = (0, _useMounted.default)(); const lastFocused = (0, _react.useRef)(); const handle = (0, _react.useRef)(); const willHandle = (0, _useEventCallback.default)(opts.willHandle); const didHandle = (0, _useEventCallback.default)(opts.didHandle); const onChange = (0, _useEventCallback.default)(opts.onChange); const isDisabled = (0, _useEventCallback.default)(opts.isDisabled); const handleChange = (0, _react.useCallback)((focused, event) => { if (focused !== lastFocused.current) { didHandle == null ? void 0 : didHandle(focused, event); // only fire a change when unmounted if its a blur if (isMounted() || !focused) { lastFocused.current = focused; onChange == null ? void 0 : onChange(focused, event); } } }, [isMounted, didHandle, onChange, lastFocused]); const handleFocusChange = (0, _react.useCallback)((focused, event) => { if (isDisabled()) return; if (event && event.persist) event.persist(); if ((willHandle == null ? void 0 : willHandle(focused, event)) === false) { return; } clearTimeout(handle.current); if (focused) { handleChange(focused, event); } else { handle.current = window.setTimeout(() => handleChange(focused, event)); } }, [willHandle, handleChange]); return (0, _react.useMemo)(() => ({ onBlur: event => { handleFocusChange(false, event); }, onFocus: event => { handleFocusChange(true, event); } }), [handleFocusChange]); }