diff --git a/docs/api.md b/docs/api.md
index 43cfbc85..45d55ad4 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -248,7 +248,7 @@ It also ensures that the scroll event is propagated properly to parent ScrollVie
| `orientation` | `'horizontal' \| 'vertical'` | The orientation of the list. Defaults to `'horizontal'`. |
| `nbMaxOfItems` | `number` | The total number of expected items for infinite scroll. This helps with aligning items and is used for pagination. If not provided, it defaults to the length of the data array. |
| `scrollDuration` | `number` | The duration of a scrolling animation inside the VirtualizedList. Defaults to 200ms. |
-| `scrollBehavior` | `'stick-to-start' \| 'stick-to-end' \| 'jump-on-scroll'` | Determines the scrolling behavior. Defaults to `'stick-to-start'`. `'stick-to-start'` and `'stick-to-end'` fix the focused item at the beginning or the end of the visible items on screen. `jump-on-scroll` jumps from `numberOfItemsVisibleOnScreen` items when needed. Warning `jump-on-scroll` is not compatible with dynamic item size. |
+| `scrollBehavior` | `'stick-to-start' \| 'stick-to-center' \| 'stick-to-end' \| 'jump-on-scroll'` | Determines the scrolling behavior. Defaults to `'stick-to-start'`. `'stick-to-start'` and `'stick-to-end'` fix the focused item at the beginning or the end of the visible items on screen. `'stick-to-end'` fixes the item at the center of the screen when possible, otherwise sticking to the sides of the list instead. `jump-on-scroll` jumps from `numberOfItemsVisibleOnScreen` items when needed. Warning `jump-on-scroll` is not compatible with dynamic item size. |
| `ascendingArrow` | `ReactElement` | For web TVs cursor handling. Optional component to display as the arrow to scroll on the ascending order. |
| `ascendingArrowContainerStyle` | `ViewStyle` | For web TVs cursor handling. Style of the view which wraps the ascending arrow. Hover this view will trigger the scroll. |
| `descendingArrow` | `ReactElement` | For web TVs cursor handling. Optional component to display as the arrow to scroll on the descending order. |
diff --git a/packages/lib/src/spatial-navigation/components/virtualizedList/SpatialNavigationVirtualizedList.test.tsx b/packages/lib/src/spatial-navigation/components/virtualizedList/SpatialNavigationVirtualizedList.test.tsx
index 22c51d8d..fa24f9cc 100644
--- a/packages/lib/src/spatial-navigation/components/virtualizedList/SpatialNavigationVirtualizedList.test.tsx
+++ b/packages/lib/src/spatial-navigation/components/virtualizedList/SpatialNavigationVirtualizedList.test.tsx
@@ -325,6 +325,140 @@ describe('SpatialNavigationVirtualizedList', () => {
});
});
+ describe('stick-to-center', () => {
+ it('handles correctly stick-to-center lists', async () => {
+ const component = render(
+
+
+
+
+ ,
+ );
+ act(() => jest.runAllTimers());
+
+ // Given this layout size, this item size, and the additional items rendered parameter:
+ // - number of visible items on screen = 3
+ // - total amount of items rendered = 5
+ setComponentLayoutSize(listTestId, component, { width: 300, height: 300 });
+
+ const listElement = await component.findByTestId(listTestId);
+ expectListToHaveScroll(listElement, 0);
+ // The size of the list should be the sum of the item sizes (virtualized or not)
+ expect(listElement).toHaveStyle({ width: 1000 });
+
+ testRemoteControlManager.handleRight();
+ expectButtonToHaveFocus(component, 'button 2');
+ expectListToHaveScroll(listElement, 0);
+
+ expect(screen.getByText('button 1')).toBeTruthy();
+ expect(screen.getByText('button 5')).toBeTruthy();
+ expect(screen.queryByText('button 6')).toBeFalsy();
+
+ testRemoteControlManager.handleRight();
+ expectButtonToHaveFocus(component, 'button 3');
+ expectListToHaveScroll(listElement, -100);
+
+ expect(screen.getByText('button 1')).toBeTruthy();
+ expect(screen.getByText('button 5')).toBeTruthy();
+ expect(screen.queryByText('button 6')).toBeFalsy();
+
+ testRemoteControlManager.handleRight();
+ expectButtonToHaveFocus(component, 'button 4');
+ expectListToHaveScroll(listElement, -200);
+
+ expect(screen.queryByText('button 1')).toBeFalsy();
+ expect(screen.getByText('button 2')).toBeTruthy();
+ expect(screen.getByText('button 6')).toBeTruthy();
+ expect(screen.queryByText('button 7')).toBeFalsy();
+
+ testRemoteControlManager.handleRight();
+ expectButtonToHaveFocus(component, 'button 5');
+ expectListToHaveScroll(listElement, -300);
+
+ testRemoteControlManager.handleRight();
+ expectButtonToHaveFocus(component, 'button 6');
+ expectListToHaveScroll(listElement, -400);
+
+ testRemoteControlManager.handleRight();
+ expectButtonToHaveFocus(component, 'button 7');
+ expectListToHaveScroll(listElement, -500);
+
+ testRemoteControlManager.handleRight();
+ expectButtonToHaveFocus(component, 'button 8');
+ expectListToHaveScroll(listElement, -600);
+
+ testRemoteControlManager.handleRight();
+ expectButtonToHaveFocus(component, 'button 9');
+ expectListToHaveScroll(listElement, -700);
+
+ testRemoteControlManager.handleRight();
+ expectButtonToHaveFocus(component, 'button 10');
+ expectListToHaveScroll(listElement, -700);
+ });
+
+ it('handles correctly stick-to-center lists with elements < visible on screen', async () => {
+ const component = render(
+
+
+
+
+ ,
+ );
+ act(() => jest.runAllTimers());
+
+ setComponentLayoutSize(listTestId, component, { width: 300, height: 300 });
+
+ const listElement = await component.findByTestId(listTestId);
+ expectListToHaveScroll(listElement, 0);
+ // The size of the list should be the sum of the item sizes (virtualized or not)
+ expect(listElement).toHaveStyle({ width: 300 });
+
+ testRemoteControlManager.handleRight();
+ expectButtonToHaveFocus(component, 'button 2');
+ expectListToHaveScroll(listElement, 0);
+
+ expect(screen.queryByText('button 1')).toBeTruthy();
+ expect(screen.getByText('button 2')).toBeTruthy();
+ expect(screen.getByText('button 3')).toBeTruthy();
+
+ testRemoteControlManager.handleRight();
+ expectButtonToHaveFocus(component, 'button 3');
+ expectListToHaveScroll(listElement, 0);
+
+ expect(screen.queryByText('button 1')).toBeTruthy();
+ expect(screen.getByText('button 2')).toBeTruthy();
+ expect(screen.getByText('button 3')).toBeTruthy();
+
+ testRemoteControlManager.handleRight();
+ expectListToHaveScroll(listElement, 0);
+
+ expect(screen.queryByText('button 1')).toBeTruthy();
+ expect(screen.getByText('button 2')).toBeTruthy();
+ expect(screen.getByText('button 3')).toBeTruthy();
+
+ // We just reached the max of the list
+ testRemoteControlManager.handleRight();
+ testRemoteControlManager.handleRight();
+ testRemoteControlManager.handleRight();
+ testRemoteControlManager.handleRight();
+ expectListToHaveScroll(listElement, 0);
+ });
+ });
+
it('handles correctly RIGHT and RENDERS new elements accordingly while deleting elements that are too far from scroll when on stick to end scroll', async () => {
const component = render(
diff --git a/packages/lib/src/spatial-navigation/components/virtualizedList/VirtualizedList.tsx b/packages/lib/src/spatial-navigation/components/virtualizedList/VirtualizedList.tsx
index 398bbefd..3cdf09a8 100644
--- a/packages/lib/src/spatial-navigation/components/virtualizedList/VirtualizedList.tsx
+++ b/packages/lib/src/spatial-navigation/components/virtualizedList/VirtualizedList.tsx
@@ -12,7 +12,11 @@ import { computeAllScrollOffsets } from './helpers/createScrollOffsetArray';
import { getNumberOfItemsVisibleOnScreen } from './helpers/getNumberOfItemsVisibleOnScreen';
import { getAdditionalNumberOfItemsRendered } from './helpers/getAdditionalNumberOfItemsRendered';
-export type ScrollBehavior = 'stick-to-start' | 'stick-to-end' | 'jump-on-scroll';
+export type ScrollBehavior =
+ | 'stick-to-start'
+ | 'stick-to-center'
+ | 'stick-to-end'
+ | 'jump-on-scroll';
export interface VirtualizedListProps {
data: T[];
renderItem: (args: { item: T; index: number }) => JSX.Element;
diff --git a/packages/lib/src/spatial-navigation/components/virtualizedList/helpers/computeTranslation.test.ts b/packages/lib/src/spatial-navigation/components/virtualizedList/helpers/computeTranslation.test.ts
index 73320629..dbacf154 100644
--- a/packages/lib/src/spatial-navigation/components/virtualizedList/helpers/computeTranslation.test.ts
+++ b/packages/lib/src/spatial-navigation/components/virtualizedList/helpers/computeTranslation.test.ts
@@ -290,6 +290,138 @@ describe('computeTranslation for virtualized list with stick-to-end scroll behav
});
});
+describe('computeTranslation for virtualized list with stick-to-center scroll behavior', () => {
+ const scrollBehavior = 'stick-to-center';
+
+ it('should not scroll if currentlyFocusedItemIndex < numberOfItemsVisibleOnScreen', () => {
+ const expectedResult = computeTranslation({
+ itemSizeInPx,
+ currentlyFocusedItemIndex: 3,
+ numberOfItemsVisibleOnScreen: 4,
+ nbMaxOfItems: 11,
+ scrollBehavior: scrollBehavior,
+ data,
+ listSizeInPx: 40,
+ maxPossibleLeftAlignedIndex: 7,
+ maxPossibleRightAlignedIndex: 3,
+ });
+
+ expect(expectedResult).toEqual(-0);
+ });
+
+ it('should not scroll if currentlyFocusedItemIndex < numberOfItemsVisibleOnScreen with dynamic sizes', () => {
+ const expectedResult = computeTranslation({
+ itemSizeInPx: itemSizeInPxFunction,
+ currentlyFocusedItemIndex: 3,
+ numberOfItemsVisibleOnScreen: 4,
+ nbMaxOfItems: 11,
+ scrollBehavior: scrollBehavior,
+ data,
+ listSizeInPx: 40,
+ maxPossibleLeftAlignedIndex: 7,
+ maxPossibleRightAlignedIndex: 3,
+ });
+
+ expect(expectedResult).toEqual(-0);
+ });
+
+ it('should end-align focused item', () => {
+ const expectedResult = computeTranslation({
+ itemSizeInPx,
+ currentlyFocusedItemIndex: 6,
+ numberOfItemsVisibleOnScreen: 4,
+ nbMaxOfItems: 11,
+ scrollBehavior: scrollBehavior,
+ data,
+ listSizeInPx: 40,
+ maxPossibleLeftAlignedIndex: 7,
+ maxPossibleRightAlignedIndex: 3,
+ });
+
+ expect(expectedResult).toEqual(-30);
+ });
+
+ it('should end-align focused item with dynamic sizes', () => {
+ const expectedResult = computeTranslation({
+ itemSizeInPx: itemSizeInPxFunction,
+ currentlyFocusedItemIndex: 6,
+ numberOfItemsVisibleOnScreen: 4,
+ nbMaxOfItems: 11,
+ scrollBehavior: scrollBehavior,
+ data,
+ listSizeInPx: 40,
+ maxPossibleLeftAlignedIndex: 7,
+ maxPossibleRightAlignedIndex: 3,
+ });
+
+ expect(expectedResult).toEqual(-60);
+ });
+
+ it('should end-align last element if focused', () => {
+ const expectedResult = computeTranslation({
+ itemSizeInPx,
+ currentlyFocusedItemIndex: 10,
+ numberOfItemsVisibleOnScreen: 4,
+ nbMaxOfItems: 11,
+ scrollBehavior: scrollBehavior,
+ data,
+ listSizeInPx: 40,
+ maxPossibleLeftAlignedIndex: 7,
+ maxPossibleRightAlignedIndex: 3,
+ });
+
+ expect(expectedResult).toEqual(-70);
+ });
+
+ it('should end-align last element if focused with dynamic sizes', () => {
+ const expectedResult = computeTranslation({
+ itemSizeInPx: itemSizeInPxFunction,
+ currentlyFocusedItemIndex: 10,
+ numberOfItemsVisibleOnScreen: 4,
+ nbMaxOfItems: 11,
+ scrollBehavior: scrollBehavior,
+ data,
+ listSizeInPx: 40,
+ maxPossibleLeftAlignedIndex: 7,
+ maxPossibleRightAlignedIndex: 3,
+ });
+
+ expect(expectedResult).toEqual(-120);
+ });
+
+ it('should start-align first element if numberOfItems <= numberOfVisibleItemsOnScreen', () => {
+ const expectedResult = computeTranslation({
+ itemSizeInPx,
+ currentlyFocusedItemIndex: 1,
+ numberOfItemsVisibleOnScreen: 4,
+ nbMaxOfItems: 3,
+ scrollBehavior: scrollBehavior,
+ data,
+ listSizeInPx: 40,
+ maxPossibleLeftAlignedIndex: 7,
+ maxPossibleRightAlignedIndex: 3,
+ });
+
+ expect(expectedResult).toEqual(-0);
+ });
+
+ it('should start-align first element if numberOfItems <= numberOfVisibleItemsOnScreen with dynamic sizes', () => {
+ const expectedResult = computeTranslation({
+ itemSizeInPx: itemSizeInPxFunction,
+ currentlyFocusedItemIndex: 1,
+ numberOfItemsVisibleOnScreen: 4,
+ nbMaxOfItems: 3,
+ scrollBehavior: scrollBehavior,
+ data: data.slice(0, 2),
+ listSizeInPx: 40,
+ maxPossibleLeftAlignedIndex: 0,
+ maxPossibleRightAlignedIndex: 2,
+ });
+
+ expect(expectedResult).toEqual(-0);
+ });
+});
+
describe('computeTranslation for virtualized list with jumping scroll behavior', () => {
const scrollBehavior = 'jump-on-scroll';
diff --git a/packages/lib/src/spatial-navigation/components/virtualizedList/helpers/computeTranslation.ts b/packages/lib/src/spatial-navigation/components/virtualizedList/helpers/computeTranslation.ts
index 4dfeb1f3..3b677b6f 100644
--- a/packages/lib/src/spatial-navigation/components/virtualizedList/helpers/computeTranslation.ts
+++ b/packages/lib/src/spatial-navigation/components/virtualizedList/helpers/computeTranslation.ts
@@ -19,6 +19,74 @@ const computeStickToStartTranslation = ({
return -scrollOffset;
};
+const computeStickToCenterTranslation = ({
+ currentlyFocusedItemIndex,
+ itemSizeInPx,
+ data,
+ listSizeInPx,
+ maxPossibleRightAlignedIndex,
+ maxPossibleLeftAlignedIndex,
+}: {
+ currentlyFocusedItemIndex: number;
+ itemSizeInPx: number | ((item: T) => number);
+ data: T[];
+ listSizeInPx: number;
+ maxPossibleRightAlignedIndex: number;
+ maxPossibleLeftAlignedIndex: number;
+}) => {
+ const currentlyFocusedItemSize =
+ typeof itemSizeInPx === 'function'
+ ? itemSizeInPx(data[currentlyFocusedItemIndex])
+ : itemSizeInPx;
+
+ const sizeOfListFromStartToCurrentlyFocusedItem = getSizeInPxFromOneItemToAnother(
+ data,
+ itemSizeInPx,
+ 0,
+ currentlyFocusedItemIndex,
+ );
+ const sizeOfListFromEndToCurrentlyFocusedItem = getSizeInPxFromOneItemToAnother(
+ data,
+ itemSizeInPx,
+ data.length - 1,
+ currentlyFocusedItemIndex,
+ );
+
+ if (sizeOfListFromStartToCurrentlyFocusedItem < listSizeInPx / 2) {
+ return 0;
+ }
+
+ if (currentlyFocusedItemIndex > maxPossibleLeftAlignedIndex) {
+ console.log('currentlyFocusedItemIndex', currentlyFocusedItemIndex);
+ }
+
+ // if (sizeOfListFromEndToCurrentlyFocusedItem < listSizeInPx / 2) {
+ // console.log('-----------');
+ // console.log('currentlyFocusedItemIndex', currentlyFocusedItemIndex);
+ // const result =
+ // sizeOfListFromStartToCurrentlyFocusedItem -
+ // listSizeInPx +
+ // sizeOfListFromEndToCurrentlyFocusedItem +
+ // currentlyFocusedItemSize;
+ // console.log('list END inferior to half list size in px', result);
+ // console.log(
+ // 'sizeOfListFromStartToCurrentlyFocusedItem',
+ // sizeOfListFromStartToCurrentlyFocusedItem,
+ // );
+ // console.log('listSizeInPx', listSizeInPx);
+ // console.log('sizeOfListFromEndToCurrentlyFocusedItem', sizeOfListFromEndToCurrentlyFocusedItem);
+ // console.log('currentlyFocusedItemSize', currentlyFocusedItemSize);
+ // console.log('-----------');
+
+ // return -result;
+ // }
+
+ const scrollOffset =
+ sizeOfListFromStartToCurrentlyFocusedItem - listSizeInPx / 2 + currentlyFocusedItemSize / 2;
+
+ return -scrollOffset;
+};
+
const computeStickToEndTranslation = ({
currentlyFocusedItemIndex,
itemSizeInPx,
@@ -102,6 +170,15 @@ export const computeTranslation = ({
data,
maxPossibleLeftAlignedIndex,
});
+ case 'stick-to-center':
+ return computeStickToCenterTranslation({
+ currentlyFocusedItemIndex,
+ itemSizeInPx,
+ data,
+ listSizeInPx,
+ maxPossibleLeftAlignedIndex,
+ maxPossibleRightAlignedIndex,
+ });
case 'stick-to-end':
return computeStickToEndTranslation({
currentlyFocusedItemIndex,
diff --git a/packages/lib/src/spatial-navigation/components/virtualizedList/helpers/createScrollOffsetArray.test.ts b/packages/lib/src/spatial-navigation/components/virtualizedList/helpers/createScrollOffsetArray.test.ts
new file mode 100644
index 00000000..ef7f5a73
--- /dev/null
+++ b/packages/lib/src/spatial-navigation/components/virtualizedList/helpers/createScrollOffsetArray.test.ts
@@ -0,0 +1,40 @@
+// Import the function to test
+
+import { computeAllScrollOffsets } from './createScrollOffsetArray';
+
+// Mock data and inputs
+const itemSize = (item) => item.size;
+// doesn't matter here
+const numberOfItemsVisibleOnScreen = 0;
+const data = [
+ { name: 'data 0', size: 1 },
+ { name: 'data 1', size: 1 },
+ { name: 'data 2', size: 2 },
+ { name: 'data 3', size: 1 },
+ { name: 'data 4', size: 1 },
+ { name: 'data 5', size: 2 },
+ { name: 'data 6', size: 1 },
+ { name: 'data 7', size: 1 },
+ { name: 'data 8', size: 2 },
+ { name: 'data 9', size: 1 },
+ { name: 'data 10', size: 1 },
+];
+const nbMaxOfItems = data.length;
+const scrollBehavior = 'stick-to-center';
+const listSizeInPx = 5;
+
+// Expected output
+const expectedOutput = [0, 0, -0.5, -2, -3, -4.5, -6, -7, -8.5, -9, -9];
+
+test('computeAllScrollOffsets returns an array of zeros with the same length as data', () => {
+ const result = computeAllScrollOffsets({
+ itemSize,
+ nbMaxOfItems,
+ numberOfItemsVisibleOnScreen,
+ scrollBehavior,
+ data,
+ listSizeInPx,
+ });
+
+ expect(result).toEqual(expectedOutput);
+});
diff --git a/packages/lib/src/spatial-navigation/components/virtualizedList/helpers/getRange.test.ts b/packages/lib/src/spatial-navigation/components/virtualizedList/helpers/getRange.test.ts
index 5070859b..08560071 100644
--- a/packages/lib/src/spatial-navigation/components/virtualizedList/helpers/getRange.test.ts
+++ b/packages/lib/src/spatial-navigation/components/virtualizedList/helpers/getRange.test.ts
@@ -58,4 +58,39 @@ describe('getRange for custom virtualized list', () => {
expect(expectedResult).toEqual(result);
expect(console.error).toHaveBeenCalledTimes(1);
});
+
+ describe('stick-to-center', () => {
+ it.each`
+ description | arraySize | focusIndex | result
+ ${'empty array'} | ${0} | ${0} | ${{ start: 0, end: 0 }}
+ ${'empty array and out of bounds focus'} | ${0} | ${3} | ${{ start: 0, end: 0 }}
+ ${'one element focused'} | ${1} | ${0} | ${{ start: 0, end: 0 }}
+ ${'one element and out of bounds focus'} | ${1} | ${12} | ${{ start: 0, end: 0 }}
+ ${'small array with focus in the middle'} | ${5} | ${3} | ${{ start: 0, end: 4 }}
+ ${'bigger number of rendered items than data with range out of bound'} | ${5} | ${12} | ${{ start: 0, end: 4 }}
+ ${'focus at the beginning of big array focus 0'} | ${30} | ${0} | ${{ start: 0, end: 7 }}
+ ${'focus at the beginning of big array focus 1'} | ${30} | ${1} | ${{ start: 0, end: 7 }}
+ ${'focus at the beginning of big array focus 2'} | ${30} | ${2} | ${{ start: 0, end: 7 }}
+ ${'focus at the beginning of big array focus 3'} | ${30} | ${3} | ${{ start: 0, end: 7 }}
+ ${'focus at the beginning of big array focus 4, starts scrolling'} | ${30} | ${4} | ${{ start: 1, end: 8 }}
+ ${'focus at the beginning of big array focus 5, scrolls'} | ${30} | ${5} | ${{ start: 2, end: 9 }}
+ ${'focus in the middle of big array, must scroll'} | ${30} | ${12} | ${{ start: 9, end: 16 }}
+ ${'focus at the end of big array'} | ${30} | ${29} | ${{ start: 22, end: 29 }}
+ ${'big array and focus out of bounds'} | ${30} | ${31} | ${{ start: 22, end: 29 }}
+ `(
+ 'should return proper range for array size $arraySize at index $focusIndex (case description: "$description")',
+ ({ arraySize, focusIndex, result }) => {
+ const input = new Array(arraySize).fill(1);
+ const expectedResult = getRange({
+ data: input,
+ currentlyFocusedItemIndex: focusIndex,
+ numberOfRenderedItems: 8,
+ numberOfItemsVisibleOnScreen: 4,
+ scrollBehavior: 'stick-to-center',
+ });
+
+ expect(expectedResult).toEqual(result);
+ },
+ );
+ });
});
diff --git a/packages/lib/src/spatial-navigation/components/virtualizedList/helpers/getRange.ts b/packages/lib/src/spatial-navigation/components/virtualizedList/helpers/getRange.ts
index 6636957c..28c61413 100644
--- a/packages/lib/src/spatial-navigation/components/virtualizedList/helpers/getRange.ts
+++ b/packages/lib/src/spatial-navigation/components/virtualizedList/helpers/getRange.ts
@@ -79,6 +79,7 @@ const getRawStartAndEndIndexes = ({
scrollBehavior: ScrollBehavior;
}) => {
const halfNumberOfItemsNotVisible = numberOfItemsNotVisible / 2;
+ const stickToCenterBoundaries = (numberOfItemsVisibleOnScreen + numberOfItemsNotVisible - 1) / 2;
switch (scrollBehavior) {
case 'stick-to-start':
@@ -90,6 +91,11 @@ const getRawStartAndEndIndexes = ({
1 +
halfNumberOfItemsNotVisible,
};
+ case 'stick-to-center':
+ return {
+ rawStartIndex: currentlyFocusedItemIndex - stickToCenterBoundaries,
+ rawEndIndex: currentlyFocusedItemIndex + stickToCenterBoundaries,
+ };
case 'stick-to-end':
return {
rawStartIndex:
diff --git a/packages/lib/src/spatial-navigation/components/virtualizedList/helpers/getSizeInPxFromOneItemToAnother.ts b/packages/lib/src/spatial-navigation/components/virtualizedList/helpers/getSizeInPxFromOneItemToAnother.ts
index d4b39631..9895015e 100644
--- a/packages/lib/src/spatial-navigation/components/virtualizedList/helpers/getSizeInPxFromOneItemToAnother.ts
+++ b/packages/lib/src/spatial-navigation/components/virtualizedList/helpers/getSizeInPxFromOneItemToAnother.ts
@@ -2,6 +2,8 @@
* This function is used to compute the size in pixels of a range of items in a list.
* If you want the size taken by items from index 0 to 5, you can call this function with
* start = 0 and end = 5. The size is computed by summing the size of each item in the range.
+ * Similarly, if you want to calculate from the other direct, you can call this function
+ * with start = 5 and end = 0.
* @param data The list of items
* @param itemSizeInPx The size of an item in pixels. It can be a number or a function that takes an item and returns a number.
* @param start The start index of the range
@@ -14,8 +16,11 @@ export const getSizeInPxFromOneItemToAnother = (
start: number,
end: number,
): number => {
+ const startIndex = start < end ? start : end;
+ const endIndex = end > start ? end : start;
+
if (typeof itemSizeInPx === 'function') {
- return data.slice(start, end).reduce((acc, item) => acc + itemSizeInPx(item), 0);
+ return data.slice(startIndex, endIndex).reduce((acc, item) => acc + itemSizeInPx(item), 0);
}
- return data.slice(start, end).length * itemSizeInPx;
+ return data.slice(startIndex, endIndex).length * itemSizeInPx;
};