Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,13 @@ VirtualizedGrids only support vertical orientation (vertically scrollable), but
| `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. |

The `SpatialNavigationVirtualizedGrid` component ref expose the following methods:

| Name | Type | Description |
| ---------- | ------------------------- | -------------------------------------------------------------- |
| `focus` | `(index: number) => void` | Give the focus to the selected node and scroll if needed. |
| `scrollTo` | `(index: number) => void` | ❌ does not work properly in the case of a grid. |

## Example Usage

```jsx
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { ReactNode, useCallback, useEffect, useMemo } from 'react';
import React, { ForwardedRef, ReactNode, useCallback, useEffect, useMemo } from 'react';
import { View, ViewStyle, StyleSheet } from 'react-native';
import range from 'lodash/range';

Expand All @@ -11,6 +11,8 @@ import { useSpatialNavigator } from '../../context/SpatialNavigatorContext';
import { ParentIdContext, useParentId } from '../../context/ParentIdContext';
import { typedMemo } from '../../helpers/TypedMemo';
import { convertToGrid } from './helpers/convertToGrid';
import { SpatialNavigationVirtualizedGridRef } from '../../types/SpatialNavigationVirtualizedGridRef';
import { typedForwardRef } from '../../helpers/TypedForwardRef';

type SpatialNavigationVirtualizedGridProps<T> = Pick<
SpatialNavigationVirtualizedListWithScrollProps<T>,
Expand All @@ -35,6 +37,7 @@ type SpatialNavigationVirtualizedGridProps<T> = Pick<
numberOfColumns: number;
/** Used to modify every row style */
rowContainerStyle?: ViewStyle;
ref?: React.RefObject<SpatialNavigationVirtualizedGridRef>;
};

