import type { Store } from '../state' import type { GridRangeSelection, GridRowId } from '../types' export function isRangeEqual( range1: GridRangeSelection | null, range2: GridRangeSelection | null ) { if (range1 == null && range2 == null) return true if (!range1 || !range2) return false return ( range1.from.columnId === range2.from.columnId && range1.from.rowId === range2.from.rowId && range1.to.columnId === range2.to.columnId && range1.to.rowId === range2.to.rowId ) } export type GridRangeIds = { rowIds: Set columnIds: Set firstColumnId: string | null firstRowId: GridRowId | null lastColumnId: string | null lastRowId: GridRowId | null } export function getIdsFromRange( range: GridRangeSelection, rowIds: GridRowId[], columnIds: string[] ): GridRangeIds { const rowIdsSet = new Set() const columnIdsSet = new Set() const { from, to } = range const fromColumnIndex = columnIds.indexOf(from.columnId) const toColumnIndex = columnIds.indexOf(to.columnId) const startColumnIndex = fromColumnIndex < toColumnIndex ? fromColumnIndex : toColumnIndex const endColumnIndex = fromColumnIndex > toColumnIndex ? fromColumnIndex : toColumnIndex const idRange = columnIds.slice(startColumnIndex, endColumnIndex + 1) idRange.forEach((columnId) => { columnIdsSet.add(columnId) }) const fromRowIndex = rowIds.indexOf(from.rowId) const toRowIndex = rowIds.indexOf(to.rowId) const startRowIndex = fromRowIndex < toRowIndex ? fromRowIndex : toRowIndex const endRowIndex = fromRowIndex > toRowIndex ? fromRowIndex : toRowIndex const idRangeRows = rowIds.slice(startRowIndex, endRowIndex + 1) idRangeRows.forEach((rowId) => { rowIdsSet.add(rowId) }) return { rowIds: rowIdsSet, columnIds: columnIdsSet, firstColumnId: idRange[0] ?? null, firstRowId: idRangeRows[0] ?? null, lastColumnId: idRange.at(-1) ?? null, lastRowId: idRangeRows.at(-1) ?? null, } } export const healSpannedRange = (range: GridRangeSelection, grid: Store) => { const state = grid.getState() const rowIds = grid.selectors.selectRowIds(state) const columnIds = grid.selectors.selectColumnIds(state) const newRange: GridRangeSelection = window.structuredClone(range) const ids = getIdsFromRange(newRange, rowIds, columnIds) if (!ids.firstColumnId) { /* Cannot heal range, so return original – this should not happen */ return range } const startingLeftIndex = columnIds.indexOf(ids.firstColumnId) let leftIndex = startingLeftIndex let leftColumnId = ids.firstColumnId const startingRightIndex = columnIds.indexOf(ids.lastColumnId!) let rightIndex = startingRightIndex let rightColumnId = ids.lastColumnId! while (true) { let selectionNotSpanned = true for (const rowId of ids.rowIds) { const colSpans = grid.selectors.selectColumnSpanByRowId( state, rowId ) if (!colSpans) { continue } for (const [, config] of colSpans) { if ( config.positionColumnIds?.some( (id) => id === leftColumnId || id === rightColumnId ) ) { const idsAsIndexes = config.positionColumnIds .map((cid) => columnIds.indexOf(cid)) .filter((i) => i >= 0) const minIndex = Math.min(Infinity, ...idsAsIndexes) const maxIndex = Math.max(-1, ...idsAsIndexes) if (minIndex < leftIndex) { leftIndex = minIndex leftColumnId = columnIds[leftIndex] selectionNotSpanned = false } if (maxIndex > rightIndex) { rightIndex = maxIndex rightColumnId = columnIds[rightIndex] selectionNotSpanned = false } } } } if (selectionNotSpanned) break } if (startingLeftIndex !== leftIndex) { if (newRange.from.columnId === ids.firstColumnId) { newRange.from.columnId = leftColumnId } else { newRange.to.columnId = leftColumnId } } if (startingRightIndex !== rightIndex) { if (newRange.to.columnId === ids.lastColumnId) { newRange.to.columnId = rightColumnId } else { newRange.from.columnId = rightColumnId } } return newRange }