UNPKG

@gravity-ui/uikit

Version:

Gravity UI base styling and components

120 lines (119 loc) 4.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.useFocusWithin = useFocusWithin; const tslib_1 = require("tslib"); const React = tslib_1.__importStar(require("react")); const useLayoutEffect_1 = require("../useLayoutEffect/index.js"); const SyntheticFocusEvent_1 = require("./SyntheticFocusEvent.js"); const useSyntheticBlurEvent_1 = require("./useSyntheticBlurEvent.js"); /** * Callback on focus outside event. * @callback onFocusEventCallback * @param {FocusEvent} event */ /** * Callback on focus change event. * @callback onFocusChangeCallback * @param {boolean} isFocusWithin */ /** * Handles focus events for the target and its descendants. * @param {object} props * @param {boolean} [props.isDisabled] - whether the focus within events should be disabled. * @param {onFocusEventCallback} props.onFocusWithin - handler that is called when the target element or a descendant receives focus. * @param {onFocusEventCallback} props.onBlurWithin - handler that is called when the target element and all descendants lose focus. * @param {onFocusChangeCallback} props.onFocusChange - handler that is called when the the focus within state changes. * @returns container props * @example * * function Select() { * const [open, setOpen] = React.useState(false); * * const handleFocusOutside = React.useCallback(() => {setOpen(false);}, []); * * const {focusWithinProps} = useFocusWithin({onBlurWithin: handleFocusOutside}); * * return ( * <span {...focusWithinProps}> * <Button onClick={() => {setOpen(true)}}>Select</Button> * <Popup open={open}> * ... * </Popup> * </span> * ); * } */ function useFocusWithin(props) { const { onFocusWithin, onBlurWithin, onFocusWithinChange, isDisabled } = props; const isFocusWithinRef = React.useRef(false); const onFocus = React.useCallback((event) => { if (!isFocusWithinRef.current && document.activeElement === event.target) { isFocusWithinRef.current = true; if (onFocusWithin) { onFocusWithin(event); } if (onFocusWithinChange) { onFocusWithinChange(true); } } }, [onFocusWithin, onFocusWithinChange]); const onBlur = React.useCallback((event) => { if (!isFocusWithinRef.current) { return; } isFocusWithinRef.current = false; if (onBlurWithin) { onBlurWithin(event); } if (onFocusWithinChange) { onFocusWithinChange(false); } }, [onBlurWithin, onFocusWithinChange]); const { onBlur: onBlurHandler, onFocus: onFocusHandler } = useFocusEvents({ onFocus, onBlur, }); if (isDisabled) { return { focusWithinProps: { onFocus: undefined, onBlur: undefined, }, }; } return { focusWithinProps: { onFocus: onFocusHandler, onBlur: onBlurHandler, }, }; } function useFocusEvents({ onFocus, onBlur, }) { const capturedRef = React.useRef(false); const onBlurRef = React.useRef(onBlur); (0, useLayoutEffect_1.useLayoutEffect)(() => { onBlurRef.current = onBlur; }); const onBlurHandler = React.useCallback((event) => { if (event.currentTarget.contains(event.relatedTarget)) { return; } capturedRef.current = false; const newEvent = new SyntheticFocusEvent_1.SyntheticFocusEvent('blur', event.nativeEvent, { currentTarget: event.currentTarget, }); setTimeout(() => { if (!capturedRef.current) { onBlurRef.current(newEvent); } }, 0); }, []); const onSyntheticFocus = (0, useSyntheticBlurEvent_1.useSyntheticBlurEvent)(onBlur); const onFocusHandler = React.useCallback((event) => { capturedRef.current = true; onSyntheticFocus(event); onFocus(event); }, [onSyntheticFocus, onFocus]); return { onBlur: onBlurHandler, onFocus: onFocusHandler }; } //# sourceMappingURL=useFocusWithin.js.map