@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
164 lines • 8.77 kB
JavaScript
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import { __rest } from "tslib";
import { useCallback, useEffect, useRef } from 'react';
import { warnOnce } from '@awsui/component-toolkit/internal';
import { useInternalI18n } from '../i18n/context';
import { useDropdownStatus } from '../internal/components/dropdown-status';
import { isGroup } from '../internal/components/option/utils/filter-options';
import { prepareOptions } from '../internal/components/option/utils/prepare-options';
import { fireNonCancelableEvent } from '../internal/events';
import { joinStrings } from '../internal/utils/strings';
import { checkOptionValueField } from '../select/utils/check-option-value-field.js';
import { findOptionIndex } from '../select/utils/connect-options';
import { useAnnouncement } from '../select/utils/use-announcement';
import { useLoadItems } from '../select/utils/use-load-items';
import { useNativeSearch } from '../select/utils/use-native-search';
import { useSelect } from '../select/utils/use-select';
export function useMultiselect(_a) {
var { options, filteringType, filteringResultsText, disabled, statusType, empty, loadingText, finishedText, errorText, noMatch, renderHighlightedAriaLive, selectedOptions, deselectAriaLabel, keepOpen, onBlur, onFocus, onLoadItems, onChange, controlId, ariaLabelId, footerId, filteringValue, setFilteringValue, externalRef, embedded } = _a, restProps = __rest(_a, ["options", "filteringType", "filteringResultsText", "disabled", "statusType", "empty", "loadingText", "finishedText", "errorText", "noMatch", "renderHighlightedAriaLive", "selectedOptions", "deselectAriaLabel", "keepOpen", "onBlur", "onFocus", "onLoadItems", "onChange", "controlId", "ariaLabelId", "footerId", "filteringValue", "setFilteringValue", "externalRef", "embedded"]);
checkOptionValueField('Multiselect', 'options', options);
const i18n = useInternalI18n('multiselect');
const i18nCommon = useInternalI18n('select');
const recoveryText = i18nCommon('recoveryText', restProps.recoveryText);
const errorIconAriaLabel = i18nCommon('errorIconAriaLabel', restProps.errorIconAriaLabel);
const selectedAriaLabel = i18nCommon('selectedAriaLabel', restProps.selectedAriaLabel);
if (restProps.recoveryText && !onLoadItems) {
warnOnce('Multiselect', '`onLoadItems` must be provided for `recoveryText` to be displayed.');
}
const { handleLoadMore, handleRecoveryClick, fireLoadItems } = useLoadItems({
onLoadItems,
options,
statusType,
});
const useInteractiveGroups = true;
const { filteredOptions, parentMap, totalCount, matchesCount } = prepareOptions(options, filteringType, filteringValue);
const updateSelectedOption = useCallback((option) => {
const filtered = filteredOptions.filter(item => item.type !== 'parent').map(item => item.option);
// switch between selection and deselection behavior, ignores disabled options to prevent
// getting stuck on one behavior when an option is disabled and its state cannot be changed
const isAllChildrenSelected = (optionsArray) => optionsArray.every(item => findOptionIndex(selectedOptions, item) > -1 || item.disabled);
const intersection = (visibleOptions, options) => visibleOptions.filter(item => findOptionIndex(options, item) > -1 && !item.disabled);
const union = (visibleOptions, options) => visibleOptions.filter(item => findOptionIndex(options, item) === -1).concat(options);
const select = (options, selectedOptions) => {
return union(selectedOptions, options);
};
const unselect = (options, selectedOptions) => {
return selectedOptions.filter(option => findOptionIndex(options, option) === -1);
};
let newSelectedOptions = [...selectedOptions];
if (isGroup(option)) {
const visibleOptions = intersection([...option.options], filtered);
newSelectedOptions = isAllChildrenSelected(visibleOptions)
? unselect(visibleOptions, newSelectedOptions)
: select(visibleOptions, newSelectedOptions);
}
else {
newSelectedOptions = isAllChildrenSelected([option])
? unselect([option], newSelectedOptions)
: select([option], newSelectedOptions);
}
fireNonCancelableEvent(onChange, {
selectedOptions: newSelectedOptions,
});
}, [onChange, selectedOptions, filteredOptions]);
const scrollToIndex = useRef(null);
const { isOpen, highlightType, highlightedOption, highlightedIndex, getTriggerProps, getDropdownProps, getFilterProps, getMenuProps, getOptionProps, highlightOption, announceSelected, } = useSelect({
selectedOptions,
updateSelectedOption,
options: filteredOptions,
filteringType,
onFocus,
onBlur,
externalRef,
keepOpen,
fireLoadItems,
setFilteringValue,
useInteractiveGroups,
statusType,
embedded,
});
const wrapperOnKeyDown = useNativeSearch({
isEnabled: filteringType === 'none' && isOpen,
options: filteredOptions,
highlightOption: highlightOption,
highlightedOption: highlightedOption === null || highlightedOption === void 0 ? void 0 : highlightedOption.option,
useInteractiveGroups,
});
const isEmpty = !options || options.length === 0;
const isNoMatch = filteredOptions && filteredOptions.length === 0;
const isFiltered = filteringType !== 'none' && filteringValue.length > 0 && filteredOptions && filteredOptions.length > 0;
const filteredText = isFiltered ? filteringResultsText === null || filteringResultsText === void 0 ? void 0 : filteringResultsText(matchesCount, totalCount) : undefined;
const dropdownStatus = useDropdownStatus({
statusType,
empty,
loadingText,
finishedText,
errorText,
recoveryText,
isEmpty,
isNoMatch,
noMatch,
isFiltered,
filteringResultsText: filteredText,
onRecoveryClick: handleRecoveryClick,
errorIconAriaLabel: errorIconAriaLabel,
hasRecoveryCallback: !!onLoadItems,
});
const announcement = useAnnouncement({
announceSelected,
highlightedOption,
getParent: option => { var _a; return (_a = parentMap.get(option)) === null || _a === void 0 ? void 0 : _a.option; },
selectedAriaLabel,
renderHighlightedAriaLive,
});
const tokens = selectedOptions.map(option => ({
label: option.label,
disabled: disabled || option.disabled,
labelTag: option.labelTag,
description: option.description,
iconAlt: option.iconAlt,
iconName: option.iconName,
iconUrl: option.iconUrl,
iconSvg: option.iconSvg,
tags: option.tags,
dismissLabel: i18n('deselectAriaLabel', deselectAriaLabel === null || deselectAriaLabel === void 0 ? void 0 : deselectAriaLabel(option), format => { var _a; return format({ option__label: (_a = option.label) !== null && _a !== void 0 ? _a : '' }); }),
}));
useEffect(() => {
var _a;
(_a = scrollToIndex.current) === null || _a === void 0 ? void 0 : _a.call(scrollToIndex, highlightedIndex);
}, [highlightedIndex]);
const dropdownOnMouseDown = (event) => {
const target = event.target;
if (target !== document.activeElement) {
// prevent currently focused element from losing it
event.preventDefault();
}
};
const tokenOnDismiss = ({ detail }) => {
const optionToDeselect = selectedOptions[detail.itemIndex];
updateSelectedOption(optionToDeselect);
const targetRef = getTriggerProps().ref;
if (targetRef.current) {
targetRef.current.focus();
}
};
return {
isOpen,
tokens,
announcement,
dropdownStatus,
filteringValue,
filteredOptions,
highlightType,
scrollToIndex,
getFilterProps,
getTriggerProps,
getMenuProps: () => (Object.assign(Object.assign({}, getMenuProps()), { onLoadMore: handleLoadMore, ariaLabelledby: joinStrings(ariaLabelId, controlId), ariaDescribedby: dropdownStatus.content ? footerId : undefined, embedded })),
getOptionProps,
getTokenProps: () => ({ onDismiss: tokenOnDismiss }),
getDropdownProps: () => (Object.assign(Object.assign({}, getDropdownProps()), { onMouseDown: dropdownOnMouseDown })),
getWrapperProps: () => ({ onKeyDown: wrapperOnKeyDown }),
};
}
//# sourceMappingURL=use-multiselect.js.map