diff --git a/src/components/multi_select_state.js b/src/components/multi_select_state.js index d8206b3..afe6013 100644 --- a/src/components/multi_select_state.js +++ b/src/components/multi_select_state.js @@ -1,6 +1,7 @@ import React, { PureComponent } from "react"; import { getSelectedByAllItems, + getSelectedItemsOutsideInterval, filterUnselectedByIds, findItem, getMinMaxIndexes, @@ -69,16 +70,33 @@ const withMultiSelectState = WrappedComponent => } handleMultiSelection(index) { - const { items, isLocked } = this.props; - const { filteredItems, firstItemShiftSelected } = this.state; + const { items, isLocked, maxSelectedItems } = this.props; + const { + filteredItems, + firstItemShiftSelected, + selectedItems + } = this.state; + + const interval = getMinMaxIndexes( + index, + firstItemShiftSelected, + items, + selectedItems, + maxSelectedItems + ); + const outsideSelectedItems = getSelectedItemsOutsideInterval( + index, + firstItemShiftSelected, + items, + selectedItems + ); - const interval = getMinMaxIndexes(index, firstItemShiftSelected); const newSelectedItems = items.filter( (item, index) => (isWithin(index, interval) && !isLocked(item) && findItem(item, filteredItems)) || - findItem(item, this.state.selectedItems) + findItem(item, outsideSelectedItems) ); 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..34b4dc7 100644 --- a/src/components/multi_select_state_utils.js +++ b/src/components/multi_select_state_utils.js @@ -32,11 +32,71 @@ export const getSelectedByAllItems = (itemsToSelect, selectedItems, items) => { return [...destinationItems, ...sourceItems]; }; -export const getMinMaxIndexes = (currentIndex, firstItemShiftSelected) => +export const getMinMaxIndexes = ( + currentIndex, + firstItemShiftSelected, + items, + selectedItems, + maxSelected +) => { + const numberOfItemsOutsideSelected = getSelectedItemsOutsideInterval( + currentIndex, + firstItemShiftSelected, + items, + selectedItems + ).length; + const sumItemsShouldBeSelect = + numberOfItemsOutsideSelected + + Math.abs(firstItemShiftSelected - currentIndex) + + 1; + + if (maxSelected && maxSelected <= sumItemsShouldBeSelect) { + return getIndexesWhenShiftSelected( + currentIndex, + firstItemShiftSelected, + maxSelected, + numberOfItemsOutsideSelected + ); + } + return getInputInterval(currentIndex, firstItemShiftSelected); +}; + +const getIndexesWhenShiftSelected = ( + currentIndex, + firstItemShiftSelected, + maxSelected, + numberOfItemsOutsideSelected +) => { + const sumItemsAllowToSelect = maxSelected - numberOfItemsOutsideSelected - 1; + return firstItemShiftSelected > currentIndex + ? { + minIndex: firstItemShiftSelected - sumItemsAllowToSelect, + maxIndex: firstItemShiftSelected + } + : { + minIndex: firstItemShiftSelected, + maxIndex: firstItemShiftSelected + sumItemsAllowToSelect + }; +}; + +const getInputInterval = (currentIndex, firstItemShiftSelected) => firstItemShiftSelected > currentIndex ? { minIndex: currentIndex, maxIndex: firstItemShiftSelected } : { minIndex: firstItemShiftSelected, maxIndex: currentIndex }; +export const getSelectedItemsOutsideInterval = ( + currentIndex, + firstItemShiftSelected, + sourceItems, + selectedItems +) => { + const interval = getInputInterval(currentIndex, firstItemShiftSelected); + return selectedItems.filter(selectedItem => { + const index = sourceItems.findIndex(item => item.id === selectedItem.id); + return !isWithin(index, interval) || selectedItem.disabled; + }); +}; + export const isWithin = (index, { minIndex, maxIndex }) => index >= minIndex && index <= maxIndex; diff --git a/tests/components/multi_select_state.spec.js b/tests/components/multi_select_state.spec.js index a9a18fc..b2a5188 100644 --- a/tests/components/multi_select_state.spec.js +++ b/tests/components/multi_select_state.spec.js @@ -715,4 +715,40 @@ describe("withMultiSelectState", () => { items[4] ]); }); + + test("case select with shift items where shouldBeSelect > maxSelectedItems", () => { + const ConditionalComponent = withMultiSelectState(CustomComponent); + const items = [ITEM_1, ITEM_2, ITEM_3, ITEM_4, ITEM_12]; + + const wrapper = shallow( + + ); + wrapper.props().selectItem(EVENT_WITH_SHIFT, ITEM_12.id); + wrapper.update(); + wrapper.props().selectItem(EVENT_WITH_SHIFT, ITEM_3.id); + wrapper.update(); + expect(wrapper.prop("selectedItems")).toEqual([ITEM_1, ITEM_4, ITEM_12]); + }); + + test("case select with shift items where shouldBeSelect < maxSelectedItems", () => { + const ConditionalComponent = withMultiSelectState(CustomComponent); + const items = [ITEM_1, ITEM_2, ITEM_3, ITEM_4]; + + const wrapper = shallow( + + ); + wrapper.props().selectItem(EVENT_WITH_SHIFT, ITEM_1.id); + wrapper.update(); + wrapper.props().selectItem(EVENT_WITH_SHIFT, ITEM_3.id); + wrapper.update(); + expect(wrapper.prop("selectedItems")).toEqual([ITEM_1, ITEM_2, ITEM_3]); + }); }); diff --git a/tests/components/multi_select_state_utils.spec.js b/tests/components/multi_select_state_utils.spec.js index 66f6905..e1f2a83 100644 --- a/tests/components/multi_select_state_utils.spec.js +++ b/tests/components/multi_select_state_utils.spec.js @@ -1,6 +1,8 @@ import { filterUnselectedByIds, - getSelectedByAllItems + getSelectedByAllItems, + getSelectedItemsOutsideInterval, + getAvailableIntervalForSelection } from "../../src/components/multi_select_state_utils"; const items = [ @@ -76,4 +78,52 @@ describe("testing utils for multi select state", () => { ); expect(allSelectedItems).toEqual([]); }); + + test("filter selectedItems that not in interval", () => { + const selectedItemsOutsideInterval = getSelectedItemsOutsideInterval( + items, + selectedItems, + { minIndex: 1, maxIndex: 3 } + ); + expect(selectedItemsOutsideInterval.length).toEqual(1); + expect(selectedItemsOutsideInterval[0]).toEqual(selectedItems[0]); + }); + + test("filter itemsToSelect that not in interval", () => { + const selectedItemsOutsideInterval = getSelectedItemsOutsideInterval( + items, + itemsToSelect, + { minIndex: 0, maxIndex: 3 } + ); + expect(selectedItemsOutsideInterval.length).toEqual(0); + }); + + test("available interval when minIndex == firstItemShiftSelected", () => { + const initialInterval = { minIndex: 0, maxIndex: 3 }; + const maxSelectedItems = 3; + const selectedItemsOutsideInterval = 0; + const firstItemShiftSelected = 0; + const availableInterval = getAvailableIntervalForSelection( + initialInterval, + selectedItemsOutsideInterval, + maxSelectedItems, + firstItemShiftSelected + ); + expect(availableInterval).toEqual({ minIndex: 0, maxIndex: 2 }); + }); + + test("available interval when maxIndex == firstItemShiftSelected", () => { + const initialInterval = { minIndex: 1, maxIndex: 3 }; + const maxSelectedItems = 3; + const selectedItemsOutsideInterval = 1; + const firstItemShiftSelected = 3; + + const availableInterval = getAvailableIntervalForSelection( + initialInterval, + selectedItemsOutsideInterval, + maxSelectedItems, + firstItemShiftSelected + ); + expect(availableInterval).toEqual({ minIndex: 2, maxIndex: 3 }); + }); });