export type GridRowType<T> = {
Expand Down Expand Up @@ -183,82 +186,90 @@ const GridRow = <T,>({
*/

export const SpatialNavigationVirtualizedGrid = typedMemo(
<T,>({
renderItem,
data,
numberOfColumns,
itemHeight,
header,
headerSize,
additionalRenderedRows,
onEndReachedThresholdRowsNumber,
nbMaxOfItems,
rowContainerStyle,
...props
}: SpatialNavigationVirtualizedGridProps<T>) => {
if (header && !headerSize) throw new Error('You must provide a headerSize when using a header');
if (headerSize && !header) throw new Error('You must provide a header when using a headerSize');
const hasHeader = !!header && !!headerSize;
typedForwardRef(
<T,>(
{
renderItem,
data,
numberOfColumns,
itemHeight,
header,
headerSize,
additionalRenderedRows,
onEndReachedThresholdRowsNumber,
nbMaxOfItems,
rowContainerStyle,
...props
}: SpatialNavigationVirtualizedGridProps<T>,
ref: ForwardedRef<SpatialNavigationVirtualizedGridRef>,
) => {
if (header && !headerSize)
throw new Error('You must provide a headerSize when using a header');
if (headerSize && !header)
throw new Error('You must provide a header when using a headerSize');
const hasHeader = !!header && !!headerSize;

const gridRows = useMemo(
() => convertToGrid(data, numberOfColumns, header),
[data, header, numberOfColumns],
);
const gridRowsWithHeaderIfProvided = useMemo(
() => (hasHeader ? [header, ...gridRows] : gridRows),
[hasHeader, header, gridRows],
);
const gridRows = useMemo(
() => convertToGrid(data, numberOfColumns, header),
[data, header, numberOfColumns],
);
const gridRowsWithHeaderIfProvided = useMemo(
() => (hasHeader ? [header, ...gridRows] : gridRows),
[hasHeader, header, gridRows],
);

const itemSizeAsAFunction = useCallback(
(item: GridRowType<T> | JSX.Element) => {
if (hasHeader && React.isValidElement(item)) {
return headerSize;
}
return itemHeight;
},
[hasHeader, headerSize, itemHeight],
);
const itemSizeAsAFunction = useCallback(
(item: GridRowType<T> | JSX.Element) => {
if (hasHeader && React.isValidElement(item)) {
return headerSize;
}
return itemHeight;
},
[hasHeader, headerSize, itemHeight],
);

const itemSize = hasHeader ? itemSizeAsAFunction : itemHeight;
const itemSize = hasHeader ? itemSizeAsAFunction : itemHeight;

const renderRow = useCallback(
({ item: row, index }: { item: GridRowType<T>; index: number }) => (
<GridRow
renderItem={renderItem}
numberOfColumns={numberOfColumns}
row={row}
rowIndex={index}
rowContainerStyle={rowContainerStyle}
/>
),
[renderItem, numberOfColumns, rowContainerStyle],
);
const renderHeaderThenRows = useCallback(
({ item, index }: { item: GridRowType<T> | JSX.Element; index: number }) => {
if (React.isValidElement(item)) {
return item;
}
//We do this to have index taking into account the header
const computedIndex = hasHeader ? index - 1 : index;
return renderRow({ item: item as GridRowType<T>, index: computedIndex });
},
[hasHeader, renderRow],
);
const renderRow = useCallback(
({ item: row, index }: { item: GridRowType<T>; index: number }) => (
<GridRow
renderItem={renderItem}
numberOfColumns={numberOfColumns}
row={row}
rowIndex={index}
rowContainerStyle={rowContainerStyle}
/>
),
[renderItem, numberOfColumns, rowContainerStyle],
);
const renderHeaderThenRows = useCallback(
({ item, index }: { item: GridRowType<T> | JSX.Element; index: number }) => {
if (React.isValidElement(item)) {
return item;
}
//We do this to have index taking into account the header
const computedIndex = hasHeader ? index - 1 : index;
return renderRow({ item: item as GridRowType<T>, index: computedIndex });
},
[hasHeader, renderRow],
);

return (
<SpatialNavigationVirtualizedList
data={gridRowsWithHeaderIfProvided}
itemSize={itemSize}
additionalItemsRendered={additionalRenderedRows}
onEndReachedThresholdItemsNumber={onEndReachedThresholdRowsNumber}
orientation="vertical"
nbMaxOfItems={nbMaxOfItems ? Math.ceil(nbMaxOfItems / numberOfColumns) : undefined}
renderItem={renderHeaderThenRows}
isGrid
{...props}
/>
);
},
return (
<SpatialNavigationVirtualizedList
ref={ref}
data={gridRowsWithHeaderIfProvided}
itemSize={itemSize}
additionalItemsRendered={additionalRenderedRows}
onEndReachedThresholdItemsNumber={onEndReachedThresholdRowsNumber}
orientation="vertical"
nbMaxOfItems={nbMaxOfItems ? Math.ceil(nbMaxOfItems / numberOfColumns) : undefined}
renderItem={renderHeaderThenRows}
isGrid
{...props}
/>
);
},
),
);
SpatialNavigationVirtualizedGrid.displayName = 'SpatialNavigationVirtualizedGrid';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,12 +197,15 @@ export const SpatialNavigationVirtualizedListWithScroll = typedMemo(
[deviceTypeRef],
);

const scrollTo = useCallback((index: number) => {
if (idRef.current) {
const newId = idRef.current.getNthVirtualNodeID(index);
spatialNavigator.grabFocusDeferred(newId);
}
}, [idRef, spatialNavigator]);
const scrollTo = useCallback(
(index: number) => {
if (idRef.current) {
const newId = idRef.current.getNthVirtualNodeID(index);
spatialNavigator.grabFocusDeferred(newId);
}
},
[idRef, spatialNavigator],
);

useImperativeHandle(
ref,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { SpatialNavigationVirtualizedListRef } from './SpatialNavigationVirtualizedListRef';

export type SpatialNavigationVirtualizedGridRef = SpatialNavigationVirtualizedListRef;