diff --git a/webapp/IronCalc/src/components/Worksheet/usePointer.ts b/webapp/IronCalc/src/components/Worksheet/usePointer.ts index 162506f..1e096de 100644 --- a/webapp/IronCalc/src/components/Worksheet/usePointer.ts +++ b/webapp/IronCalc/src/components/Worksheet/usePointer.ts @@ -3,6 +3,7 @@ import { type PointerEvent, type RefObject, useCallback, useRef } from "react"; import { isInReferenceMode } from "../Editor/util"; import type { Cell } from "../types"; import { rangeToStr } from "../util"; +import { LAST_COLUMN, LAST_ROW } from "../WorksheetCanvas/constants"; import type WorksheetCanvas from "../WorksheetCanvas/worksheetCanvas"; import { headerColumnWidth, @@ -34,6 +35,10 @@ interface PointerEvents { const usePointer = (options: PointerSettings): PointerEvents => { const isSelecting = useRef(false); const isInsertingRef = useRef(false); + const isSelectingRows = useRef(false); + const initialRowRef = useRef(null); + const isSelectingColumns = useRef(false); + const initialColumnRef = useRef(null); const onPointerMove = useCallback( (event: PointerEvent): void => { @@ -43,10 +48,17 @@ const usePointer = (options: PointerSettings): PointerEvents => { return; } - if (!(isSelecting.current || isInsertingRef.current)) { + if ( + !( + isSelecting.current || + isInsertingRef.current || + isSelectingRows.current || + isSelectingColumns.current + ) + ) { return; } - const { canvasElement, model, worksheetCanvas } = options; + const { canvasElement, model, worksheetCanvas, refresh } = options; const canvas = canvasElement.current; const worksheet = worksheetCanvas.current; // Silence the linter @@ -57,6 +69,66 @@ const usePointer = (options: PointerSettings): PointerEvents => { const x = event.clientX - canvasRect.x; const y = event.clientY - canvasRect.y; + if (isSelectingRows.current) { + // Prevent text selection during row dragging + event.preventDefault(); + // Handle row selection dragging + if (initialRowRef.current === null) { + return; + } + let targetRow: number | null = null; + if (x >= 0 && x < headerColumnWidth && y >= headerRowHeight) { + const cell = worksheet.getCellByCoordinates(headerColumnWidth, y); + if (cell) targetRow = cell.row; + } else if (x >= headerColumnWidth && y >= headerRowHeight) { + const cell = worksheet.getCellByCoordinates(x, y); + if (cell) targetRow = cell.row; + } + + if (targetRow !== null) { + const initialRow = initialRowRef.current; + model.setSelectedCell(Math.min(initialRow, targetRow), 1); + model.setSelectedRange( + Math.min(initialRow, targetRow), + 1, + Math.max(initialRow, targetRow), + LAST_COLUMN, + ); + refresh(); + } + return; + } + + if (isSelectingColumns.current) { + // Prevent text selection during column dragging + event.preventDefault(); + // Handle column selection dragging + if (initialColumnRef.current === null) { + return; + } + let targetColumn: number | null = null; + if (x >= headerColumnWidth && y >= 0 && y < headerRowHeight) { + const cell = worksheet.getCellByCoordinates(x, headerRowHeight); + if (cell) targetColumn = cell.column; + } else if (x >= headerColumnWidth && y >= headerRowHeight) { + const cell = worksheet.getCellByCoordinates(x, y); + if (cell) targetColumn = cell.column; + } + + if (targetColumn !== null) { + const initialColumn = initialColumnRef.current; + model.setSelectedCell(1, Math.min(initialColumn, targetColumn)); + model.setSelectedRange( + 1, + Math.min(initialColumn, targetColumn), + LAST_ROW, + Math.max(initialColumn, targetColumn), + ); + refresh(); + } + return; + } + const cell = worksheet.getCellByCoordinates(x, y); if (!cell) { return; @@ -65,7 +137,7 @@ const usePointer = (options: PointerSettings): PointerEvents => { if (isSelecting.current) { options.onAreaSelecting(cell); } else if (isInsertingRef.current) { - const { refresh, workbookState } = options; + const { workbookState } = options; const editingCell = workbookState.getEditingCell(); if (!editingCell || !editingCell.referencedRange) { return; @@ -99,6 +171,16 @@ const usePointer = (options: PointerSettings): PointerEvents => { const { worksheetElement } = options; isInsertingRef.current = false; worksheetElement.current?.releasePointerCapture(event.pointerId); + } else if (isSelectingRows.current) { + const { worksheetElement } = options; + isSelectingRows.current = false; + initialRowRef.current = null; + worksheetElement.current?.releasePointerCapture(event.pointerId); + } else if (isSelectingColumns.current) { + const { worksheetElement } = options; + isSelectingColumns.current = false; + initialColumnRef.current = null; + worksheetElement.current?.releasePointerCapture(event.pointerId); } }, [options], @@ -157,7 +239,17 @@ const usePointer = (options: PointerSettings): PointerEvents => { // Click on a row number const cell = worksheet.getCellByCoordinates(headerColumnWidth, y); if (cell) { - onRowSelected(cell.row, event.shiftKey); + if (event.shiftKey) { + // Shift+click: extend selection + onRowSelected(cell.row, true); + } else { + // Regular click: start drag selection + event.preventDefault(); + initialRowRef.current = cell.row; + isSelectingRows.current = true; + worksheetWrapper.setPointerCapture(event.pointerId); + onRowSelected(cell.row, false); + } } } else if ( x > headerColumnWidth && @@ -168,7 +260,17 @@ const usePointer = (options: PointerSettings): PointerEvents => { // Click on a column letter const cell = worksheet.getCellByCoordinates(x, headerRowHeight); if (cell) { - onColumnSelected(cell.column, event.shiftKey); + if (event.shiftKey) { + // Shift+click: extend selection + onColumnSelected(cell.column, true); + } else { + // Regular click: start drag selection + event.preventDefault(); + initialColumnRef.current = cell.column; + isSelectingColumns.current = true; + worksheetWrapper.setPointerCapture(event.pointerId); + onColumnSelected(cell.column, false); + } } } return;