выделение ячеек
This commit is contained in:
@@ -16,6 +16,7 @@ import { FormulaBar, Spreadsheet } from './components'
|
|||||||
import { useDraggableDivider } from './hooks/useDraggableDivider'
|
import { useDraggableDivider } from './hooks/useDraggableDivider'
|
||||||
import {
|
import {
|
||||||
CachedCellData,
|
CachedCellData,
|
||||||
|
CellSelection,
|
||||||
COL_COUNT,
|
COL_COUNT,
|
||||||
DualSpreadsheetProps,
|
DualSpreadsheetProps,
|
||||||
HighlightedCell,
|
HighlightedCell,
|
||||||
@@ -28,7 +29,9 @@ import {
|
|||||||
convertReferencesToHighlights,
|
convertReferencesToHighlights,
|
||||||
createCellAddress,
|
createCellAddress,
|
||||||
createQualifiedCellReference,
|
createQualifiedCellReference,
|
||||||
|
createSelectionFromRange,
|
||||||
extractCellReferences,
|
extractCellReferences,
|
||||||
|
getAllCellsInSelections,
|
||||||
getColumnLabel,
|
getColumnLabel,
|
||||||
} from './utils'
|
} from './utils'
|
||||||
|
|
||||||
@@ -75,6 +78,13 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
|
|||||||
row: number
|
row: number
|
||||||
col: number
|
col: number
|
||||||
} | null>(null)
|
} | 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 [isEditing, setIsEditing] = useState(false)
|
||||||
const [editingValue, setEditingValue] = useState<string>('') // Локальное значение во время редактирования
|
const [editingValue, setEditingValue] = useState<string>('') // Локальное значение во время редактирования
|
||||||
// Источник ввода: 'cell' (редактирование внутри ячейки) или 'formula'
|
// Источник ввода: 'cell' (редактирование внутри ячейки) или 'formula'
|
||||||
@@ -396,6 +406,8 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
|
|||||||
|
|
||||||
const startEditing = useCallback(() => {
|
const startEditing = useCallback(() => {
|
||||||
if (!selectedCell) return
|
if (!selectedCell) return
|
||||||
|
// Сбрасываем множественное выделение при начале редактирования
|
||||||
|
setMultiSelection([])
|
||||||
const sheetName = SHEET_NAMES[activeSheet]
|
const sheetName = SHEET_NAMES[activeSheet]
|
||||||
const rawValue = multiSheetEngine.getCellRawValue(
|
const rawValue = multiSheetEngine.getCellRawValue(
|
||||||
sheetName,
|
sheetName,
|
||||||
@@ -465,6 +477,8 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
|
|||||||
)
|
)
|
||||||
updateRevision()
|
updateRevision()
|
||||||
multiSheetEngine.debouncedRecalc()
|
multiSheetEngine.debouncedRecalc()
|
||||||
|
// Сбрасываем множественное выделение при очистке одиночной ячейки
|
||||||
|
setMultiSelection([])
|
||||||
}, [activeSheet, selectedCell, multiSheetEngine, updateRevision])
|
}, [activeSheet, selectedCell, multiSheetEngine, updateRevision])
|
||||||
|
|
||||||
const moveSelection = useCallback(
|
const moveSelection = useCallback(
|
||||||
@@ -488,10 +502,30 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
|
|||||||
)
|
)
|
||||||
|
|
||||||
setSelectedCell({ row: newRow, col: newCol })
|
setSelectedCell({ row: newRow, col: newCol })
|
||||||
|
// Сбрасываем множественное выделение при навигации
|
||||||
|
setMultiSelection([])
|
||||||
},
|
},
|
||||||
[selectedCell, isEditing, stopEditing]
|
[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(
|
const handleKeyDown = useCallback(
|
||||||
(e: React.KeyboardEvent) => {
|
(e: React.KeyboardEvent) => {
|
||||||
if (isEditing) {
|
if (isEditing) {
|
||||||
@@ -518,8 +552,39 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
|
|||||||
setEditingSource('cell')
|
setEditingSource('cell')
|
||||||
startEditing()
|
startEditing()
|
||||||
},
|
},
|
||||||
Delete: handleClearCell,
|
Delete: () => {
|
||||||
Backspace: handleClearCell,
|
// Если есть множественное выделение, удаляем все выделенные ячейки
|
||||||
|
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]) {
|
if (keyHandlers[e.key]) {
|
||||||
@@ -532,11 +597,19 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
|
|||||||
setIsEditing(true)
|
setIsEditing(true)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[selectedCell, isEditing, moveSelection, startEditing, handleClearCell]
|
[
|
||||||
|
selectedCell,
|
||||||
|
isEditing,
|
||||||
|
moveSelection,
|
||||||
|
startEditing,
|
||||||
|
handleClearCell,
|
||||||
|
multiSelection,
|
||||||
|
handleClearMultipleCells,
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleCellClick = useCallback(
|
const handleCellClick = useCallback(
|
||||||
(row: number, col: number) => {
|
(row: number, col: number, event?: React.MouseEvent) => {
|
||||||
// console.log('🖱️ Обычный клик по ячейке:', {
|
// console.log('🖱️ Обычный клик по ячейке:', {
|
||||||
// row,
|
// row,
|
||||||
//col,
|
//col,
|
||||||
@@ -547,12 +620,93 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
|
|||||||
if (isEditing) {
|
if (isEditing) {
|
||||||
stopEditing(true)
|
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 })
|
setSelectedCell({ row, col })
|
||||||
containerRefs.current[activeSheet]?.focus({ preventScroll: true })
|
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(
|
const handleCellReferenceClick = useCallback(
|
||||||
(targetSheetType: SheetType, row: number, col: number) => {
|
(targetSheetType: SheetType, row: number, col: number) => {
|
||||||
@@ -940,6 +1094,7 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
|
|||||||
spreadsheetWidths={spreadsheetWidths}
|
spreadsheetWidths={spreadsheetWidths}
|
||||||
getCachedCellData={getCachedCellData}
|
getCachedCellData={getCachedCellData}
|
||||||
selectedCell={selectedCell}
|
selectedCell={selectedCell}
|
||||||
|
multiSelection={multiSelection}
|
||||||
activeSheet={activeSheet}
|
activeSheet={activeSheet}
|
||||||
isEditing={isEditing}
|
isEditing={isEditing}
|
||||||
editingValue={editingValue}
|
editingValue={editingValue}
|
||||||
@@ -948,6 +1103,9 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
|
|||||||
handleCellDoubleClick={handleCellDoubleClick}
|
handleCellDoubleClick={handleCellDoubleClick}
|
||||||
handleCellChange={handleCellChange}
|
handleCellChange={handleCellChange}
|
||||||
handleCellReferenceClick={handleCellReferenceClick}
|
handleCellReferenceClick={handleCellReferenceClick}
|
||||||
|
handleCellMouseDown={handleCellMouseDown}
|
||||||
|
handleCellMouseEnter={handleCellMouseEnter}
|
||||||
|
handleCellMouseUp={handleCellMouseUp}
|
||||||
isInEditMode={isEditing}
|
isInEditMode={isEditing}
|
||||||
stopEditing={stopEditing}
|
stopEditing={stopEditing}
|
||||||
setActiveSheet={setActiveSheet}
|
setActiveSheet={setActiveSheet}
|
||||||
|
|||||||
@@ -9,6 +9,12 @@ export const Cell = memo(
|
|||||||
columnWidth,
|
columnWidth,
|
||||||
columnWidths,
|
columnWidths,
|
||||||
isSelected,
|
isSelected,
|
||||||
|
isInMultiSelection,
|
||||||
|
multiSelectionActive,
|
||||||
|
selectionTop = false,
|
||||||
|
selectionBottom = false,
|
||||||
|
selectionLeft = false,
|
||||||
|
selectionRight = false,
|
||||||
isEditing,
|
isEditing,
|
||||||
displayValue,
|
displayValue,
|
||||||
rawValue,
|
rawValue,
|
||||||
@@ -19,6 +25,9 @@ export const Cell = memo(
|
|||||||
onCellDoubleClick,
|
onCellDoubleClick,
|
||||||
onCellValueChange,
|
onCellValueChange,
|
||||||
onCellReferenceClick,
|
onCellReferenceClick,
|
||||||
|
onCellMouseDown,
|
||||||
|
onCellMouseEnter,
|
||||||
|
onCellMouseUp,
|
||||||
isInEditMode,
|
isInEditMode,
|
||||||
stopEditing,
|
stopEditing,
|
||||||
setActiveSheet,
|
setActiveSheet,
|
||||||
@@ -35,13 +44,29 @@ export const Cell = memo(
|
|||||||
// Определяем классы CSS для подсветки и обычного состояния
|
// Определяем классы CSS для подсветки и обычного состояния
|
||||||
let className = 'border border-border'
|
let className = 'border border-border'
|
||||||
|
|
||||||
// Приоритет стилей: подсветка формул > изменения > обычное состояние
|
// Базовый style для ячейки
|
||||||
|
let cellStyle: CSSProperties = {
|
||||||
|
...style,
|
||||||
|
overflow: 'visible',
|
||||||
|
padding: 0,
|
||||||
|
userSelect: 'none',
|
||||||
|
}
|
||||||
|
|
||||||
|
// Приоритет стилей: подсветка формул > множественное выделение > изменения > обычное состояние
|
||||||
if (highlightColor) {
|
if (highlightColor) {
|
||||||
className += ` ${highlightColor}`
|
className += ` ${highlightColor}`
|
||||||
} else if (isModified && !mergedCellInfo?.isMerged) {
|
} else if (isModified && !mergedCellInfo?.isMerged) {
|
||||||
className += ' bg-yellow-200'
|
className += ' bg-yellow-200'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Настраиваем box-shadow, фон и z-index для выделенной области
|
||||||
|
if (isInMultiSelection) {
|
||||||
|
cellStyle = {
|
||||||
|
...cellStyle,
|
||||||
|
backgroundColor: 'rgba(59,130,246,0.08)', // голубоватый оттенок
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Отладка стилей для измененных ячеек
|
// Отладка стилей для измененных ячеек
|
||||||
if (isModified) {
|
if (isModified) {
|
||||||
// console.log(`🎨 Стили для измененной ячейки ${rowIndex},${colIndex}:`, //{
|
// console.log(`🎨 Стили для измененной ячейки ${rowIndex},${colIndex}:`, //{
|
||||||
@@ -54,13 +79,6 @@ export const Cell = memo(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Для объединенных ячеек нужно рассчитать правильные размеры
|
// Для объединенных ячеек нужно рассчитать правильные размеры
|
||||||
let cellStyle: CSSProperties = {
|
|
||||||
...style,
|
|
||||||
overflow: 'visible',
|
|
||||||
padding: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Если это основная ячейка объединенной группы, расширяем её размеры
|
|
||||||
if (
|
if (
|
||||||
mergedCellInfo?.isMainCell &&
|
mergedCellInfo?.isMainCell &&
|
||||||
mergedCellInfo.rowSpan &&
|
mergedCellInfo.rowSpan &&
|
||||||
@@ -140,8 +158,7 @@ export const Cell = memo(
|
|||||||
//)
|
//)
|
||||||
}
|
}
|
||||||
|
|
||||||
setActiveSheet(sheetType)
|
onCellClick(rowIndex, colIndex, e)
|
||||||
onCellClick(rowIndex, colIndex)
|
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
setActiveSheet,
|
setActiveSheet,
|
||||||
@@ -162,6 +179,11 @@ export const Cell = memo(
|
|||||||
|
|
||||||
const handleMouseDown = useCallback(
|
const handleMouseDown = useCallback(
|
||||||
(e: React.MouseEvent) => {
|
(e: React.MouseEvent) => {
|
||||||
|
if (!isInEditMode) {
|
||||||
|
// блокируем стандартное выделение текста браузером
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
// Используем mousedown, чтобы сработать раньше blur FormulaBar
|
// Используем mousedown, чтобы сработать раньше blur FormulaBar
|
||||||
if (isInEditMode && onCellReferenceClick && !isSelected) {
|
if (isInEditMode && onCellReferenceClick && !isSelected) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -174,6 +196,12 @@ export const Cell = memo(
|
|||||||
onCellReferenceClick(sheetType, rowIndex, colIndex)
|
onCellReferenceClick(sheetType, rowIndex, colIndex)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Обработчик для начала drag selection
|
||||||
|
if (onCellMouseDown) {
|
||||||
|
setActiveSheet(sheetType)
|
||||||
|
onCellMouseDown(rowIndex, colIndex, e)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
isInEditMode,
|
isInEditMode,
|
||||||
@@ -182,14 +210,30 @@ export const Cell = memo(
|
|||||||
sheetType,
|
sheetType,
|
||||||
rowIndex,
|
rowIndex,
|
||||||
colIndex,
|
colIndex,
|
||||||
|
onCellMouseDown,
|
||||||
|
setActiveSheet,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const handleMouseEnter = useCallback(() => {
|
||||||
|
if (onCellMouseEnter) {
|
||||||
|
onCellMouseEnter(rowIndex, colIndex)
|
||||||
|
}
|
||||||
|
}, [onCellMouseEnter, rowIndex, colIndex])
|
||||||
|
|
||||||
|
const handleMouseUp = useCallback(() => {
|
||||||
|
if (onCellMouseUp) {
|
||||||
|
onCellMouseUp()
|
||||||
|
}
|
||||||
|
}, [onCellMouseUp])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={className}
|
className={className}
|
||||||
style={cellStyle}
|
style={cellStyle}
|
||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseUp={handleMouseUp}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
onDoubleClick={handleDoubleClick}
|
onDoubleClick={handleDoubleClick}
|
||||||
>
|
>
|
||||||
@@ -226,7 +270,7 @@ export const Cell = memo(
|
|||||||
style={{
|
style={{
|
||||||
overflow: availablePx > columnWidth ? 'visible' : 'hidden',
|
overflow: availablePx > columnWidth ? 'visible' : 'hidden',
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
...(isSelected
|
...(isSelected && !multiSelectionActive
|
||||||
? { boxShadow: 'inset 0 0 0 2px hsl(var(--primary))' }
|
? { boxShadow: 'inset 0 0 0 2px hsl(var(--primary))' }
|
||||||
: highlightColor
|
: highlightColor
|
||||||
? { boxShadow: 'inset 0 0 0 2px currentColor' }
|
? { boxShadow: 'inset 0 0 0 2px currentColor' }
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { CellRendererProps } from '../types'
|
import { CellRendererProps } from '../types'
|
||||||
import { findMergedCellInfo } from '../utils'
|
import { findMergedCellInfo, isInAnySelection } from '../utils'
|
||||||
import { Cell } from './Cell'
|
import { Cell } from './Cell'
|
||||||
|
|
||||||
// Рендер-проп для ячеек грида
|
// Рендер-проп для ячеек грида
|
||||||
@@ -13,6 +13,7 @@ export const CellRenderer = ({
|
|||||||
sheetType,
|
sheetType,
|
||||||
widths,
|
widths,
|
||||||
selectedCell,
|
selectedCell,
|
||||||
|
multiSelection,
|
||||||
activeSheet,
|
activeSheet,
|
||||||
isEditing,
|
isEditing,
|
||||||
editingValue,
|
editingValue,
|
||||||
@@ -23,6 +24,9 @@ export const CellRenderer = ({
|
|||||||
handleCellDoubleClick,
|
handleCellDoubleClick,
|
||||||
handleCellChange,
|
handleCellChange,
|
||||||
handleCellReferenceClick,
|
handleCellReferenceClick,
|
||||||
|
handleCellMouseDown,
|
||||||
|
handleCellMouseEnter,
|
||||||
|
handleCellMouseUp,
|
||||||
isInEditMode,
|
isInEditMode,
|
||||||
stopEditing,
|
stopEditing,
|
||||||
setActiveSheet,
|
setActiveSheet,
|
||||||
@@ -33,6 +37,40 @@ export const CellRenderer = ({
|
|||||||
selectedCell?.row === rowIndex &&
|
selectedCell?.row === rowIndex &&
|
||||||
selectedCell?.col === columnIndex &&
|
selectedCell?.col === columnIndex &&
|
||||||
activeSheet === sheetType
|
activeSheet === sheetType
|
||||||
|
|
||||||
|
// Проверяем, является ли ячейка частью множественного выделения
|
||||||
|
const isInMultiSelection =
|
||||||
|
sheetType === activeSheet &&
|
||||||
|
isInAnySelection(rowIndex, columnIndex, multiSelection)
|
||||||
|
|
||||||
|
const multiSelectionActive =
|
||||||
|
sheetType === activeSheet && multiSelection.length > 0
|
||||||
|
|
||||||
|
// Вычисляем границы выделения, если ячейка внутри выделения
|
||||||
|
let selectionTop = false,
|
||||||
|
selectionBottom = false,
|
||||||
|
selectionLeft = false,
|
||||||
|
selectionRight = false
|
||||||
|
|
||||||
|
if (isInMultiSelection && multiSelection.length > 0) {
|
||||||
|
const bounds = {
|
||||||
|
top: Math.min(...multiSelection.map(s => Math.min(s.startRow, s.endRow))),
|
||||||
|
bottom: Math.max(
|
||||||
|
...multiSelection.map(s => Math.max(s.startRow, s.endRow))
|
||||||
|
),
|
||||||
|
left: Math.min(
|
||||||
|
...multiSelection.map(s => Math.min(s.startCol, s.endCol))
|
||||||
|
),
|
||||||
|
right: Math.max(
|
||||||
|
...multiSelection.map(s => Math.max(s.startCol, s.endCol))
|
||||||
|
),
|
||||||
|
}
|
||||||
|
selectionTop = rowIndex === bounds.top
|
||||||
|
selectionBottom = rowIndex === bounds.bottom
|
||||||
|
selectionLeft = columnIndex === bounds.left
|
||||||
|
selectionRight = columnIndex === bounds.right
|
||||||
|
}
|
||||||
|
|
||||||
const isCellEditing = isCellSelected && isEditing
|
const isCellEditing = isCellSelected && isEditing
|
||||||
|
|
||||||
const cellKey = `${rowIndex}-${columnIndex}`
|
const cellKey = `${rowIndex}-${columnIndex}`
|
||||||
@@ -92,6 +130,12 @@ export const CellRenderer = ({
|
|||||||
columnWidth={widths[columnIndex]}
|
columnWidth={widths[columnIndex]}
|
||||||
columnWidths={widths}
|
columnWidths={widths}
|
||||||
isSelected={isCellSelected}
|
isSelected={isCellSelected}
|
||||||
|
isInMultiSelection={isInMultiSelection}
|
||||||
|
multiSelectionActive={multiSelectionActive}
|
||||||
|
selectionTop={selectionTop}
|
||||||
|
selectionBottom={selectionBottom}
|
||||||
|
selectionLeft={selectionLeft}
|
||||||
|
selectionRight={selectionRight}
|
||||||
isEditing={isCellEditing}
|
isEditing={isCellEditing}
|
||||||
displayValue={displayValue}
|
displayValue={displayValue}
|
||||||
rawValue={rawValue}
|
rawValue={rawValue}
|
||||||
@@ -102,6 +146,9 @@ export const CellRenderer = ({
|
|||||||
onCellDoubleClick={handleCellDoubleClick}
|
onCellDoubleClick={handleCellDoubleClick}
|
||||||
onCellValueChange={handleCellChange}
|
onCellValueChange={handleCellChange}
|
||||||
onCellReferenceClick={handleCellReferenceClick}
|
onCellReferenceClick={handleCellReferenceClick}
|
||||||
|
onCellMouseDown={handleCellMouseDown}
|
||||||
|
onCellMouseEnter={handleCellMouseEnter}
|
||||||
|
onCellMouseUp={handleCellMouseUp}
|
||||||
isInEditMode={isEditing}
|
isInEditMode={isEditing}
|
||||||
stopEditing={stopEditing}
|
stopEditing={stopEditing}
|
||||||
setActiveSheet={setActiveSheet}
|
setActiveSheet={setActiveSheet}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export const Spreadsheet = ({
|
|||||||
spreadsheetWidths,
|
spreadsheetWidths,
|
||||||
getCachedCellData,
|
getCachedCellData,
|
||||||
selectedCell,
|
selectedCell,
|
||||||
|
multiSelection,
|
||||||
activeSheet,
|
activeSheet,
|
||||||
isEditing,
|
isEditing,
|
||||||
editingValue,
|
editingValue,
|
||||||
@@ -19,6 +20,9 @@ export const Spreadsheet = ({
|
|||||||
handleCellDoubleClick,
|
handleCellDoubleClick,
|
||||||
handleCellChange,
|
handleCellChange,
|
||||||
handleCellReferenceClick,
|
handleCellReferenceClick,
|
||||||
|
handleCellMouseDown,
|
||||||
|
handleCellMouseEnter,
|
||||||
|
handleCellMouseUp,
|
||||||
isInEditMode,
|
isInEditMode,
|
||||||
stopEditing,
|
stopEditing,
|
||||||
setActiveSheet,
|
setActiveSheet,
|
||||||
@@ -39,16 +43,22 @@ export const Spreadsheet = ({
|
|||||||
sheetType,
|
sheetType,
|
||||||
widths,
|
widths,
|
||||||
selectedCell,
|
selectedCell,
|
||||||
|
multiSelection,
|
||||||
activeSheet,
|
activeSheet,
|
||||||
isEditing,
|
isEditing,
|
||||||
editingValue,
|
editingValue,
|
||||||
cachedData,
|
cachedData,
|
||||||
mergedCells,
|
mergedCells,
|
||||||
highlightedCells,
|
highlightedCells,
|
||||||
|
multiSelectionActive:
|
||||||
|
activeSheet === sheetType && multiSelection.length > 0,
|
||||||
handleCellClick,
|
handleCellClick,
|
||||||
handleCellDoubleClick,
|
handleCellDoubleClick,
|
||||||
handleCellChange,
|
handleCellChange,
|
||||||
handleCellReferenceClick,
|
handleCellReferenceClick,
|
||||||
|
handleCellMouseDown,
|
||||||
|
handleCellMouseEnter,
|
||||||
|
handleCellMouseUp,
|
||||||
isInEditMode,
|
isInEditMode,
|
||||||
stopEditing,
|
stopEditing,
|
||||||
setActiveSheet,
|
setActiveSheet,
|
||||||
@@ -67,6 +77,9 @@ export const Spreadsheet = ({
|
|||||||
const parentRef = useRef<HTMLDivElement>(null)
|
const parentRef = useRef<HTMLDivElement>(null)
|
||||||
const [gridSize, setGridSize] = useState({ width: 0, height: 0 })
|
const [gridSize, setGridSize] = useState({ width: 0, height: 0 })
|
||||||
|
|
||||||
|
// Состояние текущего скролла грида
|
||||||
|
const [scrollPos, setScrollPos] = useState({ left: 0, top: 0 })
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const parent = parentRef.current
|
const parent = parentRef.current
|
||||||
if (!parent) return
|
if (!parent) return
|
||||||
@@ -82,13 +95,25 @@ export const Spreadsheet = ({
|
|||||||
return () => resizeObserver.disconnect()
|
return () => resizeObserver.disconnect()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const selectedRows = new Set<number>()
|
||||||
|
const selectedCols = new Set<number>()
|
||||||
|
if (sheetType === activeSheet && multiSelection.length > 0) {
|
||||||
|
multiSelection.forEach(sel => {
|
||||||
|
const top = Math.min(sel.startRow, sel.endRow)
|
||||||
|
const bottom = Math.max(sel.startRow, sel.endRow)
|
||||||
|
const left = Math.min(sel.startCol, sel.endCol)
|
||||||
|
const right = Math.max(sel.startCol, sel.endCol)
|
||||||
|
for (let r = top; r <= bottom; r++) selectedRows.add(r)
|
||||||
|
for (let c = left; c <= right; c++) selectedCols.add(c)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={parentRef}
|
ref={parentRef}
|
||||||
className="flex min-w-0 flex-col overflow-hidden"
|
className="flex min-w-0 flex-col overflow-hidden"
|
||||||
style={{
|
style={{
|
||||||
flexBasis: `${spreadsheetWidths[SHEET_NAMES[sheetType]] * 100}%`,
|
flexBasis: `${spreadsheetWidths[SHEET_NAMES[sheetType]] * 100}%`,
|
||||||
transition: 'none',
|
|
||||||
}}
|
}}
|
||||||
onClick={() => setActiveSheet(sheetType)}
|
onClick={() => setActiveSheet(sheetType)}
|
||||||
>
|
>
|
||||||
@@ -98,6 +123,7 @@ export const Spreadsheet = ({
|
|||||||
}}
|
}}
|
||||||
className="flex h-full flex-col overflow-hidden border bg-card shadow-sm"
|
className="flex h-full flex-col overflow-hidden border bg-card shadow-sm"
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
|
style={{ userSelect: isEditing ? 'text' : 'none' }}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
{/* Spreadsheet Body */}
|
{/* Spreadsheet Body */}
|
||||||
@@ -128,7 +154,9 @@ export const Spreadsheet = ({
|
|||||||
<div>
|
<div>
|
||||||
{Array.from({ length: ROW_COUNT }).map((_, rowIndex) => {
|
{Array.from({ length: ROW_COUNT }).map((_, rowIndex) => {
|
||||||
const isActiveRow =
|
const isActiveRow =
|
||||||
selectedCell?.row === rowIndex && activeSheet === sheetType
|
(selectedCell?.row === rowIndex &&
|
||||||
|
activeSheet === sheetType) ||
|
||||||
|
selectedRows.has(rowIndex)
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={rowIndex}
|
key={rowIndex}
|
||||||
@@ -153,7 +181,8 @@ export const Spreadsheet = ({
|
|||||||
<div className="flex">
|
<div className="flex">
|
||||||
{Array.from({ length: COL_COUNT }).map((_, i) => {
|
{Array.from({ length: COL_COUNT }).map((_, i) => {
|
||||||
const isActiveColumn =
|
const isActiveColumn =
|
||||||
selectedCell?.col === i && activeSheet === sheetType
|
(selectedCell?.col === i && activeSheet === sheetType) ||
|
||||||
|
selectedCols.has(i)
|
||||||
const handleMouseDown = (e: React.MouseEvent) => {
|
const handleMouseDown = (e: React.MouseEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
const startX = e.clientX
|
const startX = e.clientX
|
||||||
@@ -190,7 +219,61 @@ export const Spreadsheet = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Grid */}
|
{/* Grid */}
|
||||||
<div className="min-h-0 flex-1">
|
<div className="relative min-h-0 flex-1">
|
||||||
|
{/* selection overlay */}
|
||||||
|
{sheetType === activeSheet &&
|
||||||
|
multiSelection.length > 0 &&
|
||||||
|
(() => {
|
||||||
|
const bounds = {
|
||||||
|
topRow: Math.min(
|
||||||
|
...multiSelection.map(sel =>
|
||||||
|
Math.min(sel.startRow, sel.endRow)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
bottomRow: Math.max(
|
||||||
|
...multiSelection.map(sel =>
|
||||||
|
Math.max(sel.startRow, sel.endRow)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
leftCol: Math.min(
|
||||||
|
...multiSelection.map(sel =>
|
||||||
|
Math.min(sel.startCol, sel.endCol)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
rightCol: Math.max(
|
||||||
|
...multiSelection.map(sel =>
|
||||||
|
Math.max(sel.startCol, sel.endCol)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
const topPx =
|
||||||
|
bounds.topRow * 28 /* ROW_HEIGHT */ - scrollPos.top
|
||||||
|
const heightPx = (bounds.bottomRow - bounds.topRow + 1) * 28
|
||||||
|
const leftPx =
|
||||||
|
widths.slice(0, bounds.leftCol).reduce((a, b) => a + b, 0) -
|
||||||
|
scrollPos.left
|
||||||
|
const widthPx = widths
|
||||||
|
.slice(bounds.leftCol, bounds.rightCol + 1)
|
||||||
|
.reduce((a, b) => a + b, 0)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: topPx,
|
||||||
|
left: leftPx,
|
||||||
|
width: widthPx,
|
||||||
|
height: heightPx,
|
||||||
|
border: '2px solid hsl(var(--primary))',
|
||||||
|
pointerEvents: 'none',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
transition: 'none',
|
||||||
|
zIndex: 5,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})()}
|
||||||
<VariableSizeGrid
|
<VariableSizeGrid
|
||||||
ref={(el: VariableSizeGrid | null) => {
|
ref={(el: VariableSizeGrid | null) => {
|
||||||
gridRefs.current[sheetType] = el
|
gridRefs.current[sheetType] = el
|
||||||
@@ -204,7 +287,10 @@ export const Spreadsheet = ({
|
|||||||
itemData={itemData}
|
itemData={itemData}
|
||||||
overscanColumnCount={20}
|
overscanColumnCount={20}
|
||||||
overscanRowCount={10}
|
overscanRowCount={10}
|
||||||
onScroll={e => onGridScroll(sheetType, e)}
|
onScroll={e => {
|
||||||
|
setScrollPos({ left: e.scrollLeft, top: e.scrollTop })
|
||||||
|
onGridScroll(sheetType, e)
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{CellRenderer}
|
{CellRenderer}
|
||||||
</VariableSizeGrid>
|
</VariableSizeGrid>
|
||||||
|
|||||||
@@ -24,6 +24,20 @@ export interface HighlightedCell {
|
|||||||
color: string // класс CSS для цвета подсветки
|
color: string // класс CSS для цвета подсветки
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Интерфейс для области выделения
|
||||||
|
export interface CellSelection {
|
||||||
|
startRow: number
|
||||||
|
startCol: number
|
||||||
|
endRow: number
|
||||||
|
endCol: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// Интерфейс для выделения множества ячеек
|
||||||
|
export interface MultiSelection {
|
||||||
|
sheet: SheetType
|
||||||
|
selections: CellSelection[]
|
||||||
|
}
|
||||||
|
|
||||||
// Кэшированные данные ячеек для оптимизации
|
// Кэшированные данные ячеек для оптимизации
|
||||||
export interface CachedCellData {
|
export interface CachedCellData {
|
||||||
displayValue: string
|
displayValue: string
|
||||||
@@ -48,13 +62,20 @@ export interface CellProps {
|
|||||||
columnWidth: number
|
columnWidth: number
|
||||||
columnWidths: number[] // Добавляем массив всех ширин колонок
|
columnWidths: number[] // Добавляем массив всех ширин колонок
|
||||||
isSelected: boolean
|
isSelected: boolean
|
||||||
|
isInMultiSelection: boolean // Является ли ячейка частью множественного выделения
|
||||||
|
multiSelectionActive: boolean // Есть ли вообще активное множественное выделение
|
||||||
|
// Флаги, является ли ячейка на краю выделения
|
||||||
|
selectionTop?: boolean
|
||||||
|
selectionBottom?: boolean
|
||||||
|
selectionLeft?: boolean
|
||||||
|
selectionRight?: boolean
|
||||||
isEditing: boolean
|
isEditing: boolean
|
||||||
displayValue: string // Отображаемое значение (вычисленное)
|
displayValue: string // Отображаемое значение (вычисленное)
|
||||||
rawValue: string // "Сырое" значение (формула)
|
rawValue: string // "Сырое" значение (формула)
|
||||||
isModified: boolean
|
isModified: boolean
|
||||||
nextContentCol: number // индекс ближайшей занятой колонки
|
nextContentCol: number // индекс ближайшей занятой колонки
|
||||||
availablePx: number // суммарная ширина, куда можно расширяться
|
availablePx: number // суммарная ширина, куда можно расширяться
|
||||||
onCellClick: (row: number, col: number) => void
|
onCellClick: (row: number, col: number, event?: React.MouseEvent) => void
|
||||||
onCellDoubleClick: (row: number, col: number) => void
|
onCellDoubleClick: (row: number, col: number) => void
|
||||||
onCellValueChange: (newValue: string) => void // Изменение значения во время редактирования
|
onCellValueChange: (newValue: string) => void // Изменение значения во время редактирования
|
||||||
onCellReferenceClick?: (
|
onCellReferenceClick?: (
|
||||||
@@ -62,6 +83,9 @@ export interface CellProps {
|
|||||||
row: number,
|
row: number,
|
||||||
col: number
|
col: number
|
||||||
) => void // Клик для вставки ссылки
|
) => void // Клик для вставки ссылки
|
||||||
|
onCellMouseDown?: (row: number, col: number, event: React.MouseEvent) => void // Для начала drag selection
|
||||||
|
onCellMouseEnter?: (row: number, col: number) => void // Для drag selection
|
||||||
|
onCellMouseUp?: () => void // Для завершения drag selection
|
||||||
isInEditMode: boolean // Глобальный режим редактирования
|
isInEditMode: boolean // Глобальный режим редактирования
|
||||||
stopEditing: (save: boolean) => void
|
stopEditing: (save: boolean) => void
|
||||||
setActiveSheet: (type: SheetType) => void
|
setActiveSheet: (type: SheetType) => void
|
||||||
@@ -102,13 +126,15 @@ export interface CellRendererData {
|
|||||||
sheetType: SheetType
|
sheetType: SheetType
|
||||||
widths: number[]
|
widths: number[]
|
||||||
selectedCell: { row: number; col: number } | null
|
selectedCell: { row: number; col: number } | null
|
||||||
|
multiSelection: CellSelection[]
|
||||||
activeSheet: SheetType
|
activeSheet: SheetType
|
||||||
isEditing: boolean
|
isEditing: boolean
|
||||||
editingValue: string
|
editingValue: string
|
||||||
cachedData: Record<string, CachedCellData>
|
cachedData: Record<string, CachedCellData>
|
||||||
mergedCells?: MergedCell[]
|
mergedCells?: MergedCell[]
|
||||||
highlightedCells: HighlightedCell[] // Добавляем подсвеченные ячейки
|
highlightedCells: HighlightedCell[] // Добавляем подсвеченные ячейки
|
||||||
handleCellClick: (row: number, col: number) => void
|
multiSelectionActive: boolean
|
||||||
|
handleCellClick: (row: number, col: number, event?: React.MouseEvent) => void
|
||||||
handleCellDoubleClick: (row: number, col: number) => void
|
handleCellDoubleClick: (row: number, col: number) => void
|
||||||
handleCellChange: (newValue: string) => void
|
handleCellChange: (newValue: string) => void
|
||||||
handleCellReferenceClick?: (
|
handleCellReferenceClick?: (
|
||||||
@@ -116,6 +142,13 @@ export interface CellRendererData {
|
|||||||
row: number,
|
row: number,
|
||||||
col: number
|
col: number
|
||||||
) => void // Клик для вставки ссылки
|
) => void // Клик для вставки ссылки
|
||||||
|
handleCellMouseDown?: (
|
||||||
|
row: number,
|
||||||
|
col: number,
|
||||||
|
event: React.MouseEvent
|
||||||
|
) => void
|
||||||
|
handleCellMouseEnter?: (row: number, col: number) => void
|
||||||
|
handleCellMouseUp?: () => void
|
||||||
isInEditMode: boolean // Глобальный режим редактирования
|
isInEditMode: boolean // Глобальный режим редактирования
|
||||||
stopEditing: (save: boolean) => void
|
stopEditing: (save: boolean) => void
|
||||||
setActiveSheet: (type: SheetType) => void
|
setActiveSheet: (type: SheetType) => void
|
||||||
@@ -135,11 +168,12 @@ export interface SpreadsheetProps {
|
|||||||
spreadsheetWidths: { L: number; R: number }
|
spreadsheetWidths: { L: number; R: number }
|
||||||
getCachedCellData: (sheetType: SheetType) => Record<string, CachedCellData>
|
getCachedCellData: (sheetType: SheetType) => Record<string, CachedCellData>
|
||||||
selectedCell: { row: number; col: number } | null
|
selectedCell: { row: number; col: number } | null
|
||||||
|
multiSelection: CellSelection[]
|
||||||
activeSheet: SheetType
|
activeSheet: SheetType
|
||||||
isEditing: boolean
|
isEditing: boolean
|
||||||
editingValue: string
|
editingValue: string
|
||||||
highlightedCells: HighlightedCell[] // Добавляем подсвеченные ячейки
|
highlightedCells: HighlightedCell[] // Добавляем подсвеченные ячейки
|
||||||
handleCellClick: (row: number, col: number) => void
|
handleCellClick: (row: number, col: number, event?: React.MouseEvent) => void
|
||||||
handleCellDoubleClick: (row: number, col: number) => void
|
handleCellDoubleClick: (row: number, col: number) => void
|
||||||
handleCellChange: (newValue: string) => void
|
handleCellChange: (newValue: string) => void
|
||||||
handleCellReferenceClick?: (
|
handleCellReferenceClick?: (
|
||||||
@@ -147,6 +181,13 @@ export interface SpreadsheetProps {
|
|||||||
row: number,
|
row: number,
|
||||||
col: number
|
col: number
|
||||||
) => void // Клик для вставки ссылки
|
) => void // Клик для вставки ссылки
|
||||||
|
handleCellMouseDown?: (
|
||||||
|
row: number,
|
||||||
|
col: number,
|
||||||
|
event: React.MouseEvent
|
||||||
|
) => void
|
||||||
|
handleCellMouseEnter?: (row: number, col: number) => void
|
||||||
|
handleCellMouseUp?: () => void
|
||||||
isInEditMode: boolean // Глобальный режим редактирования
|
isInEditMode: boolean // Глобальный режим редактирования
|
||||||
stopEditing: (save: boolean) => void
|
stopEditing: (save: boolean) => void
|
||||||
setActiveSheet: (type: SheetType) => void
|
setActiveSheet: (type: SheetType) => void
|
||||||
|
|||||||
@@ -303,3 +303,69 @@ export function convertReferencesToHighlights(
|
|||||||
|
|
||||||
return highlights
|
return highlights
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Функции для работы с выделением множества ячеек
|
||||||
|
import { CellSelection } from './types'
|
||||||
|
|
||||||
|
export const isInSelection = (
|
||||||
|
row: number,
|
||||||
|
col: number,
|
||||||
|
selection: CellSelection
|
||||||
|
): boolean => {
|
||||||
|
const { startRow, startCol, endRow, endCol } = selection
|
||||||
|
const minRow = Math.min(startRow, endRow)
|
||||||
|
const maxRow = Math.max(startRow, endRow)
|
||||||
|
const minCol = Math.min(startCol, endCol)
|
||||||
|
const maxCol = Math.max(startCol, endCol)
|
||||||
|
|
||||||
|
return row >= minRow && row <= maxRow && col >= minCol && col <= maxCol
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isInAnySelection = (
|
||||||
|
row: number,
|
||||||
|
col: number,
|
||||||
|
selections: CellSelection[]
|
||||||
|
): boolean => {
|
||||||
|
return selections.some(selection => isInSelection(row, col, selection))
|
||||||
|
}
|
||||||
|
|
||||||
|
export const normalizeSelection = (selection: CellSelection): CellSelection => {
|
||||||
|
const { startRow, startCol, endRow, endCol } = selection
|
||||||
|
return {
|
||||||
|
startRow: Math.min(startRow, endRow),
|
||||||
|
startCol: Math.min(startCol, endCol),
|
||||||
|
endRow: Math.max(startRow, endRow),
|
||||||
|
endCol: Math.max(startCol, endCol),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getAllCellsInSelections = (
|
||||||
|
selections: CellSelection[]
|
||||||
|
): Array<{ row: number; col: number }> => {
|
||||||
|
const cells: Array<{ row: number; col: number }> = []
|
||||||
|
|
||||||
|
selections.forEach(selection => {
|
||||||
|
const normalized = normalizeSelection(selection)
|
||||||
|
for (let row = normalized.startRow; row <= normalized.endRow; row++) {
|
||||||
|
for (let col = normalized.startCol; col <= normalized.endCol; col++) {
|
||||||
|
cells.push({ row, col })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return cells
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createSelectionFromRange = (
|
||||||
|
startRow: number,
|
||||||
|
startCol: number,
|
||||||
|
endRow: number,
|
||||||
|
endCol: number
|
||||||
|
): CellSelection => {
|
||||||
|
return {
|
||||||
|
startRow,
|
||||||
|
startCol,
|
||||||
|
endRow,
|
||||||
|
endCol,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user