hooks_use-popup.js

import { React } from '@gravityforms/libraries';

const { useEffect, 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 {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.
 * @param {object}   args.popupRef                The reference to the popup element.
 * @param {object}   args.triggerRef              The reference to the trigger element.
 *
 * @return {object} The popup state.
 */
const usePopup = ( {
	closeOnClickOutside = true,
	customClickOutsideLogic = () => {},
	duration = 150,
	initialOpen = false,
	onAfterClose = () => {},
	onAfterOpen = () => {},
	onClose = () => {},
	onOpen = () => {},
	popupRef = null,
	triggerRef = null,
} ) => {
	const [ popupReveal, setPopupReveal ] = useState( false );
	const [ popupHide, setPopupHide ] = useState( false );
	const [ popupOpen, setPopupOpen ] = useState( initialOpen );

	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 );
		};
	}, [ popupOpen, popupRef, triggerRef ] );

	/**
	 * @function openPopup
	 * @description Open the popup.
	 *
	 * @since 4.0.1
	 */
	const openPopup = () => {
		onOpen();
		setPopupReveal( true );
		requestAnimationFrame( () => {
			setPopupOpen( true );
			setTimeout( () => {
				setPopupReveal( false );
				onAfterOpen();
			}, duration );
		} );
	};

	/**
	 * @function closePopup
	 * @description Close the popup.
	 *
	 * @since 4.0.1
	 */
	const closePopup = () => {
		onClose();
		setPopupOpen( false );
		setPopupHide( true );
		setTimeout( () => {
			setPopupHide( false );
			onAfterClose();
		}, duration );
	};

	/**
	 * @function handleEscKeyDown
	 * @description Handles the keydown event for the escape key.
	 *
	 * @since 4.0.1
	 *
	 * @param {Event} event The event object.
	 */
	const handleEscKeyDown = ( event ) => {
		if ( event.key !== ESCAPE ) {
			return;
		}
		// Close dropdown if escape is pressed.
		closePopup();
		triggerRef?.current?.focus();
	};

	return {
		closePopup,
		openPopup,
		handleEscKeyDown,
		popupHide,
		popupOpen,
		popupReveal,
		setPopupHide,
		setPopupOpen,
		setPopupReveal,
	};
};

export default usePopup;