hooks_use-hijack-wp-menu.js

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

const { useEffect } = React;
const { useNavigate } = ReactRouter;

/**
 * @function getSearchParamsObjFromKeys
 * @description Get a search params object from an array of keys and search params provided.
 *
 * @since 4.0.2
 *
 * @param {Array|string}    keys         The keys to get from the search params.
 * @param {URLSearchParams} searchParams The search params to get the keys from.
 *
 * @return {object} The search params object.
 */
const getSearchParamsObjFromKeys = ( keys = [], searchParams = null ) => {
	if ( ! searchParams || ! keys.length ) {
		return {};
	}

	if ( Array.isArray( keys ) ) {
		return keys.reduce( ( acc, key ) => {
			acc[ key ] = searchParams.get( key );
			return acc;
		}, {} );
	}

	return { [ keys ]: searchParams.get( keys ) };
};

/**
 * @function addCurrentToMenuItem
 * @description Add the current class and attributes to a menu item.
 *
 * @since 4.0.2
 *
 * @param {HTMLElement} item The menu item to add the current class and attributes to.
 */
const addCurrentToMenuItem = ( item ) => {
	item.classList.add( 'current' );
	const itemLink = item.querySelector( 'a' );
	itemLink.classList.add( 'current' );
	itemLink.setAttribute( 'aria-current', 'page' );
};

/**
 * @function removeCurrentFromMenuItem
 * @description Remove the current class and attributes from a menu item.
 *
 * @since 4.0.2
 *
 * @param {HTMLElement} item The menu item to remove the current class and attributes from.
 */
const removeCurrentFromMenuItem = ( item ) => {
	item.classList.remove( 'current' );
	const itemLink = item.querySelector( 'a' );
	itemLink.classList.remove( 'current' );
	itemLink.removeAttribute( 'aria-current' );
};

/**
 * @function useHijackWpMenu
 * @description Hijacks the click event on the menu.
 *
 * @since 4.0.2
 *
 * @param {string}       menuId          The ID of the menu to hijack.
 * @param {Array|string} paramKeys       The keys to get from the search params.
 * @param {Function}     setSearchParams The function to set the search params.
 */
const useHijackWpMenu = ( menuId = '', paramKeys = [], setSearchParams = () => {} ) => {
	const navigate = useNavigate();

	useEffect( () => {
		const menu = document.getElementById( menuId );
		if ( ! menu ) {
			return;
		}

		/**
		 * @function hijackMenuClick
		 * @description Hijacks the click event on the menu.
		 *
		 * @since 4.0.2
		 *
		 * @param {Event} event The click event.
		 */
		const hijackMenuClick = ( event ) => {
			event.preventDefault();
			const link = event.currentTarget;
			const href = link.getAttribute( 'href' );

			const menuItems = menu.querySelectorAll( 'li:not(.wp-submenu-head)' );
			menuItems.forEach( removeCurrentFromMenuItem );
			if ( link.classList.contains( 'menu-top' ) ) {
				// Link is a top-level menu item, update classes and aria attributes.
				menuItems.forEach( ( item ) => {
					if ( item.querySelector( 'a' )?.getAttribute( 'href' ) !== href ) {
						return;
					}
					addCurrentToMenuItem( item );
				} );
			} else {
				// Link is a submenu item, update classes and aria attributes.
				addCurrentToMenuItem( link.parentElement );
			}

			const linkSearchParams = new URLSearchParams( href.split( '?' )[ 1 ] );
			const newSearchParams = getSearchParamsObjFromKeys( paramKeys, linkSearchParams );
			setSearchParams( newSearchParams );
			navigate( `?${ linkSearchParams.toString() }`, { replace: true } );
		};

		const menuLinks = menu.querySelectorAll( 'a' );
		menuLinks.forEach( ( link ) => {
			link.addEventListener( 'click', hijackMenuClick );
		} );

		return () => {
			menuLinks.forEach( ( link ) => {
				link.removeEventListener( 'click', hijackMenuClick );
			} );
		};
	}, [ menuId, paramKeys, setSearchParams, navigate ] );
};

export default useHijackWpMenu;