diff --git a/.all-contributorsrc b/.all-contributorsrc
index a74b62ac4ba7..0129e6c6cab2 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -848,6 +848,15 @@
"contributions": [
"code"
]
+ },
+ {
+ "login": "dezkareid",
+ "name": "Joel Humberto Gómez Paredes",
+ "avatar_url": "https://avatars.githubusercontent.com/u/1269896?v=4",
+ "profile": "https://github.com/dezkareid",
+ "contributions": [
+ "code"
+ ]
}
],
"commitConvention": "none"
diff --git a/README.md b/README.md
index eb72d8a9552b..13bd0c5821e7 100644
--- a/README.md
+++ b/README.md
@@ -194,6 +194,7 @@ check out our [Contributing Guide](/.github/CONTRIBUTING.md) and our
Zhen Wang 💻 📖 |
Cathal Kenneally 💻 |
+ Joel Humberto Gómez Paredes 💻 |
diff --git a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap
index 29e416cc166e..cedc2c0944cd 100644
--- a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap
+++ b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap
@@ -4311,6 +4311,7 @@ Map {
"light": false,
"locale": "en",
"open": false,
+ "selectedItems": null,
"selectionFeedback": "top-after-reopen",
"sortItems": [Function],
"title": false,
@@ -4507,6 +4508,9 @@ Map {
"open": Object {
"type": "bool",
},
+ "selectedItems": Object {
+ "type": "array",
+ },
"selectionFeedback": Object {
"args": Array [
Array [
diff --git a/packages/react/src/components/MultiSelect/MultiSelect-story.js b/packages/react/src/components/MultiSelect/MultiSelect-story.js
index 5872c25c574d..146ce9a30430 100644
--- a/packages/react/src/components/MultiSelect/MultiSelect-story.js
+++ b/packages/react/src/components/MultiSelect/MultiSelect-story.js
@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
-import React, { useState } from 'react';
+import React, { useCallback, useState } from 'react';
import { action } from '@storybook/addon-actions';
import {
withKnobs,
@@ -20,6 +20,7 @@ import MultiSelect from '../MultiSelect';
import FilterableMultiSelect from '../MultiSelect/FilterableMultiSelect';
import Checkbox from '../Checkbox';
import mdx from './MultiSelect.mdx';
+import Button from '../Button';
const items = [
{
@@ -143,6 +144,44 @@ export const Default = withReadme(readme, () => {
);
});
+export const controlled = withReadme(readme, () => {
+ const {
+ listBoxMenuIconTranslationIds,
+ selectionFeedback,
+ ...multiSelectProps
+ } = props();
+ const [selectedItems, setSelectedItems] = useState([]);
+ const onChange = useCallback(({ selectedItems: newSelectedItems }) => {
+ setSelectedItems(newSelectedItems);
+ }, []);
+
+ return (
+
+ (item ? item.text : '')}
+ translateWithId={(id) => listBoxMenuIconTranslationIds[id]}
+ selectionFeedback={selectionFeedback}
+ onChange={onChange}
+ selectedItems={selectedItems}
+ />
+
+
+
+ );
+});
+
export const ItemToElement = withReadme(readme, () => {
return (
diff --git a/packages/react/src/components/MultiSelect/MultiSelect.js b/packages/react/src/components/MultiSelect/MultiSelect.js
index c9f0e96d6cd7..42e85a25c686 100644
--- a/packages/react/src/components/MultiSelect/MultiSelect.js
+++ b/packages/react/src/components/MultiSelect/MultiSelect.js
@@ -68,6 +68,7 @@ const MultiSelect = React.forwardRef(function MultiSelect(
onChange,
onMenuChange,
direction,
+ selectedItems: selected,
},
ref
) {
@@ -85,6 +86,7 @@ const MultiSelect = React.forwardRef(function MultiSelect(
disabled,
initialSelectedItems,
onChange,
+ selectedItems: selected,
});
const {
@@ -431,6 +433,11 @@ MultiSelect.propTypes = {
*/
open: PropTypes.bool,
+ /**
+ * For full control of the selected items
+ */
+ selectedItems: PropTypes.array,
+
/**
* Specify feedback (mode) of the selection.
* `top`: selected item jumps to top
@@ -491,6 +498,7 @@ MultiSelect.defaultProps = {
direction: 'bottom',
clearSelectionText: 'To clear selection, press Delete or Backspace,',
clearSelectionDescription: 'Total items selected: ',
+ selectedItems: null,
};
export default MultiSelect;
diff --git a/packages/react/src/internal/Selection.js b/packages/react/src/internal/Selection.js
index a3c1a3ce576f..5995f5e9421d 100644
--- a/packages/react/src/internal/Selection.js
+++ b/packages/react/src/internal/Selection.js
@@ -9,14 +9,35 @@ import React, { useCallback, useEffect, useState, useRef } from 'react';
import PropTypes from 'prop-types';
import isEqual from 'lodash.isequal';
+function callOnChangeHandler({
+ isControlled,
+ isMounted,
+ onChangeHandlerControlled,
+ onChangeHandlerUncontrolled,
+ selectedItems,
+}) {
+ if (isControlled) {
+ if (isMounted && onChangeHandlerControlled) {
+ onChangeHandlerControlled({ selectedItems });
+ }
+ } else {
+ onChangeHandlerUncontrolled(selectedItems);
+ }
+}
+
export function useSelection({
disabled,
onChange,
initialSelectedItems = [],
+ selectedItems: controlledItems,
}) {
const isMounted = useRef(false);
const savedOnChange = useRef(onChange);
- const [selectedItems, setSelectedItems] = useState(initialSelectedItems);
+ const [uncontrolledItems, setUncontrolledItems] = useState(
+ initialSelectedItems
+ );
+ const isControlled = !!controlledItems;
+ const selectedItems = isControlled ? controlledItems : uncontrolledItems;
const onItemChange = useCallback(
(item) => {
if (disabled) {
@@ -29,35 +50,53 @@ export function useSelection({
selectedIndex = index;
}
});
-
+ let newSelectedItems;
if (selectedIndex === undefined) {
- setSelectedItems((selectedItems) => selectedItems.concat(item));
+ newSelectedItems = selectedItems.concat(item);
+ callOnChangeHandler({
+ isControlled,
+ isMounted: isMounted.current,
+ onChangeHandlerControlled: savedOnChange.current,
+ onChangeHandlerUncontrolled: setUncontrolledItems,
+ selectedItems: newSelectedItems,
+ });
return;
}
- setSelectedItems((selectedItems) =>
- removeAtIndex(selectedItems, selectedIndex)
- );
+ newSelectedItems = removeAtIndex(selectedItems, selectedIndex);
+ callOnChangeHandler({
+ isControlled,
+ isMounted: isMounted.current,
+ onChangeHandlerControlled: savedOnChange.current,
+ onChangeHandlerUncontrolled: setUncontrolledItems,
+ selectedItems: newSelectedItems,
+ });
},
- [disabled, selectedItems]
+ [disabled, isControlled, selectedItems]
);
const clearSelection = useCallback(() => {
if (disabled) {
return;
}
- setSelectedItems([]);
- }, [disabled]);
+ callOnChangeHandler({
+ isControlled,
+ isMounted: isMounted.current,
+ onChangeHandlerControlled: savedOnChange.current,
+ onChangeHandlerUncontrolled: setUncontrolledItems,
+ selectedItems: [],
+ });
+ }, [disabled, isControlled]);
useEffect(() => {
savedOnChange.current = onChange;
}, [onChange]);
useEffect(() => {
- if (isMounted.current === true && savedOnChange.current) {
+ if (isMounted.current && savedOnChange.current && !isControlled) {
savedOnChange.current({ selectedItems });
}
- }, [selectedItems]);
+ }, [isControlled, selectedItems]);
useEffect(() => {
isMounted.current = true;