UNPKG

@gravityforms/components

Version:

UI components for use in Gravity Forms development. Both React and vanilla js flavors.

183 lines (174 loc) 5.45 kB
import { ARROW_UP, ARROW_DOWN, HOME, PAGE_UP, END, PAGE_DOWN, SPACE, ENTER, ESCAPE } from '../../../utils/keymap'; /** * @function useDropdownKeyDown * @description Adds the keydown handlers to props. * * @since 4.5.0 * * @param {object} props The props to add handlers to. * @param {Function} useStore The useStore hook. * * @return {object} The modified props. */ const useDropdownKeyDown = ( props = {}, useStore = () => {} ) => { const { hasSearch = false, multi = false, open = () => {}, resetAndClose = () => {}, selectItem = () => {}, selectMultipleItems = () => {}, } = props; const activeItem = useStore( ( state ) => state.activeItem ); const selectedItem = useStore( ( state ) => state.selectedItem ); const listItems = useStore( ( state ) => state.listItems ); const dropdownOpen = useStore( ( state ) => state.open ); const setActiveItem = useStore( ( state ) => state.setActiveItem ); const setSelectedItem = useStore( ( state ) => state.setSelectedItem ); const baseElRef = useStore( ( state ) => state.baseElRef ); const popoverRef = useStore( ( state ) => state.popoverRef ); const triggerRef = useStore( ( state ) => state.triggerRef ); const allowedKeys = [ ARROW_UP, ARROW_DOWN, HOME, PAGE_UP, END, PAGE_DOWN, ENTER, ]; if ( ! hasSearch ) { allowedKeys.push( SPACE ); } if ( multi ) { allowedKeys.push( 'a' ); } /** * @function handleEscKeyDown * @description Handles the keydown event for the escape key. * * @since 4.5.0 * * @param {Event} event The event object. */ const handleEscKeyDown = ( event ) => { if ( event.key !== ESCAPE ) { return; } // Close dropdown if escape is pressed. resetAndClose( event ); triggerRef?.current?.focus(); }; /** * @function handleTriggerKeyDown * @description Handles the keydown event for the dropdown trigger. * * @since 4.5.0 * * @param {Event} event The event object. */ const handleTriggerKeyDown = ( event ) => { // Return early if not arrow up or down key. if ( ! [ ARROW_UP, ARROW_DOWN ].includes( event.key ) ) { return; } if ( ! dropdownOpen ) { open(); } // Current target is not the base element, focus on the base element. baseElRef?.current?.focus(); }; /** * @function handleListKeyDown * @description Handles the keydown event for the dropdown list. * * @since 4.5.0 * * @param {Event} event The event object. */ const handleListKeyDown = ( event ) => { // If default prevented from earlier propagation, return early. if ( event.defaultPrevented ) { return; } // If not one of the allowed keys, return early. if ( ! allowedKeys.includes( event.key ) ) { return; } // Prevent default to prevent multiple calls of this event handler. if ( ! multi || event.key !== 'a' ) { event.preventDefault(); } // Handle if arrow up or arrow down is pressed. if ( [ ARROW_UP, ARROW_DOWN ].includes( event.key ) ) { const activeIndex = listItems.ids.indexOf( activeItem.id ); if ( activeIndex === -1 ) { return; } let nextIndex; if ( event.key === ARROW_UP ) { nextIndex = activeIndex - 1; if ( nextIndex === -1 ) { nextIndex = listItems.ids.length - 1; } } if ( event.key === ARROW_DOWN ) { nextIndex = activeIndex + 1; if ( nextIndex === listItems.ids.length ) { nextIndex = 0; } } const nextItem = listItems.items[ nextIndex ]; setActiveItem( nextItem ); if ( nextItem.component !== 'search' && event.shiftKey ) { selectItem( nextItem )( event ); } popoverRef?.current?.querySelector( `#${ nextItem.id }` )?.focus(); } // Set active item to first item if home or page up is pressed. if ( [ HOME, PAGE_UP ].includes( event.key ) ) { const nextIndex = 0; const nextItem = listItems.items[ nextIndex ]; if ( event.shiftKey && event.ctrlKey ) { const activeIndex = listItems.ids.indexOf( activeItem.id ); const items = listItems.items.slice( nextIndex, activeIndex + 1 ); // Select items in reverse order as we are jumping to the top. selectMultipleItems( items.reverse() ); } setActiveItem( nextItem ); } // Set active item to last item if end or page down is pressed. if ( [ END, PAGE_DOWN ].includes( event.key ) ) { const nextIndex = hasSearch ? listItems.ids.length - 2 : listItems.ids.length - 1; const nextItem = listItems.items[ nextIndex ]; if ( event.shiftKey && event.ctrlKey ) { const activeIndex = listItems.ids.indexOf( activeItem.id ); const items = listItems.items.slice( activeIndex, nextIndex + 1 ); selectMultipleItems( items ); } setActiveItem( nextItem ); } if ( event.key === 'a' && event.ctrlKey ) { // If selected items are already all items, deselect all items. if ( selectedItem.length === listItems.items.length - ( hasSearch ? 1 : 0 ) ) { setSelectedItem( [] ); return; } // Select all items except search. const items = listItems.items.filter( ( item ) => item.component !== 'search' ); selectMultipleItems( items ); } // Set active item to selected item. // If search is active, only listen for ENTER key, as space is allowed in the input. if ( [ ENTER, ...( hasSearch ? [] : [ SPACE ] ) ].includes( event.key ) ) { selectItem( activeItem )( event ); } }; return { ...props, handleEscKeyDown, handleTriggerKeyDown, handleListKeyDown, }; }; export default useDropdownKeyDown;