выделение ячеек

This commit is contained in:
2025-07-22 09:32:58 +03:00
parent a71f018608
commit ac8be1a347
6 changed files with 467 additions and 25 deletions

View File

@@ -16,6 +16,7 @@ import { FormulaBar, Spreadsheet } from './components'
import { useDraggableDivider } from './hooks/useDraggableDivider'
import {
CachedCellData,
CellSelection,
COL_COUNT,
DualSpreadsheetProps,
HighlightedCell,
@@ -28,7 +29,9 @@ import {
convertReferencesToHighlights,
createCellAddress,
createQualifiedCellReference,
createSelectionFromRange,
extractCellReferences,
getAllCellsInSelections,
getColumnLabel,
} from './utils'
@@ -75,6 +78,13 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
row: number
col: number
} | null>(null)
// Состояние для множественного выделения
const [multiSelection, setMultiSelection] = useState<CellSelection[]>([])
const [isDragging, setIsDragging] = useState(false)
const [dragStart, setDragStart] = useState<{
row: number
col: number
} | null>(null)
const [isEditing, setIsEditing] = useState(false)
const [editingValue, setEditingValue] = useState<string>('') // Локальное значение во время редактирования
// Источник ввода: 'cell' (редактирование внутри ячейки) или 'formula'
@@ -396,6 +406,8 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
const startEditing = useCallback(() => {
if (!selectedCell) return
// Сбрасываем множественное выделение при начале редактирования
setMultiSelection([])
const sheetName = SHEET_NAMES[activeSheet]
const rawValue = multiSheetEngine.getCellRawValue(
sheetName,
@@ -465,6 +477,8 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
)
updateRevision()
multiSheetEngine.debouncedRecalc()
// Сбрасываем множественное выделение при очистке одиночной ячейки
setMultiSelection([])
}, [activeSheet, selectedCell, multiSheetEngine, updateRevision])
const moveSelection = useCallback(
@@ -488,10 +502,30 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
)
setSelectedCell({ row: newRow, col: newCol })
// Сбрасываем множественное выделение при навигации
setMultiSelection([])
},
[selectedCell, isEditing, stopEditing]
)
// Функция для очистки множества выделенных ячеек
const handleClearMultipleCells = useCallback(() => {
if (multiSelection.length === 0) return
const sheetName = SHEET_NAMES[activeSheet]
const cellsToDelete = getAllCellsInSelections(multiSelection)
cellsToDelete.forEach(({ row, col }: { row: number; col: number }) => {
multiSheetEngine.setCellValueWithoutRecalc(sheetName, row, col, '')
})
updateRevision()
multiSheetEngine.debouncedRecalc()
// Очищаем выделение после удаления
setMultiSelection([])
}, [multiSelection, activeSheet, multiSheetEngine, updateRevision])
const handleKeyDown = useCallback(
(e: React.KeyboardEvent) => {
if (isEditing) {
@@ -518,8 +552,39 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
setEditingSource('cell')
startEditing()
},
Delete: handleClearCell,
Backspace: handleClearCell,
Delete: () => {
// Если есть множественное выделение, удаляем все выделенные ячейки
if (multiSelection.length > 0) {
handleClearMultipleCells()
} else {
handleClearCell()
}
},
Backspace: () => {
// Если есть множественное выделение, удаляем все выделенные ячейки
if (multiSelection.length > 0) {
handleClearMultipleCells()
} else {
handleClearCell()
}
},
Escape: () => {
// Сбрасываем множественное выделение
setMultiSelection([])
},
}
// Ctrl+A - выделить все ячейки
if (e.key === 'a' && (e.ctrlKey || e.metaKey)) {
e.preventDefault()
const allSelection = createSelectionFromRange(
0,
0,
ROW_COUNT - 1,
COL_COUNT - 1
)
setMultiSelection([allSelection])
return
}
if (keyHandlers[e.key]) {
@@ -532,11 +597,19 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
setIsEditing(true)
}
},
[selectedCell, isEditing, moveSelection, startEditing, handleClearCell]
[
selectedCell,
isEditing,
moveSelection,
startEditing,
handleClearCell,
multiSelection,
handleClearMultipleCells,
]
)
const handleCellClick = useCallback(
(row: number, col: number) => {
(row: number, col: number, event?: React.MouseEvent) => {
// console.log('🖱️ Обычный клик по ячейке:', {
// row,
//col,
@@ -547,12 +620,93 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
if (isEditing) {
stopEditing(true)
}
// Если удерживается Ctrl (или Cmd на Mac), добавляем к множественному выделению
if (event && (event.ctrlKey || event.metaKey)) {
const newSelection = createSelectionFromRange(row, col, row, col)
setMultiSelection(prev => [...prev, newSelection])
setSelectedCell({ row, col })
return
}
// Если удерживается Shift, расширяем выделение
if (event && event.shiftKey && selectedCell) {
const newSelection = createSelectionFromRange(
selectedCell.row,
selectedCell.col,
row,
col
)
setMultiSelection([newSelection])
return
}
// Обычный клик - очищаем множественное выделение
setMultiSelection([])
setSelectedCell({ row, col })
containerRefs.current[activeSheet]?.focus({ preventScroll: true })
},
[isEditing, stopEditing, activeSheet]
[isEditing, stopEditing, activeSheet, selectedCell]
)
// Новый обработчик для начала drag selection
const handleCellMouseDown = useCallback(
(row: number, col: number, event: React.MouseEvent) => {
// Игнорируем правый клик
if (event.button !== 0) return
// Если удерживаются модификаторы, не начинаем drag
if (event.ctrlKey || event.metaKey || event.shiftKey) return
setIsDragging(true)
setDragStart({ row, col })
setMultiSelection([])
setSelectedCell({ row, col })
},
[]
)
// Обработчик для drag selection
const handleCellMouseEnter = useCallback(
(row: number, col: number) => {
if (!isDragging || !dragStart) return
const newSelection = createSelectionFromRange(
dragStart.row,
dragStart.col,
row,
col
)
setMultiSelection([newSelection])
},
[isDragging, dragStart]
)
// Обработчик завершения drag selection
const handleCellMouseUp = useCallback(() => {
if (isDragging) {
setIsDragging(false)
setDragStart(null)
}
}, [isDragging])
// Глобальный обработчик для завершения drag selection
useEffect(() => {
const handleGlobalMouseUp = () => {
if (isDragging) {
setIsDragging(false)
setDragStart(null)
}
}
if (isDragging) {
document.addEventListener('mouseup', handleGlobalMouseUp)
return () => {
document.removeEventListener('mouseup', handleGlobalMouseUp)
}
}
}, [isDragging])
// Новый обработчик для кликов в режиме редактирования - вставляет ссылку
const handleCellReferenceClick = useCallback(
(targetSheetType: SheetType, row: number, col: number) => {
@@ -940,6 +1094,7 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
spreadsheetWidths={spreadsheetWidths}
getCachedCellData={getCachedCellData}
selectedCell={selectedCell}
multiSelection={multiSelection}
activeSheet={activeSheet}
isEditing={isEditing}
editingValue={editingValue}
@@ -948,6 +1103,9 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
handleCellDoubleClick={handleCellDoubleClick}
handleCellChange={handleCellChange}
handleCellReferenceClick={handleCellReferenceClick}
handleCellMouseDown={handleCellMouseDown}
handleCellMouseEnter={handleCellMouseEnter}
handleCellMouseUp={handleCellMouseUp}
isInEditMode={isEditing}
stopEditing={stopEditing}
setActiveSheet={setActiveSheet}