diff --git a/README.md b/README.md
index 17564e5..a0464db 100644
--- a/README.md
+++ b/README.md
@@ -105,6 +105,7 @@ class Example extends Component {
| `responsiveHeight` | `string` | 400px | Responsive height of the wrapping component, can send percent for example: `70%` |
| `withGrouping` | `boolean` | false | Your items will be grouped by the group prop values - see "item grouping" section below |
| `showSelectedItemsSearch` | `boolean` | false | toggle to show search option in detination list. |
+| `keepSelectionOrder` | `boolean` | false | keep selection order in the detination list |
| `searchSelectedItemsValue` | `string` | | The value of the search field for destination list. |
| `searchSelectedItemsChanged` | `function` | | Function to handle the change of search field for destination list. Accepts value as a single argument. |
| `selectedItemsFilterFunction` | `function` | based on label | Is the same as filterFunction by default to filter items based on the search query in destination list. |
diff --git a/src/components/multi_select.js b/src/components/multi_select.js
index de411c6..28b2d7a 100644
--- a/src/components/multi_select.js
+++ b/src/components/multi_select.js
@@ -26,6 +26,7 @@ export class MultiSelect extends PureComponent {
showSearch: PropTypes.bool,
showSelectAll: PropTypes.bool,
showSelectedItems: PropTypes.bool,
+ keepSelectionOrder: PropTypes.bool,
searchIcon: PropTypes.string,
deleteIcon: PropTypes.string,
searchRenderer: PropTypes.func,
@@ -55,7 +56,8 @@ export class MultiSelect extends PureComponent {
loaderRenderer: Loader,
withGrouping: false,
generateClassName: defaultGenerateClassName,
- showSelectedItemsSearch: false
+ showSelectedItemsSearch: false,
+ keepSelectionOrder: false
};
calculateHeight() {
@@ -112,7 +114,8 @@ export class MultiSelect extends PureComponent {
searchSelectedItemsValue,
filterSelectedItems,
filteredSelectedItems,
- isLocked
+ isLocked,
+ keepSelectionOrder
} = this.props;
const calculatedHeight = this.calculateHeight();
const selectedIds = selectedItems.map(item => item.id);
@@ -151,6 +154,7 @@ export class MultiSelect extends PureComponent {
withGrouping={withGrouping}
listRenderer={listRenderer}
isLocked={isLocked}
+ keepSelectionOrder={keepSelectionOrder}
/>
)}
{!loading && showSelectedItems && (
diff --git a/src/components/multi_select_state.js b/src/components/multi_select_state.js
index d8206b3..e7f6e69 100644
--- a/src/components/multi_select_state.js
+++ b/src/components/multi_select_state.js
@@ -115,7 +115,7 @@ const withMultiSelectState = WrappedComponent =>
}
selectItem(event, id) {
- const { items } = this.props;
+ const { items, keepSelectionOrder } = this.props;
const { selectedItems, firstItemShiftSelected } = this.state;
if (!selectedItems.find(item => item.id === id)) {
if (event.shiftKey && firstItemShiftSelected !== undefined) {
@@ -125,15 +125,25 @@ const withMultiSelectState = WrappedComponent =>
const index = items.findIndex(item => item.id === id);
this.setState({ firstItemShiftSelected: index });
}
- this.setNewItemsBySelectItem(id, items, selectedItems);
+ this.setNewItemsBySelectItem(
+ id,
+ items,
+ selectedItems,
+ keepSelectionOrder
+ );
}
} else {
this.unselectItems([id]);
}
}
- setNewItemsBySelectItem(id, items, selectedItems) {
- const newSelectedItems = getNewSelectedItems(id, items, selectedItems);
+ setNewItemsBySelectItem(id, items, selectedItems, keepSelectionOrder) {
+ const newSelectedItems = getNewSelectedItems(
+ id,
+ items,
+ selectedItems,
+ keepSelectionOrder
+ );
const newFilteredSelectedItems = this.getNewFilteredSelectedItems(
newSelectedItems
);
diff --git a/src/components/multi_select_state_utils.js b/src/components/multi_select_state_utils.js
index 56f9f2d..13bb808 100644
--- a/src/components/multi_select_state_utils.js
+++ b/src/components/multi_select_state_utils.js
@@ -40,12 +40,26 @@ export const getMinMaxIndexes = (currentIndex, firstItemShiftSelected) =>
export const isWithin = (index, { minIndex, maxIndex }) =>
index >= minIndex && index <= maxIndex;
-export const getNewSelectedItems = (itemId, items, selectedItems) => {
- const sourceItems = items.filter(
- item => item.id === itemId || findItem(item, selectedItems)
- );
- const destinationItems = selectedItems.filter(
- selectedItem => !findItem(selectedItem, items)
- );
- return [...destinationItems, ...sourceItems];
+export const getNewSelectedItems = (
+ itemId,
+ items,
+ selectedItems,
+ keepSelectionOrder
+) => {
+ let alreadySelectedItems = [];
+ let sourceItems = [];
+ let destinationItems = [];
+
+ if (keepSelectionOrder) {
+ // In order to keep selection order on the list,
+ // First, iterate threw already selectedItems
+ alreadySelectedItems = selectedItems.filter(item => findItem(item, items));
+ sourceItems = items.filter(item => item.id === itemId);
+ } else {
+ sourceItems = items.filter(
+ item => item.id === itemId || findItem(item, selectedItems)
+ );
+ }
+
+ return [...alreadySelectedItems, ...sourceItems];
};
diff --git a/stories/multi-select.stories.js b/stories/multi-select.stories.js
index 458db74..c4d2755 100644
--- a/stories/multi-select.stories.js
+++ b/stories/multi-select.stories.js
@@ -80,6 +80,21 @@ storiesOf("React Multi Select", module)
);
})
)
+ .add(
+ "with keep selection order",
+ withReadme(Readme, () => {
+ return (
+
+ );
+ })
+ )
.add(
"With some of the items disabled",
withReadme(Readme, () => {
@@ -363,15 +378,12 @@ storiesOf("React Multi Select", module)
withReadme(Readme, () => {
class SelectedItemsController extends React.Component {
SINGLE_ITEM = [{ id: 1, label: "Item 1" }];
- MULTI_ITEMS = [
- { id: 2, label: "Item 2" },
- { id: 4, label: "Item 4" }
- ]
+ MULTI_ITEMS = [{ id: 2, label: "Item 2" }, { id: 4, label: "Item 4" }];
constructor(props) {
super(props);
this.state = {
- selectedItems: this.SINGLE_ITEM,
+ selectedItems: this.SINGLE_ITEM
};
}
@@ -384,7 +396,10 @@ storiesOf("React Multi Select", module)
type="button"
onClick={() => {
this.setState({
- selectedItems: this.state.selectedItems.length > 1 ? this.SINGLE_ITEM : this.MULTI_ITEMS
+ selectedItems:
+ this.state.selectedItems.length > 1
+ ? this.SINGLE_ITEM
+ : this.MULTI_ITEMS
});
}}
/>
diff --git a/tests/components/__snapshots__/multi_select.spec.js.snap b/tests/components/__snapshots__/multi_select.spec.js.snap
index 3966e85..f43d5a3 100644
--- a/tests/components/__snapshots__/multi_select.spec.js.snap
+++ b/tests/components/__snapshots__/multi_select.spec.js.snap
@@ -134,6 +134,7 @@ exports[`MultiSelect Snapshots can remove search 1`] = `
isLocked={undefined}
itemHeight={40}
itemRenderer={undefined}
+ keepSelectionOrder={false}
listRenderer={undefined}
messages={Object {}}
noItemsRenderer={undefined}
@@ -305,6 +306,7 @@ exports[`MultiSelect Snapshots can remove select all 1`] = `
isLocked={undefined}
itemHeight={40}
itemRenderer={undefined}
+ keepSelectionOrder={false}
listRenderer={undefined}
messages={Object {}}
noItemsRenderer={undefined}
@@ -476,6 +478,7 @@ exports[`MultiSelect Snapshots can remove selected items 1`] = `
isLocked={undefined}
itemHeight={40}
itemRenderer={undefined}
+ keepSelectionOrder={false}
listRenderer={undefined}
messages={Object {}}
noItemsRenderer={undefined}
@@ -629,6 +632,7 @@ exports[`MultiSelect Snapshots custom itemRenderer 1`] = `
isLocked={undefined}
itemHeight={40}
itemRenderer={[MockFunction mockedComponent]}
+ keepSelectionOrder={false}
listRenderer={undefined}
messages={Object {}}
noItemsRenderer={undefined}
@@ -800,6 +804,7 @@ exports[`MultiSelect Snapshots custom listRenderer 1`] = `
isLocked={undefined}
itemHeight={40}
itemRenderer={undefined}
+ keepSelectionOrder={false}
listRenderer={[MockFunction mockedComponent]}
messages={Object {}}
noItemsRenderer={undefined}
@@ -971,6 +976,7 @@ exports[`MultiSelect Snapshots custom messages 1`] = `
isLocked={undefined}
itemHeight={40}
itemRenderer={undefined}
+ keepSelectionOrder={false}
listRenderer={undefined}
messages={
Object {
@@ -1160,6 +1166,7 @@ exports[`MultiSelect Snapshots custom noItemsRenderer 1`] = `
isLocked={undefined}
itemHeight={40}
itemRenderer={undefined}
+ keepSelectionOrder={false}
listRenderer={undefined}
messages={Object {}}
noItemsRenderer={[MockFunction mockedComponent]}
@@ -1331,6 +1338,7 @@ exports[`MultiSelect Snapshots custom searchRenderer 1`] = `
isLocked={undefined}
itemHeight={40}
itemRenderer={undefined}
+ keepSelectionOrder={false}
listRenderer={undefined}
messages={Object {}}
noItemsRenderer={undefined}
@@ -1502,6 +1510,7 @@ exports[`MultiSelect Snapshots custom selectAllRenderer 1`] = `
isLocked={undefined}
itemHeight={40}
itemRenderer={undefined}
+ keepSelectionOrder={false}
listRenderer={undefined}
messages={Object {}}
noItemsRenderer={undefined}
@@ -1673,6 +1682,7 @@ exports[`MultiSelect Snapshots default snapshot 1`] = `
isLocked={undefined}
itemHeight={40}
itemRenderer={undefined}
+ keepSelectionOrder={false}
listRenderer={undefined}
messages={Object {}}
noItemsRenderer={undefined}
@@ -2102,6 +2112,7 @@ exports[`MultiSelect Snapshots does not pass disabled if maxSelectedItem has pas
isLocked={undefined}
itemHeight={40}
itemRenderer={undefined}
+ keepSelectionOrder={false}
listRenderer={undefined}
messages={Object {}}
noItemsRenderer={undefined}
@@ -2283,6 +2294,7 @@ exports[`MultiSelect Snapshots passed clearAll 1`] = `
isLocked={undefined}
itemHeight={40}
itemRenderer={undefined}
+ keepSelectionOrder={false}
listRenderer={undefined}
messages={Object {}}
noItemsRenderer={undefined}
@@ -2454,6 +2466,7 @@ exports[`MultiSelect Snapshots passed filterItems 1`] = `
isLocked={undefined}
itemHeight={40}
itemRenderer={undefined}
+ keepSelectionOrder={false}
listRenderer={undefined}
messages={Object {}}
noItemsRenderer={undefined}
@@ -2625,6 +2638,7 @@ exports[`MultiSelect Snapshots passed selectAllItems 1`] = `
isLocked={undefined}
itemHeight={40}
itemRenderer={undefined}
+ keepSelectionOrder={false}
listRenderer={undefined}
messages={Object {}}
noItemsRenderer={undefined}
@@ -2796,6 +2810,7 @@ exports[`MultiSelect Snapshots passed selectItem 1`] = `
isLocked={undefined}
itemHeight={40}
itemRenderer={undefined}
+ keepSelectionOrder={false}
listRenderer={undefined}
messages={Object {}}
noItemsRenderer={undefined}
@@ -2967,6 +2982,7 @@ exports[`MultiSelect Snapshots passed selectedIds 1`] = `
isLocked={undefined}
itemHeight={40}
itemRenderer={undefined}
+ keepSelectionOrder={false}
listRenderer={undefined}
messages={Object {}}
noItemsRenderer={undefined}
@@ -3138,6 +3154,7 @@ exports[`MultiSelect Snapshots passed selectedItems 1`] = `
isLocked={undefined}
itemHeight={40}
itemRenderer={undefined}
+ keepSelectionOrder={false}
listRenderer={undefined}
messages={Object {}}
noItemsRenderer={undefined}
@@ -3319,6 +3336,7 @@ exports[`MultiSelect Snapshots passed unselectItems 1`] = `
isLocked={undefined}
itemHeight={40}
itemRenderer={undefined}
+ keepSelectionOrder={false}
listRenderer={undefined}
messages={Object {}}
noItemsRenderer={undefined}
@@ -3490,6 +3508,7 @@ exports[`MultiSelect Snapshots passes disabled if maxSelectedItem has passed 1`]
isLocked={undefined}
itemHeight={40}
itemRenderer={undefined}
+ keepSelectionOrder={false}
listRenderer={undefined}
messages={Object {}}
noItemsRenderer={undefined}
@@ -3671,6 +3690,7 @@ exports[`MultiSelect Snapshots will pass itemHeight 1`] = `
isLocked={undefined}
itemHeight={60}
itemRenderer={undefined}
+ keepSelectionOrder={false}
listRenderer={undefined}
messages={Object {}}
noItemsRenderer={undefined}
@@ -3842,6 +3862,7 @@ exports[`MultiSelect Snapshots will pass selectAllHeight 1`] = `
isLocked={undefined}
itemHeight={60}
itemRenderer={undefined}
+ keepSelectionOrder={false}
listRenderer={undefined}
messages={Object {}}
noItemsRenderer={undefined}
@@ -4013,6 +4034,7 @@ exports[`MultiSelect Snapshots will pass selectAllHeight without itemHeight 1`]
isLocked={undefined}
itemHeight={40}
itemRenderer={undefined}
+ keepSelectionOrder={false}
listRenderer={undefined}
messages={Object {}}
noItemsRenderer={undefined}
@@ -4184,6 +4206,7 @@ exports[`MultiSelect Snapshots will pass selectedItemHeight 1`] = `
isLocked={undefined}
itemHeight={40}
itemRenderer={undefined}
+ keepSelectionOrder={false}
listRenderer={undefined}
messages={Object {}}
noItemsRenderer={undefined}
@@ -4355,6 +4378,7 @@ exports[`MultiSelect Snapshots with generateClassName 1`] = `
isLocked={undefined}
itemHeight={40}
itemRenderer={undefined}
+ keepSelectionOrder={false}
listRenderer={undefined}
messages={Object {}}
noItemsRenderer={undefined}
@@ -4439,6 +4463,7 @@ Object {
"isLocked": undefined,
"itemHeight": 40,
"itemRenderer": [Function],
+ "keepSelectionOrder": false,
"listRenderer": [Function],
"messages": Object {},
"noItemsRenderer": [Function],
@@ -4468,6 +4493,7 @@ Object {
"isLocked": undefined,
"itemHeight": 40,
"itemRenderer": [Function],
+ "keepSelectionOrder": false,
"listRenderer": [Function],
"messages": Object {},
"noItemsRenderer": [Function],
@@ -4497,6 +4523,7 @@ Object {
"isLocked": undefined,
"itemHeight": 40,
"itemRenderer": [Function],
+ "keepSelectionOrder": false,
"listRenderer": [Function],
"messages": Object {},
"noItemsRenderer": [Function],
@@ -4526,6 +4553,7 @@ Object {
"isLocked": undefined,
"itemHeight": 40,
"itemRenderer": [MockFunction mockedComponent],
+ "keepSelectionOrder": false,
"listRenderer": [Function],
"messages": Object {},
"noItemsRenderer": [Function],
@@ -4555,6 +4583,7 @@ Object {
"isLocked": undefined,
"itemHeight": 40,
"itemRenderer": [Function],
+ "keepSelectionOrder": false,
"listRenderer": [MockFunction mockedComponent],
"messages": Object {},
"noItemsRenderer": [Function],
@@ -4584,6 +4613,7 @@ Object {
"isLocked": undefined,
"itemHeight": 40,
"itemRenderer": [Function],
+ "keepSelectionOrder": false,
"listRenderer": [Function],
"messages": Object {
"clearAllMessage": "Uncheck all",
@@ -4620,6 +4650,7 @@ Object {
"isLocked": undefined,
"itemHeight": 40,
"itemRenderer": [Function],
+ "keepSelectionOrder": false,
"listRenderer": [Function],
"messages": Object {},
"noItemsRenderer": [MockFunction mockedComponent],
@@ -4649,6 +4680,7 @@ Object {
"isLocked": undefined,
"itemHeight": 40,
"itemRenderer": [Function],
+ "keepSelectionOrder": false,
"listRenderer": [Function],
"messages": Object {},
"noItemsRenderer": [Function],
@@ -4678,6 +4710,7 @@ Object {
"isLocked": undefined,
"itemHeight": 40,
"itemRenderer": [Function],
+ "keepSelectionOrder": false,
"listRenderer": [Function],
"messages": Object {},
"noItemsRenderer": [Function],
@@ -4707,6 +4740,7 @@ Object {
"isLocked": undefined,
"itemHeight": 40,
"itemRenderer": [Function],
+ "keepSelectionOrder": false,
"listRenderer": [Function],
"messages": Object {},
"noItemsRenderer": [Function],
@@ -4736,6 +4770,7 @@ Object {
"isLocked": undefined,
"itemHeight": 40,
"itemRenderer": [Function],
+ "keepSelectionOrder": false,
"listRenderer": [Function],
"messages": Object {},
"noItemsRenderer": [Function],
@@ -4768,6 +4803,7 @@ Object {
"isLocked": undefined,
"itemHeight": 40,
"itemRenderer": [Function],
+ "keepSelectionOrder": false,
"listRenderer": [Function],
"messages": Object {},
"noItemsRenderer": [Function],
@@ -4797,6 +4833,7 @@ Object {
"isLocked": undefined,
"itemHeight": 40,
"itemRenderer": [Function],
+ "keepSelectionOrder": false,
"listRenderer": [Function],
"messages": Object {},
"noItemsRenderer": [Function],
@@ -4826,6 +4863,7 @@ Object {
"isLocked": undefined,
"itemHeight": 40,
"itemRenderer": [Function],
+ "keepSelectionOrder": false,
"listRenderer": [Function],
"messages": Object {},
"noItemsRenderer": [Function],
@@ -4855,6 +4893,7 @@ Object {
"isLocked": undefined,
"itemHeight": 40,
"itemRenderer": [Function],
+ "keepSelectionOrder": false,
"listRenderer": [Function],
"messages": Object {},
"noItemsRenderer": [Function],
@@ -4884,6 +4923,7 @@ Object {
"isLocked": undefined,
"itemHeight": 40,
"itemRenderer": [Function],
+ "keepSelectionOrder": false,
"listRenderer": [Function],
"messages": Object {},
"noItemsRenderer": [Function],
@@ -4913,6 +4953,7 @@ Object {
"isLocked": undefined,
"itemHeight": 40,
"itemRenderer": [Function],
+ "keepSelectionOrder": false,
"listRenderer": [Function],
"messages": Object {},
"noItemsRenderer": [Function],
@@ -4945,6 +4986,7 @@ Object {
"isLocked": undefined,
"itemHeight": 40,
"itemRenderer": [Function],
+ "keepSelectionOrder": false,
"listRenderer": [Function],
"messages": Object {},
"noItemsRenderer": [Function],
@@ -4974,6 +5016,7 @@ Object {
"isLocked": undefined,
"itemHeight": 40,
"itemRenderer": [Function],
+ "keepSelectionOrder": false,
"listRenderer": [Function],
"messages": Object {},
"noItemsRenderer": [Function],
@@ -5006,6 +5049,7 @@ Object {
"isLocked": undefined,
"itemHeight": 60,
"itemRenderer": [Function],
+ "keepSelectionOrder": false,
"listRenderer": [Function],
"messages": Object {},
"noItemsRenderer": [Function],
@@ -5035,6 +5079,7 @@ Object {
"isLocked": undefined,
"itemHeight": 60,
"itemRenderer": [Function],
+ "keepSelectionOrder": false,
"listRenderer": [Function],
"messages": Object {},
"noItemsRenderer": [Function],
@@ -5064,6 +5109,7 @@ Object {
"isLocked": undefined,
"itemHeight": 40,
"itemRenderer": [Function],
+ "keepSelectionOrder": false,
"listRenderer": [Function],
"messages": Object {},
"noItemsRenderer": [Function],
@@ -5093,6 +5139,7 @@ Object {
"isLocked": undefined,
"itemHeight": 40,
"itemRenderer": [Function],
+ "keepSelectionOrder": false,
"listRenderer": [Function],
"messages": Object {},
"noItemsRenderer": [Function],
diff --git a/tests/components/multi_select_state.spec.js b/tests/components/multi_select_state.spec.js
index a9a18fc..ad97047 100644
--- a/tests/components/multi_select_state.spec.js
+++ b/tests/components/multi_select_state.spec.js
@@ -17,6 +17,7 @@ const EVENT_WITH_SHIFT = { keyCode: 16, shiftKey: true };
const EVENT_WITH_CTRL = { keyCode: 17, shiftKey: true };
const items = [ITEM_1, ITEM_2, ITEM_3];
+const manyItems = [ITEM_1, ITEM_2, ITEM_3, ITEM_4, ITEM_12, ITEM_22];
const itemsWithDisabled = [ITEM_1, ITEM_2, DISABLED_ITEM_23, ITEM_3];
describe("withMultiSelectState", () => {
@@ -179,7 +180,58 @@ describe("withMultiSelectState", () => {
wrapper.update();
wrapper.props().selectItem(EVENT, ITEM_1.id);
wrapper.update();
- expect(wrapper.prop("selectedItems")).toEqual([ITEM_1, ITEM_2]);
+ wrapper.props().selectItem(EVENT, ITEM_3.id);
+ wrapper.update();
+ expect(wrapper.prop("selectedItems")).toEqual([ITEM_1, ITEM_2, ITEM_3]);
+ });
+
+ test("keep selection order", () => {
+ const ConditionalComponent = withMultiSelectState(CustomComponent);
+ const wrapper = shallow(
+
+ );
+ wrapper.props().selectItem(EVENT, ITEM_2.id);
+ wrapper.update();
+ wrapper.props().selectItem(EVENT, ITEM_1.id);
+ wrapper.update();
+ wrapper.props().selectItem(EVENT, ITEM_3.id);
+ wrapper.update();
+ wrapper.props().selectItem(EVENT, ITEM_22.id);
+ wrapper.update();
+ wrapper.props().selectItem(EVENT, ITEM_12.id);
+ wrapper.update();
+ expect(wrapper.prop("selectedItems")).toEqual([
+ ITEM_2,
+ ITEM_1,
+ ITEM_3,
+ ITEM_22,
+ ITEM_12
+ ]);
+ });
+
+ test("select some items with selection order and then click on select all", () => {
+ const ConditionalComponent = withMultiSelectState(CustomComponent);
+ const wrapper = shallow(
+
+ );
+ wrapper.props().selectItem(EVENT, ITEM_2.id);
+ wrapper.update();
+ wrapper.props().selectItem(EVENT, ITEM_1.id);
+ wrapper.update();
+ wrapper.props().selectItem(EVENT, ITEM_22.id);
+ wrapper.update();
+ wrapper.props().selectItem(EVENT, ITEM_3.id);
+ wrapper.update();
+ wrapper.props().selectAllItems();
+ wrapper.update();
+ expect(wrapper.prop("selectedItems")).toEqual([
+ ITEM_1,
+ ITEM_2,
+ ITEM_3,
+ ITEM_4,
+ ITEM_12,
+ ITEM_22
+ ]);
});
test("can filter items", () => {