import type { Store } from '../state' import type { GridRowId } from '../types' import type { ColSpanConfig } from './column-span' export type FindNextEditableReturn = { columnId: string rowId: GridRowId } export const findNextEditable = ( store: Store, forward = true ): FindNextEditableReturn | null => { const state = store.getState() const currentEdit = store.selectors.selectEdit(state) if (!currentEdit) { return null } const rowIds = store.selectors.selectRowIds(state) const columnIds = store.selectors.selectColumnIds(state) const startingRowIndex = rowIds.indexOf(currentEdit.rowId) const startingColumnIndex = columnIds.indexOf(currentEdit.columnId) if (startingRowIndex === -1 || startingColumnIndex === -1) { return null } let maxCounter = 0 let rowIndex = startingRowIndex let row = store.selectors.selectRow(state, rowIds[rowIndex]) const direction = forward ? 1 : -1 let columnIndex = startingColumnIndex + direction const columnSpanConfigMap = new Map() /* Since we evaluate each cell on its own, each `colSpan` would be called for all cells in a row on each cell iteration. This simple cache reduces that risk for the duration of this function. */ function getColumnSpanConfig(rowId: GridRowId) { if (columnSpanConfigMap.has(rowId)) { return columnSpanConfigMap.get(rowId) } const columnSpanConfig = store.selectors.selectColumnSpanByRowId( state, rowId ) columnSpanConfigMap.set(rowId, columnSpanConfig) return columnSpanConfig } while (maxCounter < 100) { maxCounter++ if (columnIndex > columnIds.length - 1) { columnIndex = 0 rowIndex += direction } else if (columnIndex < 0) { columnIndex = columnIds.length - 1 rowIndex += direction } if (rowIndex > rowIds.length - 1) { rowIndex = 0 } else if (rowIndex < 0) { rowIndex = rowIds.length - 1 } if ( rowIndex === startingRowIndex && columnIndex === startingColumnIndex ) { return null } const rowId = rowIds[rowIndex] row = store.selectors.selectRow(state, rowId) if (!row) { rowIndex += direction continue } const columnId = columnIds[columnIndex] const columnSpanConfig = getColumnSpanConfig(rowId) if (columnSpanConfig) { const columnSpan = columnSpanConfig.get(columnId) const columnIsAliasSource = [...columnSpanConfig.values()].some( (cell) => cell.as === columnId ) /* If this cell is marked as skipped and isn't working as an alias (and itself doesn't have an alias) we can keep looking */ if ((columnSpan?.skip && !columnIsAliasSource) || columnSpan?.as) { columnIndex += direction continue } } const columnDef = store.selectors.selectColumn(state, columnId) const column = columnDef.data const editable = typeof column.cell?.editable === 'function' ? column.cell.editable({ columnId: columnId, row, rowMeta: store.selectors.selectRowMeta(state, rowId), }) : !!column.cell?.editable if (editable) { return { columnId, rowId, } } columnIndex += direction } return null }