import { ROW_FOCUS_ID } from '../constants' import type { GridFocusArea } from '../state' import type { GridRowId } from '../types' import { findNextHeaderCell, getColumnIndex } from './focus-utils' import type { ApiSection } from './types' export type GridFocusApi = { move: (direction: 'left' | 'right' | 'up' | 'down') => void set: ( rowId: GridRowId, columnId: string, area: GridFocusArea, subFocus?: number | 'first' | 'last' ) => void } export const createFocusApi: ApiSection = (store) => ({ move(direction) { const state = store.getState() const currentFocus = store.selectors.selectCurrentFocus(state) const newFocus = { ...currentFocus } const { rowId, columnId } = newFocus const hasFooter = store.selectors.selectAggregationEnabled(state) const columnCount = store.selectors.selectColumnCount(state) const columnIds = store.selectors.selectColumnIds(state) const isDraggingEnabled = store.selectors.selectIsDraggingEnabled(state) let columnIndex = getColumnIndex( columnIds, columnId, currentFocus.subFocus ) const headerHierarchy = store.selectors.selectHeaderHierarchy(state) const rowIds = store.selectors.selectRowIds(state) const headerRowIds = store.selectors.selectHeaderRowIds(state) const rowCount = rowIds.length const rowIndex = rowIds.indexOf(rowId) const loopHorizontally = store.selectors.selectCanLoopHorizontally(state) if (direction === 'left' || direction === 'right') { const colSpanConfig = store.selectors.selectColumnSpanByRowId( state, rowId ) const config = colSpanConfig?.get(currentFocus.columnId) if (config?.skip || config?.positionColumnIds) { const [hostColumnId, config] = [ ...colSpanConfig!.entries(), ].find(([_, v]) => v.positionColumnIds?.includes(currentFocus.columnId) ) ?? [null, null] if (hostColumnId !== null) { columnIndex = columnIds.indexOf( config.positionColumnIds!.at( direction === 'right' ? -1 : 0 )! ) } } } if (direction === 'left') { if (columnIndex > 0) { if (currentFocus.area === 'header' && headerHierarchy.levels) { const next = findNextHeaderCell( direction, currentFocus, columnIds, headerHierarchy, headerRowIds ) newFocus.columnId = columnIds[next.columnIndex] } else { newFocus.columnId = columnIds[columnIndex - 1] } newFocus.subFocus = 'last' } else if (isDraggingEnabled) { if ( loopHorizontally && currentFocus.columnId === ROW_FOCUS_ID && currentFocus.subFocus === 'last' ) { newFocus.columnId = columnIds[columnCount - 1] newFocus.subFocus = 'last' } else { newFocus.columnId = ROW_FOCUS_ID newFocus.subFocus = 'last' } } } if (direction === 'right') { if (columnIndex < columnCount - 1) { if (currentFocus.area === 'header' && headerHierarchy.levels) { const next = findNextHeaderCell( direction, currentFocus, columnIds, headerHierarchy, headerRowIds ) newFocus.columnId = columnIds[next.columnIndex] } else { newFocus.columnId = columnIds[columnIndex + 1] } newFocus.subFocus = 'first' } else if (isDraggingEnabled) { if ( loopHorizontally && currentFocus.columnId === ROW_FOCUS_ID && currentFocus.subFocus === 'first' ) { newFocus.columnId = columnIds[0] newFocus.subFocus = 'first' } else { newFocus.columnId = ROW_FOCUS_ID newFocus.subFocus = 'first' } } } if (direction === 'down') { if (currentFocus.area === 'header') { const next = findNextHeaderCell( direction, currentFocus, columnIds, headerHierarchy, headerRowIds ) if (next.rowIndex === -1) { //Out of bounds means switch to next area newFocus.area = 'body' newFocus.rowId = rowIds[0] } else { newFocus.rowId = headerRowIds[next.rowIndex] newFocus.columnId = columnIds[next.columnIndex] } } else if (currentFocus.area === 'body') { if (rowIndex < rowCount - 1) { newFocus.rowId = rowIds[rowIndex + 1] } else if (hasFooter) { newFocus.area = 'footer' newFocus.rowId = '0' } } } if (direction === 'up') { if (currentFocus.area === 'footer') { newFocus.area = 'body' newFocus.rowId = rowIds[rowCount - 1] } else if (currentFocus.area === 'body') { if (rowIndex > 0) { newFocus.rowId = rowIds[rowIndex - 1] } else { newFocus.area = 'header' newFocus.rowId = headerRowIds[headerRowIds.length - 1] } } else if (currentFocus.area === 'header') { const next = findNextHeaderCell( direction, currentFocus, columnIds, headerHierarchy, headerRowIds ) newFocus.rowId = headerRowIds[next.rowIndex] newFocus.columnId = currentFocus.columnId === ROW_FOCUS_ID ? currentFocus.columnId : columnIds[next.columnIndex] } } store.dispatch({ type: 'updateFocus', payload: newFocus, }) }, set(rowId, columnId, area, subFocus) { const state = store.getState() const currentFocus = store.selectors.selectCurrentFocus(state) store.dispatch({ type: 'updateFocus', payload: { columnId, rowId, area, subFocus: subFocus ?? currentFocus.subFocus, }, }) }, })