UNPKG

@base-ui/react

Version:

Base UI is a library of headless ('unstyled') React components and low-level hooks. You gain complete control over your app's CSS and accessibility features.

59 lines (58 loc) 2.53 kB
import { createGridCellMap, getGridCellIndexOfCorner, getGridCellIndices, getGridNavigatedIndex, isListIndexDisabled } from "../../../floating-ui-react/utils/composite.mjs"; import { ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT } from "../composite.mjs"; /** * Builds the grid navigation handler passed to `CompositeRoot`/`useCompositeRoot` * via the `grid` prop. Importing and calling this is the opt-in for grid * navigation: composites that don't pass `grid` never reference the algorithm, * so bundlers tree-shake the grid helpers out. */ export function gridNavigation(config) { const { cols, dense = false, itemSizes } = config; return state => { const { disabledIndices, elementsRef, event, highlightedIndex, loopFocus, maxIndex, minIndex, onLoop, orientation, rtl } = state; const sizes = itemSizes || Array.from({ length: elementsRef.current.length }, () => ({ width: 1, height: 1 })); // Work in hypothetical 1x1 cell indices, then convert back to item indices. const cellMap = createGridCellMap(sizes, cols, dense); const minGridIndex = cellMap.findIndex(index => index != null && !isListIndexDisabled(elementsRef.current, index, disabledIndices)); const maxGridIndex = cellMap.reduce((foundIndex, index, cellIndex) => index != null && !isListIndexDisabled(elementsRef.current, index, disabledIndices) ? cellIndex : foundIndex, -1); const cellIndex = getGridNavigatedIndex(cellMap.map(itemIndex => itemIndex != null ? elementsRef.current[itemIndex] : null), { event, orientation, loopFocus, onLoop, cols, // Treat undefined gaps as disabled so navigation cannot land in them. disabledIndices: getGridCellIndices([...(disabledIndices || elementsRef.current.map((_, index) => isListIndexDisabled(elementsRef.current, index) ? index : undefined)), undefined], cellMap), minIndex: minGridIndex, maxIndex: maxGridIndex, prevIndex: getGridCellIndexOfCorner(highlightedIndex > maxIndex ? minIndex : highlightedIndex, sizes, cellMap, cols, // Choose the corner closest to the movement direction so spanning items // do not immediately resolve back to themselves. // eslint-disable-next-line no-nested-ternary event.key === ARROW_DOWN ? 'bl' : event.key === (rtl ? ARROW_LEFT : ARROW_RIGHT) ? 'tr' : 'tl'), rtl }); // Navigated cell will never be nullish. return cellMap[cellIndex]; }; }