@awsui/components-react
Version:
On July 19th, 2022, we launched [Cloudscape Design System](https://cloudscape.design). Cloudscape is an evolution of AWS-UI. It consists of user interface guidelines, front-end components, design resources, and development tools for building intuitive, en
282 lines • 12.1 kB
JavaScript
import { useEffect, useRef } from 'react';
import { useUniqueId } from '@awsui/component-toolkit/internal';
import { isGroup, isGroupInteractive, isInteractive } from '../../internal/components/option/utils/filter-options';
import { useHighlightedOption } from '../../internal/components/options-list/utils/use-highlight-option';
import { getOptionId } from '../../internal/components/options-list/utils/use-ids';
import { useMenuKeyboard, useTriggerKeyboard } from '../../internal/components/options-list/utils/use-keyboard';
import { useOpenState } from '../../internal/components/options-list/utils/use-open-state';
import { fireNonCancelableEvent } from '../../internal/events';
import useForwardFocus from '../../internal/hooks/forward-focus';
import { usePrevious } from '../../internal/hooks/use-previous';
import { connectOptionsByValue } from './connect-options';
export function useSelect({ selectedOptions, updateSelectedOption, options, filteringType, onBlur, onFocus, externalRef, keepOpen, embedded, fireLoadItems, setFilteringValue, useInteractiveGroups = false, statusType, isAllSelected, isSomeSelected, toggleAll, }) {
const interactivityCheck = useInteractiveGroups ? isGroupInteractive : isInteractive;
const isHighlightable = (option) => !!option && (useInteractiveGroups || option.type !== 'parent');
const filterRef = useRef(null);
const triggerRef = useRef(null);
const menuRef = useRef(null);
const hasFilter = filteringType !== 'none' && !embedded;
const activeRef = hasFilter ? filterRef : menuRef;
const __selectedOptions = connectOptionsByValue(options, selectedOptions);
const __selectedValuesSet = selectedOptions.reduce((selectedValuesSet, item) => {
if (item.value) {
selectedValuesSet.add(item.value);
}
return selectedValuesSet;
}, new Set());
const [{ highlightType, highlightedOption, highlightedIndex }, { moveHighlightWithKeyboard, resetHighlightWithKeyboard, setHighlightedIndexWithMouse, highlightOptionWithKeyboard, highlightFirstOptionWithMouse, goHomeWithKeyboard, goEndWithKeyboard, },] = useHighlightedOption({ options: options, isHighlightable });
const { isOpen, openDropdown, closeDropdown, toggleDropdown, openedWithKeyboard } = useOpenState({
defaultOpen: embedded,
onOpen: () => fireLoadItems(''),
onClose: () => {
resetHighlightWithKeyboard();
setFilteringValue === null || setFilteringValue === void 0 ? void 0 : setFilteringValue('');
},
});
const handleFocus = () => {
fireNonCancelableEvent(onFocus, {});
};
const handleBlur = () => {
fireNonCancelableEvent(onBlur, {});
closeDropdown();
};
const hasSelectedOption = __selectedOptions.length > 0;
const menuId = useUniqueId('option-list');
const dialogId = useUniqueId('dialog');
const highlightedOptionId = getOptionId(menuId, highlightedIndex);
const closeDropdownIfNecessary = () => {
var _a;
if (!keepOpen) {
(_a = triggerRef.current) === null || _a === void 0 ? void 0 : _a.focus();
closeDropdown();
}
};
const selectOption = (option) => {
const optionToSelect = option || highlightedOption;
if (!optionToSelect || !interactivityCheck(optionToSelect)) {
return;
}
if (optionToSelect.type === 'select-all' && toggleAll) {
toggleAll();
}
else {
updateSelectedOption(optionToSelect.option);
}
closeDropdownIfNecessary();
};
const activeKeyDownHandler = useMenuKeyboard({
goUp: () => {
if ((!useInteractiveGroups && (highlightedOption === null || highlightedOption === void 0 ? void 0 : highlightedOption.type) === 'child' && highlightedIndex === 1) ||
highlightedIndex === 0) {
goEndWithKeyboard();
return;
}
moveHighlightWithKeyboard(-1);
},
goDown: () => {
if (highlightedIndex === options.length - 1) {
goHomeWithKeyboard();
return;
}
moveHighlightWithKeyboard(1);
},
selectOption,
goHome: goHomeWithKeyboard,
goEnd: goEndWithKeyboard,
closeDropdown: () => {
var _a;
if (!embedded) {
(_a = triggerRef.current) === null || _a === void 0 ? void 0 : _a.focus();
closeDropdown();
}
},
preventNativeSpace: !hasFilter || (highlightedOption && highlightType.type === 'keyboard'),
});
const triggerKeyDownHandler = useTriggerKeyboard({
openDropdown: () => openDropdown(true),
goHome: goHomeWithKeyboard,
});
const getDropdownProps = () => ({
onFocus: handleFocus,
onBlur: handleBlur,
dropdownContentId: dialogId,
dropdownContentRole: hasFilter ? 'dialog' : undefined,
});
const getTriggerProps = (disabled = false, autoFocus = false) => {
const triggerProps = {
ref: triggerRef,
onFocus: () => closeDropdown(),
autoFocus,
ariaHasPopup: hasFilter ? 'dialog' : 'listbox',
ariaControls: isOpen ? (hasFilter ? dialogId : menuId) : undefined,
};
if (!disabled) {
triggerProps.onMouseDown = (event) => {
var _a;
event.preventDefault(); // prevent current focus from blurring as it immediately closes the dropdown
if (isOpen) {
(_a = triggerRef.current) === null || _a === void 0 ? void 0 : _a.focus();
}
toggleDropdown();
};
triggerProps.onKeyDown = triggerKeyDownHandler;
}
return triggerProps;
};
const getFilterProps = () => {
if (!hasFilter || !setFilteringValue) {
return {};
}
return {
ref: filterRef,
onKeyDown: activeKeyDownHandler,
onChange: event => {
setFilteringValue(event.detail.value);
resetHighlightWithKeyboard();
},
__onDelayedInput: event => {
fireLoadItems(event.detail.value);
},
nativeInputAttributes: {
'aria-activedescendant': highlightedOptionId,
['aria-owns']: menuId,
['aria-controls']: menuId,
},
};
};
const getMenuProps = () => {
const menuProps = {
id: menuId,
ref: menuRef,
open: isOpen,
onMouseUp: itemIndex => {
if (itemIndex > -1) {
selectOption(options[itemIndex]);
}
},
onMouseMove: itemIndex => {
if (itemIndex > -1) {
setHighlightedIndexWithMouse(itemIndex);
}
},
statusType,
};
if (!hasFilter) {
menuProps.onKeyDown = activeKeyDownHandler;
menuProps.nativeAttributes = {
'aria-activedescendant': highlightedOptionId,
};
}
if (embedded) {
menuProps.onFocus = () => {
if (!highlightedOption) {
goHomeWithKeyboard();
}
};
menuProps.onBlur = () => {
resetHighlightWithKeyboard();
};
}
return menuProps;
};
const getGroupState = (option) => {
const totalSelected = option.options.filter(item => !!item.value && __selectedValuesSet.has(item.value)).length;
const hasSelected = totalSelected > 0;
const allSelected = totalSelected === option.options.length;
return {
selected: hasSelected && allSelected && useInteractiveGroups,
indeterminate: hasSelected && !allSelected,
};
};
const getOptionProps = (option, index) => {
var _a, _b;
const isSelectAll = option.type === 'select-all';
const highlighted = option === highlightedOption;
const groupState = isGroup(option.option) ? getGroupState(option.option) : undefined;
const selected = isSelectAll ? isAllSelected : __selectedOptions.indexOf(option) > -1 || !!(groupState === null || groupState === void 0 ? void 0 : groupState.selected);
const nextOption = (_a = options[index + 1]) === null || _a === void 0 ? void 0 : _a.option;
const isNextSelected = !!nextOption && isGroup(nextOption)
? getGroupState(nextOption).selected
: __selectedOptions.indexOf(options[index + 1]) > -1;
const previousOption = (_b = options[index - 1]) === null || _b === void 0 ? void 0 : _b.option;
const isPreviousSelected = !!previousOption && isGroup(previousOption)
? getGroupState(previousOption).selected
: __selectedOptions.indexOf(options[index - 1]) > -1;
const optionProps = {
key: index,
option,
highlighted,
selected,
isNextSelected,
isPreviousSelected,
indeterminate: !!(groupState === null || groupState === void 0 ? void 0 : groupState.indeterminate) || (isSelectAll && !isAllSelected && isSomeSelected),
['data-mouse-target']: isHighlightable(option) ? index : -1,
id: getOptionId(menuId, index),
};
return optionProps;
};
const prevOpen = usePrevious(isOpen);
useEffect(() => {
// highlight the first selected option, when opening the Select component without filter input
// keep the focus in the filter input when opening, so that screenreader can recognize the combobox
if (isOpen && !prevOpen && options.length > 0 && !hasFilter) {
if (openedWithKeyboard) {
if (__selectedOptions[0]) {
highlightOptionWithKeyboard(__selectedOptions[0]);
}
else {
goHomeWithKeyboard();
}
}
else {
if (!__selectedOptions[0] || !options.includes(__selectedOptions[0])) {
highlightFirstOptionWithMouse();
}
else {
const highlightedIndex = options.indexOf(__selectedOptions[0]);
setHighlightedIndexWithMouse(highlightedIndex, true);
}
}
}
}, [
isOpen,
__selectedOptions,
hasSelectedOption,
setHighlightedIndexWithMouse,
highlightOptionWithKeyboard,
highlightFirstOptionWithMouse,
goHomeWithKeyboard,
openedWithKeyboard,
options,
prevOpen,
hasFilter,
]);
useEffect(() => {
var _a;
if (isOpen && !embedded) {
// dropdown-fit calculations ensure that the dropdown will fit inside the current
// viewport, so prevent the browser from trying to scroll it into view (e.g. if
// scroll-padding-top is set on a parent)
(_a = activeRef.current) === null || _a === void 0 ? void 0 : _a.focus({ preventScroll: true });
}
}, [isOpen, activeRef, embedded]);
useForwardFocus(externalRef, triggerRef);
const highlightedGroupSelected = !!highlightedOption && isGroup(highlightedOption.option) && getGroupState(highlightedOption.option).selected;
const announceSelected = !!highlightedOption && (__selectedOptions.indexOf(highlightedOption) > -1 || highlightedGroupSelected);
return {
isOpen,
highlightedOption,
highlightedIndex,
highlightType,
getTriggerProps,
getDropdownProps,
getMenuProps,
getFilterProps,
getOptionProps,
highlightOption: highlightOptionWithKeyboard,
selectOption,
announceSelected,
dialogId,
};
}
//# sourceMappingURL=use-select.js.map