import { React } from '@gravityforms/libraries';
const { useCallback, useEffect, useRef, useState } = React;
const ESCAPE = 'Escape';
/**
* @module usePopup
* @description A hook to manage a popup state.
*
* @since 4.0.1
*
* @param {object} args The arguments for the hook.
* @param {boolean} args.closeOnClickOutside Whether to close the popup when clicking outside.
* @param {Function} args.customClickOutsideLogic Custom logic to determine if the popup should close when clicking outside.
* @param {number} args.duration The duration of the popup animation.
* @param {boolean} args.initialOpen The initial open state of the popup.
* @param {boolean} args.isOpen External control of the popup open state. When provided (not undefined), enables external control mode.
* @param {Function} args.onAfterClose The callback after the popup is closed.
* @param {Function} args.onAfterOpen The callback after the popup is opened.
* @param {Function} args.onClose The callback when the popup is closed.
* @param {Function} args.onOpen The callback when the popup is opened.
*
* @return {object} The popup state.
*/
const usePopup = ( {
closeOnClickOutside = true,
customClickOutsideLogic = () => {},
duration = 150,
initialOpen = false,
isOpen = undefined,
onAfterClose = () => {},
onAfterOpen = () => {},
onClose = () => {},
onOpen = () => {},
} = {} ) => {
// Internal state management for popup visibility and animation states. The popupOpen state also represents the actual open/closed state.
const [ popupReveal, setPopupReveal ] = useState( false );
const [ popupHide, setPopupHide ] = useState( false );
const [ popupOpen, setPopupOpen ] = useState( initialOpen );
const popupRef = useRef( null );
const triggerRef = useRef( null );
/**
* @function openPopup
* @description Open the popup.
*
* @since 4.0.1
*
*/
const openPopup = useCallback( () => {
onOpen();
setPopupReveal( true );
setTimeout( () => {
setPopupOpen( true );
setTimeout( () => {
setPopupReveal( false );
onAfterOpen();
}, duration );
}, 0 );
}, [ duration, onAfterOpen, onOpen ] );
/**
* @function closePopup
* @description Close the popup.
*
* @since 4.0.1
*
*/
const closePopup = useCallback( () => {
onClose();
setPopupOpen( false );
setPopupHide( true );
setTimeout( () => {
setPopupHide( false );
onAfterClose();
}, duration );
}, [ duration, onAfterClose, onClose ] );
/**
* @function handleEscKeyDown
* @description Handles the keydown event for the escape key.
*
* @since 4.0.1
*
* @param {Event} event The event object.
*/
const handleEscKeyDown = useCallback( ( event ) => {
if ( event.key !== ESCAPE ) {
return;
}
// Close dropdown if escape is pressed.
closePopup();
triggerRef?.current?.focus();
}, [ closePopup, triggerRef ] );
// Sync internal popupOpen state with external isOpen prop if provided.
useEffect( () => {
if ( isOpen === undefined || isOpen === popupOpen ) {
return;
}
if ( isOpen ) {
openPopup();
} else {
closePopup();
}
}, [ isOpen ] ); // eslint-disable-line react-hooks/exhaustive-deps
useEffect( () => {
if ( ! closeOnClickOutside ) {
return;
}
const handleClickOutside = ( event ) => {
// If refs don't exist, return early.
if ( ! popupOpen || ! triggerRef?.current || ! popupRef?.current ) {
return;
}
if (
! triggerRef.current.contains( event.target ) &&
! popupRef.current.contains( event.target ) &&
! customClickOutsideLogic( event )
) {
closePopup();
}
};
document.addEventListener( 'click', handleClickOutside );
return () => {
document.removeEventListener( 'click', handleClickOutside );
};
}, [ closeOnClickOutside, closePopup, customClickOutsideLogic, popupOpen ] );
return {
closePopup,
openPopup,
handleEscKeyDown,
popupHide,
popupOpen,
popupReveal,
setPopupHide,
setPopupOpen,
setPopupReveal,
popupRef,
triggerRef,
};
};
export default usePopup;