142 lines
4.7 KiB
JavaScript
142 lines
4.7 KiB
JavaScript
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;
|
|
}
|
|
});
|
|
}
|