From 9a4c39fc0f7486175194616163f96998bcbb666f Mon Sep 17 00:00:00 2001 From: Igor Babiy Date: Wed, 10 Jul 2019 14:00:58 +0300 Subject: [PATCH 1/3] Issue #149 - It is possible to select more than maxSelectedItems when using Shift --- src/components/multi_select_state.js | 26 ++++++++-- src/components/multi_select_state_utils.js | 44 ++++++++++++++++ tests/components/multi_select_state.spec.js | 36 +++++++++++++ .../multi_select_state_utils.spec.js | 52 ++++++++++++++++++- 4 files changed, 153 insertions(+), 5 deletions(-) diff --git a/src/components/multi_select_state.js b/src/components/multi_select_state.js index 5b8d513..3f64406 100644 --- a/src/components/multi_select_state.js +++ b/src/components/multi_select_state.js @@ -1,6 +1,8 @@ import React, { PureComponent } from "react"; import { getSelectedByAllItems, + getSelectedItemsOutsideInterval, + getAvailableIntervalForSelection, filterUnselectedByIds, findItem, getMinMaxIndexes, @@ -66,16 +68,32 @@ 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 initialInterval = getMinMaxIndexes(index, firstItemShiftSelected); + const outsideIntervalSelectedItems = getSelectedItemsOutsideInterval( + items, + selectedItems, + initialInterval + ); + const interval = getAvailableIntervalForSelection( + initialInterval, + outsideIntervalSelectedItems.length, + maxSelectedItems, + firstItemShiftSelected + ); - 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, outsideIntervalSelectedItems) ); 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..f2e6388 100644 --- a/src/components/multi_select_state_utils.js +++ b/src/components/multi_select_state_utils.js @@ -37,6 +37,50 @@ export const getMinMaxIndexes = (currentIndex, firstItemShiftSelected) => ? { minIndex: currentIndex, maxIndex: firstItemShiftSelected } : { minIndex: firstItemShiftSelected, maxIndex: currentIndex }; +export const getAvailableIntervalForSelection = ( + interval, + outsideSelectedItems, + maxSelectedItems, + firstItemShiftSelected +) => { + const { minIndex, maxIndex } = interval; + const shouldBeSelect = outsideSelectedItems + (maxIndex - minIndex); + return maxSelectedItems > 0 && maxSelectedItems <= shouldBeSelect + ? getFixedInterval( + interval, + outsideSelectedItems, + maxSelectedItems, + firstItemShiftSelected + ) + : interval; +}; + +const getFixedInterval = ( + { minIndex, maxIndex }, + outsideSelectedItems, + maxSelectedItems, + firstItemShiftSelected +) => ({ + minIndex: + maxIndex === firstItemShiftSelected + ? maxIndex - (maxSelectedItems - outsideSelectedItems - 1) + : minIndex, + maxIndex: + minIndex === firstItemShiftSelected + ? minIndex + (maxSelectedItems - outsideSelectedItems - 1) + : maxIndex +}); + +export const getSelectedItemsOutsideInterval = ( + sourceItems, + selectedItems, + interval +) => + 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 }); + }); }); From 4667207bf1af9f94e1b1da2edeaab2a8e4f3c1a1 Mon Sep 17 00:00:00 2001 From: Igor Babiy Date: Wed, 7 Aug 2019 17:50:22 +0300 Subject: [PATCH 2/3] Fix review issues --- src/components/multi_select_state.js | 20 +++--- src/components/multi_select_state_utils.js | 78 +++++++++++----------- 2 files changed, 49 insertions(+), 49 deletions(-) diff --git a/src/components/multi_select_state.js b/src/components/multi_select_state.js index 3f64406..1b8c32e 100644 --- a/src/components/multi_select_state.js +++ b/src/components/multi_select_state.js @@ -2,7 +2,6 @@ import React, { PureComponent } from "react"; import { getSelectedByAllItems, getSelectedItemsOutsideInterval, - getAvailableIntervalForSelection, filterUnselectedByIds, findItem, getMinMaxIndexes, @@ -75,17 +74,18 @@ const withMultiSelectState = WrappedComponent => selectedItems } = this.state; - const initialInterval = getMinMaxIndexes(index, firstItemShiftSelected); - const outsideIntervalSelectedItems = getSelectedItemsOutsideInterval( + const interval = getMinMaxIndexes( + index, + firstItemShiftSelected, items, selectedItems, - initialInterval + maxSelectedItems ); - const interval = getAvailableIntervalForSelection( - initialInterval, - outsideIntervalSelectedItems.length, - maxSelectedItems, - firstItemShiftSelected + const outsideSelectedItems = getSelectedItemsOutsideInterval( + index, + firstItemShiftSelected, + items, + selectedItems ); const newSelectedItems = items.filter( @@ -93,7 +93,7 @@ const withMultiSelectState = WrappedComponent => (isWithin(index, interval) && !isLocked(item) && findItem(item, filteredItems)) || - findItem(item, outsideIntervalSelectedItems) + 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 f2e6388..b62ef66 100644 --- a/src/components/multi_select_state_utils.js +++ b/src/components/multi_select_state_utils.js @@ -32,54 +32,54 @@ export const getSelectedByAllItems = (itemsToSelect, selectedItems, items) => { return [...destinationItems, ...sourceItems]; }; -export const getMinMaxIndexes = (currentIndex, firstItemShiftSelected) => - firstItemShiftSelected > currentIndex - ? { minIndex: currentIndex, maxIndex: firstItemShiftSelected } - : { minIndex: firstItemShiftSelected, maxIndex: currentIndex }; - -export const getAvailableIntervalForSelection = ( - interval, - outsideSelectedItems, - maxSelectedItems, - firstItemShiftSelected +export const getMinMaxIndexes = ( + currentIndex, + firstItemShiftSelected, + items, + selectedItems, + maxSelected ) => { - const { minIndex, maxIndex } = interval; - const shouldBeSelect = outsideSelectedItems + (maxIndex - minIndex); - return maxSelectedItems > 0 && maxSelectedItems <= shouldBeSelect - ? getFixedInterval( - interval, - outsideSelectedItems, - maxSelectedItems, - firstItemShiftSelected - ) - : interval; + const outsideSelected = getSelectedItemsOutsideInterval( + currentIndex, + firstItemShiftSelected, + items, + selectedItems + ).length; + const shouldBeSelect = + outsideSelected + Math.abs(firstItemShiftSelected - currentIndex) + 1; + + if (maxSelected && maxSelected <= shouldBeSelect) { + const availableToSelect = maxSelected - outsideSelected - 1; + return firstItemShiftSelected > currentIndex + ? { + minIndex: firstItemShiftSelected - availableToSelect, + maxIndex: firstItemShiftSelected + } + : { + minIndex: firstItemShiftSelected, + maxIndex: firstItemShiftSelected + availableToSelect + }; + } + return getInputInterval(currentIndex, firstItemShiftSelected); }; -const getFixedInterval = ( - { minIndex, maxIndex }, - outsideSelectedItems, - maxSelectedItems, - firstItemShiftSelected -) => ({ - minIndex: - maxIndex === firstItemShiftSelected - ? maxIndex - (maxSelectedItems - outsideSelectedItems - 1) - : minIndex, - maxIndex: - minIndex === firstItemShiftSelected - ? minIndex + (maxSelectedItems - outsideSelectedItems - 1) - : maxIndex -}); +const getInputInterval = (currentIndex, firstItemShiftSelected) => + firstItemShiftSelected > currentIndex + ? { minIndex: currentIndex, maxIndex: firstItemShiftSelected } + : { minIndex: firstItemShiftSelected, maxIndex: currentIndex }; export const getSelectedItemsOutsideInterval = ( + currentIndex, + firstItemShiftSelected, sourceItems, - selectedItems, - interval -) => - selectedItems.filter(selectedItem => { + 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; From ebee5577c58f5ffc4fd3a61b302ec2adf57039c5 Mon Sep 17 00:00:00 2001 From: Igor Babiy Date: Thu, 8 Aug 2019 10:50:51 +0300 Subject: [PATCH 3/3] Replace names on more meaningful --- src/components/multi_select_state_utils.js | 44 +++++++++++++++------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/src/components/multi_select_state_utils.js b/src/components/multi_select_state_utils.js index b62ef66..34b4dc7 100644 --- a/src/components/multi_select_state_utils.js +++ b/src/components/multi_select_state_utils.js @@ -39,30 +39,46 @@ export const getMinMaxIndexes = ( selectedItems, maxSelected ) => { - const outsideSelected = getSelectedItemsOutsideInterval( + const numberOfItemsOutsideSelected = getSelectedItemsOutsideInterval( currentIndex, firstItemShiftSelected, items, selectedItems ).length; - const shouldBeSelect = - outsideSelected + Math.abs(firstItemShiftSelected - currentIndex) + 1; + const sumItemsShouldBeSelect = + numberOfItemsOutsideSelected + + Math.abs(firstItemShiftSelected - currentIndex) + + 1; - if (maxSelected && maxSelected <= shouldBeSelect) { - const availableToSelect = maxSelected - outsideSelected - 1; - return firstItemShiftSelected > currentIndex - ? { - minIndex: firstItemShiftSelected - availableToSelect, - maxIndex: firstItemShiftSelected - } - : { - minIndex: firstItemShiftSelected, - maxIndex: firstItemShiftSelected + availableToSelect - }; + 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 }