UNPKG

@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
// 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