diff --git a/docs/api.md b/docs/api.md index edceaa42..43cfbc85 100644 --- a/docs/api.md +++ b/docs/api.md @@ -51,7 +51,7 @@ The `SpatialNavigationNode` component receives the following props: | `onFocus` | `function` | `undefined` | Callback function to be called when the node gains focus. | | `onBlur` | `function` | `undefined` | Callback function to be called when the node loses focus. | | `onSelect` | `function` | `undefined` | Callback function to be called when the node is selected. | -| `onLongSelect` | `function` | `onSelect` | Callback function to be called when the node is selected with long key press. | +| `onLongSelect` | `function` | `onSelect` | Callback function to be called when the node is selected with long key press. | | `onActive` | `function` | `undefined` | Callback function to be called when the node is made active by either itself or one of its descendants gaining focus. | | `onInactive` | `function` | `undefined` | Callback function to be called when the node was active and due to an updated focus, is no longer active. | | `orientation` | `'vertical' \| 'horizontal` | `'vertical'` | Determines the orientation of the node. | @@ -241,7 +241,7 @@ It also ensures that the scroll event is propagated properly to parent ScrollVie | `data` | `Array` | The array of data items to render. ⚠️ You should memoize this array for maximum performance. A costly memo depends on it. | | `renderItem` | `(args: { item: T }) => JSX.Element` | A function that returns the JSX element to render for each item in the data array. The function receives an object with the item as a parameter. | | `itemSize` | `number \| ((item: T) => number)` | In case you specify a number it will behave like this : ff vertical, the height of an item; otherwise, the width. You can also specify a function which needs to return for each item of `data` its size in pixel in order for the list to handle various item sizes. ⚠️ You should memoize this function for maximal performances. An important memo depends on it. | -| `additionalItemsRendered` | `number` | Optional : The number of items to be rendered (virtualization size) additionally to the elements visible on screen. Base value is 4 for `stick-to-start` and `stick-to-end` scrolls, and twice the number of elements visible for `jump-on-scroll`. | +| `additionalItemsRendered` | `number` | Optional : The number of items to be rendered (virtualization size) additionally to the minimum value for the list to work. Default value is 2. The minimum amount of elements for the list to work is 2N + 1 for `jump-on-scroll`, and 2N + 2 for the other behaviours (N being the number of visible elements on screen) | | `onEndReached` | `() => void` | An optional callback function that is called when the user reaches the end of the list. Helps with pagination. | | `onEndReachedThresholdItemsNumber` | `number` | The number of items left to display before triggering the `onEndReached` callback. Defaults to 3. | | `style` | `ViewStyle` | Custom style to be applied to the VirtualizedList container. | @@ -312,27 +312,27 @@ VirtualizedGrids only support vertical orientation (vertically scrollable), but ## Props -| Name | Type | Description | -| --------------------------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `data` | `Array` | The array of items to render in the grid. ⚠️ You should memoize this array for maximum performance. A costly memo depends on it. | -| `renderItem` | `(args: { item: T }) => JSX.Element` | A function that returns the JSX element to render for each item in the data array. The function receives an object with the item as a parameter. | -| `numberOfColumns` | `Number` | The number of columns in the grid or the number of items per row. | -| `itemHeight` | `Number` | The height of each item in the grid. | -| `additionalRenderedRows` | `Number` | Optional : The number of rows to be rendered (virtualization size) additionally to the rows visible on screen. Base value is 4 for `stick-to-start` and `stick-to-end` scrolls, and twice the number of elements visible for `jump-on-scroll`. | -| `header` | `JSX.Element` | Optional header component you can provide to display at the top of a virtualized grid. If provided, you also need to provide its size (so that the grid knows how to scroll) | -| `headerSize` | `Number` | The Size in pixels of the (optionnally) provided header. | -| `onEndReached` | `() => void` | An optional callback function that is called when the user reaches the end of the list. Helps with pagination. | -| `onEndReachedThresholdRowsNumber` | `Number` | Number of rows left to display before triggering the onEndReached event. | -| `style` | `Object` | Used to modify the style of the grid. | -| `nbMaxOfItems` | `Number` | The maximum number of items to render : used to compute the number of rows to render. | -| `rowContainerStyle` | `Object` | Used to modify the style of each row in the grid. | -| `scrollDuration` | `number` | The duration of a scrolling animation inside the VirtualizedList. Defaults to `200` (ms). | -| `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 row at the beginning or the end of the visible items on screen. `jump-on-scroll` jumps from `numberOfItemsVisibleOnScreen` rows when needed. | -| `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. | -| `descendingArrowContainerStyle` | `ViewStyle` | For web TVs cursor handling. Style of the view which wraps the descending arrow. Hover this view will trigger the scroll. | -| `scrollInterval` | `number` | For web TVs cursor handling. Speed of the pointer scroll. It represents the interval in ms between every item scrolled. Default value is set to 100. | +| Name | Type | Description | +| --------------------------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data` | `Array` | The array of items to render in the grid. ⚠️ You should memoize this array for maximum performance. A costly memo depends on it. | +| `renderItem` | `(args: { item: T }) => JSX.Element` | A function that returns the JSX element to render for each item in the data array. The function receives an object with the item as a parameter. | +| `numberOfColumns` | `Number` | The number of columns in the grid or the number of items per row. | +| `itemHeight` | `Number` | The height of each item in the grid. | +| `additionalRenderedRows` | `Number` | Optional : The number of items to be rendered (virtualization size) additionally to the minimum value for the list to work. Default value is 2. The minimum amount of elements for the list to work is 2N + 1 for `jump-on-scroll`, and 2N + 2 for the other behaviours (N being the number of visible elements on screen) | +| `header` | `JSX.Element` | Optional header component you can provide to display at the top of a virtualized grid. If provided, you also need to provide its size (so that the grid knows how to scroll) | +| `headerSize` | `Number` | The Size in pixels of the (optionnally) provided header. | +| `onEndReached` | `() => void` | An optional callback function that is called when the user reaches the end of the list. Helps with pagination. | +| `onEndReachedThresholdRowsNumber` | `Number` | Number of rows left to display before triggering the onEndReached event. | +| `style` | `Object` | Used to modify the style of the grid. | +| `nbMaxOfItems` | `Number` | The maximum number of items to render : used to compute the number of rows to render. | +| `rowContainerStyle` | `Object` | Used to modify the style of each row in the grid. | +| `scrollDuration` | `number` | The duration of a scrolling animation inside the VirtualizedList. Defaults to `200` (ms). | +| `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 row at the beginning or the end of the visible items on screen. `jump-on-scroll` jumps from `numberOfItemsVisibleOnScreen` rows when needed. | +| `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. | +| `descendingArrowContainerStyle` | `ViewStyle` | For web TVs cursor handling. Style of the view which wraps the descending arrow. Hover this view will trigger the scroll. | +| `scrollInterval` | `number` | For web TVs cursor handling. Speed of the pointer scroll. It represents the interval in ms between every item scrolled. Default value is set to 100. | ## Example Usage 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 3684b941..22c51d8d 100644 --- a/packages/lib/src/spatial-navigation/components/virtualizedList/SpatialNavigationVirtualizedList.test.tsx +++ b/packages/lib/src/spatial-navigation/components/virtualizedList/SpatialNavigationVirtualizedList.test.tsx @@ -412,6 +412,7 @@ describe('SpatialNavigationVirtualizedList', () => { data={data} itemSize={100} scrollBehavior="jump-on-scroll" + additionalItemsRendered={0} /> , diff --git a/packages/lib/src/spatial-navigation/components/virtualizedList/VirtualizedList.tsx b/packages/lib/src/spatial-navigation/components/virtualizedList/VirtualizedList.tsx index dd9a4dfb..398bbefd 100644 --- a/packages/lib/src/spatial-navigation/components/virtualizedList/VirtualizedList.tsx +++ b/packages/lib/src/spatial-navigation/components/virtualizedList/VirtualizedList.tsx @@ -19,7 +19,17 @@ export interface VirtualizedListProps { /** If vertical the height of an item, otherwise the width */ itemSize: number | ((item: T) => number); currentlyFocusedItemIndex: number; - /** How many items are RENDERED ADDITIONALLY to those already visible (impacts virtualization size). Defaults to 4 for stick-to-start & stick-to-end scrolls, and twice the number of elements visible + 1 for jump-on-scroll. */ + /** + * How many items are RENDERED ADDITIONALLY to the minimum amount possible. It impacts virtualization size. + * Defaults to 2. + * + * Should be a POSITIVE number. + * + * Minimal amount possible is 2N + 1 for jump-on-scroll, and N + 2 for the other behaviours, N being the number + * of visible elements on the screen. + * + * By default, you will then have N + 2 + 2 elements rendered for stick-to-* behaviours. + */ additionalItemsRendered?: number; onEndReached?: () => void; /** Number of items left to display before triggering onEndReached */ @@ -127,7 +137,7 @@ export const VirtualizedList = typedMemo( renderItem, itemSize, currentlyFocusedItemIndex, - additionalItemsRendered, + additionalItemsRendered = 2, onEndReached, onEndReachedThresholdItemsNumber = 3, style, @@ -145,9 +155,11 @@ export const VirtualizedList = typedMemo( itemSize, }); - const numberOfItemsToRender = additionalItemsRendered - ? additionalItemsRendered + numberOfItemsVisibleOnScreen - : getAdditionalNumberOfItemsRendered(scrollBehavior, numberOfItemsVisibleOnScreen); + const numberOfItemsToRender = getAdditionalNumberOfItemsRendered( + scrollBehavior, + numberOfItemsVisibleOnScreen, + additionalItemsRendered, + ); const range = getRange({ data, diff --git a/packages/lib/src/spatial-navigation/components/virtualizedList/helpers/getAdditionalNumberOfItemsRendered.ts b/packages/lib/src/spatial-navigation/components/virtualizedList/helpers/getAdditionalNumberOfItemsRendered.ts index dcbd1f89..1f96d6ab 100644 --- a/packages/lib/src/spatial-navigation/components/virtualizedList/helpers/getAdditionalNumberOfItemsRendered.ts +++ b/packages/lib/src/spatial-navigation/components/virtualizedList/helpers/getAdditionalNumberOfItemsRendered.ts @@ -1,14 +1,36 @@ import { ScrollBehavior } from '../VirtualizedList'; +/** + * If list rendered elements is too small, it creates spatial navigation bugs + * like not being able to go back on the left + * + * There are other ways to fix this than forcing a minimum number of additional items to render + * but having a minimum number items to render inferior to the window size makes no sense anyway + */ +const MINIMUM_ADDITIONAL_ITEMS_TO_WORK = 2; + export const getAdditionalNumberOfItemsRendered = ( scrollBehavior: ScrollBehavior, numberOfElementsVisibleOnScreen: number, + additionalNumberOfItemsRendered: number, ) => { + if (additionalNumberOfItemsRendered < 0) { + console.error( + '[VirtualizedList] Negative number of additional items to render was given, no elements will be rendered', + ); + } + switch (scrollBehavior) { - case 'stick-to-start': - case 'stick-to-end': - return 4 + numberOfElementsVisibleOnScreen; case 'jump-on-scroll': - return 2 * numberOfElementsVisibleOnScreen + 1; + // This is a special case + // Since we're jumping on scroll, we need to render more items to make sure that a whole + // window is ready when we jump! + return 2 * numberOfElementsVisibleOnScreen + 1 + additionalNumberOfItemsRendered; + default: + return ( + numberOfElementsVisibleOnScreen + + MINIMUM_ADDITIONAL_ITEMS_TO_WORK + + additionalNumberOfItemsRendered + ); } };