import { ref } from 'vue'; import type { CellRange } from '../types'; import { normalizeRange, rangesContainCell, } from '../commands/FillRangeCommand'; export interface SelectionModifiers { shift?: boolean; ctrl?: boolean; } function fullRowRange(row: number, maxCol: number): CellRange { return normalizeRange({ startRow: row, endRow: row, startCol: 0, endCol: maxCol, }); } function fullColRange(col: number, maxRow: number): CellRange { return normalizeRange({ startRow: 0, endRow: maxRow, startCol: col, endCol: col, }); } function rowRange(r0: number, r1: number, maxCol: number): CellRange { return normalizeRange({ startRow: r0, endRow: r1, startCol: 0, endCol: maxCol, }); } function colRange(c0: number, c1: number, maxRow: number): CellRange { return normalizeRange({ startRow: 0, endRow: maxRow, startCol: c0, endCol: c1, }); } function isFullRowRange(range: CellRange, maxCol: number): boolean { const r = normalizeRange(range); return r.startCol === 0 && r.endCol === maxCol; } function isFullColRange(range: CellRange, maxRow: number): boolean { const r = normalizeRange(range); return r.startRow === 0 && r.endRow === maxRow; } function rangeContainsRow(range: CellRange, row: number, maxCol: number): boolean { const r = normalizeRange(range); return isFullRowRange(r, maxCol) && row >= r.startRow && row <= r.endRow; } function rangeContainsCol(range: CellRange, col: number, maxRow: number): boolean { const r = normalizeRange(range); return isFullColRange(r, maxRow) && col >= r.startCol && col <= r.endCol; } export function useSelection() { const ranges = ref([]); const anchor = ref<{ row: number; col: number } | null>(null); const activeCell = ref<{ row: number; col: number } | null>(null); const isFillDragging = ref(false); /** @deprecated use ranges */ const selection = ranges; function selectCell(row: number, col: number) { activeCell.value = { row, col }; anchor.value = { row, col }; ranges.value = [ { startRow: row, startCol: col, endRow: row, endCol: col }, ]; } function extendSelection(row: number, col: number) { if (!anchor.value) { selectCell(row, col); return; } ranges.value = [ { startRow: anchor.value.row, startCol: anchor.value.col, endRow: row, endCol: col, }, ]; activeCell.value = { row, col }; } function selectRange(range: CellRange) { const normalized = normalizeRange(range); ranges.value = [normalized]; activeCell.value = { row: normalized.startRow, col: normalized.startCol, }; anchor.value = { ...activeCell.value }; } function getSelectionRanges(): CellRange[] { return ranges.value.map((r) => normalizeRange(r)); } function getNormalizedSelection(): CellRange | null { const primary = getPrimaryRange(); return primary; } function getPrimaryRange(): CellRange | null { if (!ranges.value.length) return null; if (!activeCell.value) return normalizeRange(ranges.value[0]!); const { row, col } = activeCell.value; for (const range of ranges.value) { const r = normalizeRange(range); if ( row >= r.startRow && row <= r.endRow && col >= r.startCol && col <= r.endCol ) { return r; } } return normalizeRange(ranges.value[ranges.value.length - 1]!); } function containsCell(row: number, col: number): boolean { return rangesContainCell(ranges.value, row, col); } function selectAll(maxRow: number, maxCol: number) { const range = normalizeRange({ startRow: 0, endRow: maxRow, startCol: 0, endCol: maxCol, }); ranges.value = [range]; activeCell.value = { row: 0, col: 0 }; anchor.value = { row: 0, col: 0 }; } function selectEntireRow( row: number, maxCol: number, opts: SelectionModifiers = {}, ) { const { shift, ctrl } = opts; if (ctrl) { const next = [...ranges.value]; const idx = next.findIndex((r) => rangeContainsRow(r, row, maxCol)); if (idx >= 0) { next.splice(idx, 1); } else { next.push(fullRowRange(row, maxCol)); } ranges.value = next.length ? next : []; if (ranges.value.length) { activeCell.value = { row, col: 0 }; if (!shift) anchor.value = { row, col: 0 }; } else { activeCell.value = null; } return; } if (shift && anchor.value) { const range = rowRange(anchor.value.row, row, maxCol); ranges.value = [range]; activeCell.value = { row, col: 0 }; return; } ranges.value = [fullRowRange(row, maxCol)]; activeCell.value = { row, col: 0 }; anchor.value = { row, col: 0 }; } function selectEntireColumn( col: number, maxRow: number, opts: SelectionModifiers = {}, ) { const { shift, ctrl } = opts; if (ctrl) { const next = [...ranges.value]; const idx = next.findIndex((r) => rangeContainsCol(r, col, maxRow)); if (idx >= 0) { next.splice(idx, 1); } else { next.push(fullColRange(col, maxRow)); } ranges.value = next.length ? next : []; if (ranges.value.length) { activeCell.value = { row: 0, col }; if (!shift) anchor.value = { row: 0, col }; } else { activeCell.value = null; } return; } if (shift && anchor.value) { const range = colRange(anchor.value.col, col, maxRow); ranges.value = [range]; activeCell.value = { row: 0, col }; return; } ranges.value = [fullColRange(col, maxRow)]; activeCell.value = { row: 0, col }; anchor.value = { row: 0, col }; } function extendRowSelection(row: number, maxCol: number) { if (!anchor.value) { selectEntireRow(row, maxCol); return; } ranges.value = [rowRange(anchor.value.row, row, maxCol)]; activeCell.value = { row, col: 0 }; } function extendColumnSelection(col: number, maxRow: number) { if (!anchor.value) { selectEntireColumn(col, maxRow); return; } ranges.value = [colRange(anchor.value.col, col, maxRow)]; activeCell.value = { row: 0, col }; } function clearSelection() { ranges.value = []; anchor.value = null; activeCell.value = null; } return { ranges, selection, anchor, activeCell, isFillDragging, selectCell, extendSelection, selectRange, selectAll, selectEntireRow, selectEntireColumn, extendRowSelection, extendColumnSelection, getSelectionRanges, getNormalizedSelection, getPrimaryRange, containsCell, clearSelection, }; }