import * as React from 'react'; import { useSetKeyboardNavigation } from '@fluentui/react-tabster'; import { mergeCallbacks, slot, useEventCallback, useMergedRefs } from '@fluentui/react-utilities'; import { getDropdownActionFromKey } from '../utils/dropdownKeyActions'; /** * Shared trigger behaviour for combobox and dropdown * @returns trigger slot with desired behaviour and props */ export function useTriggerSlot(triggerSlotFromProp, ref, options) { const { state: { open, setOpen, setHasFocus }, defaultProps, elementType, activeDescendantController } = options; const trigger = slot.always(triggerSlotFromProp, { defaultProps: { type: 'text', 'aria-expanded': open, role: 'combobox', ...typeof defaultProps === 'object' && defaultProps }, elementType }); // handle trigger focus/blur const triggerRef = React.useRef(null); trigger.ref = useMergedRefs(triggerRef, trigger.ref, ref); // the trigger should open/close the popup on click or blur trigger.onBlur = mergeCallbacks((event)=>{ setOpen(event, false); setHasFocus(false); }, trigger.onBlur); trigger.onFocus = mergeCallbacks((event)=>{ if (event.target === event.currentTarget) { setHasFocus(true); } }, trigger.onFocus); trigger.onClick = mergeCallbacks((event)=>{ setOpen(event, !open); }, trigger.onClick); // handle combobox keyboard interaction trigger.onKeyDown = mergeCallbacks(useTriggerKeydown({ activeDescendantController, ...options.state }), trigger.onKeyDown); return trigger; } function useTriggerKeydown(options) { const { activeDescendantController, getOptionById, setOpen, selectOption, multiselect, open } = options; const getActiveOption = React.useCallback(()=>{ const activeOptionId = activeDescendantController.active(); return activeOptionId ? getOptionById(activeOptionId) : undefined; }, [ activeDescendantController, getOptionById ]); const first = ()=>{ activeDescendantController.first(); }; const last = ()=>{ activeDescendantController.last(); }; const next = (activeOption)=>{ if (activeOption) { activeDescendantController.next(); } else { activeDescendantController.first(); } }; const previous = (activeOption)=>{ if (activeOption) { activeDescendantController.prev(); } else { activeDescendantController.first(); } }; const pageUp = ()=>{ for(let i = 0; i < 10; i++){ activeDescendantController.prev(); } }; const pageDown = ()=>{ for(let i = 0; i < 10; i++){ activeDescendantController.next(); } }; const setKeyboardNavigation = useSetKeyboardNavigation(); return useEventCallback((e)=>{ const action = getDropdownActionFromKey(e, { open, multiselect }); const activeOption = getActiveOption(); switch(action){ case 'First': case 'Last': case 'Next': case 'Previous': case 'PageDown': case 'PageUp': case 'Open': case 'Close': case 'CloseSelect': case 'Select': e.preventDefault(); break; } setKeyboardNavigation(true); switch(action){ case 'First': first(); break; case 'Last': last(); break; case 'Next': next(activeOption); break; case 'Previous': previous(activeOption); break; case 'PageDown': pageDown(); break; case 'PageUp': pageUp(); break; case 'Open': setOpen(e, true); break; case 'Close': // stop propagation for escape key to avoid dismissing any parent popups e.stopPropagation(); setOpen(e, false); break; case 'CloseSelect': !multiselect && !(activeOption === null || activeOption === void 0 ? void 0 : activeOption.disabled) && setOpen(e, false); // fallthrough case 'Select': activeOption && selectOption(e, activeOption); break; case 'Tab': !multiselect && activeOption && selectOption(e, activeOption); break; } }); }