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

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}

View File

@@ -9,6 +9,12 @@ export const Cell = memo(
columnWidth,
columnWidths,
isSelected,
isInMultiSelection,
multiSelectionActive,
selectionTop = false,
selectionBottom = false,
selectionLeft = false,
selectionRight = false,
isEditing,
displayValue,
rawValue,
@@ -19,6 +25,9 @@ export const Cell = memo(
onCellDoubleClick,
onCellValueChange,
onCellReferenceClick,
onCellMouseDown,
onCellMouseEnter,
onCellMouseUp,
isInEditMode,
stopEditing,
setActiveSheet,
@@ -35,13 +44,29 @@ export const Cell = memo(
// Определяем классы CSS для подсветки и обычного состояния
let className = 'border border-border'
// Приоритет стилей: подсветка формул > изменения > обычное состояние
// Базовый style для ячейки
let cellStyle: CSSProperties = {
...style,
overflow: 'visible',
padding: 0,
userSelect: 'none',
}
// Приоритет стилей: подсветка формул > множественное выделение > изменения > обычное состояние
if (highlightColor) {
className += ` ${highlightColor}`
} else if (isModified && !mergedCellInfo?.isMerged) {
className += ' bg-yellow-200'
}
// Настраиваем box-shadow, фон и z-index для выделенной области
if (isInMultiSelection) {
cellStyle = {
...cellStyle,
backgroundColor: 'rgba(59,130,246,0.08)', // голубоватый оттенок
}
}
// Отладка стилей для измененных ячеек
if (isModified) {
// console.log(`🎨 Стили для измененной ячейки ${rowIndex},${colIndex}:`, //{
@@ -54,13 +79,6 @@ export const Cell = memo(
}
// Для объединенных ячеек нужно рассчитать правильные размеры
let cellStyle: CSSProperties = {
...style,
overflow: 'visible',
padding: 0,
}
// Если это основная ячейка объединенной группы, расширяем её размеры
if (
mergedCellInfo?.isMainCell &&
mergedCellInfo.rowSpan &&
@@ -140,8 +158,7 @@ export const Cell = memo(
//)
}
setActiveSheet(sheetType)
onCellClick(rowIndex, colIndex)
onCellClick(rowIndex, colIndex, e)
},
[
setActiveSheet,
@@ -162,6 +179,11 @@ export const Cell = memo(
const handleMouseDown = useCallback(
(e: React.MouseEvent) => {
if (!isInEditMode) {
// блокируем стандартное выделение текста браузером
e.preventDefault()
}
// Используем mousedown, чтобы сработать раньше blur FormulaBar
if (isInEditMode && onCellReferenceClick && !isSelected) {
e.preventDefault()
@@ -174,6 +196,12 @@ export const Cell = memo(
onCellReferenceClick(sheetType, rowIndex, colIndex)
return
}
// Обработчик для начала drag selection
if (onCellMouseDown) {
setActiveSheet(sheetType)
onCellMouseDown(rowIndex, colIndex, e)
}
},
[
isInEditMode,
@@ -182,14 +210,30 @@ export const Cell = memo(
sheetType,
rowIndex,
colIndex,
onCellMouseDown,
setActiveSheet,
]
)
const handleMouseEnter = useCallback(() => {
if (onCellMouseEnter) {
onCellMouseEnter(rowIndex, colIndex)
}
}, [onCellMouseEnter, rowIndex, colIndex])
const handleMouseUp = useCallback(() => {
if (onCellMouseUp) {
onCellMouseUp()
}
}, [onCellMouseUp])
return (
<div
className={className}
style={cellStyle}
onMouseDown={handleMouseDown}
onMouseEnter={handleMouseEnter}
onMouseUp={handleMouseUp}
onClick={handleClick}
onDoubleClick={handleDoubleClick}
>
@@ -226,7 +270,7 @@ export const Cell = memo(
style={{
overflow: availablePx > columnWidth ? 'visible' : 'hidden',
whiteSpace: 'nowrap',
...(isSelected
...(isSelected && !multiSelectionActive
? { boxShadow: 'inset 0 0 0 2px hsl(var(--primary))' }
: highlightColor
? { boxShadow: 'inset 0 0 0 2px currentColor' }

View File

@@ -1,5 +1,5 @@
import { CellRendererProps } from '../types'
import { findMergedCellInfo } from '../utils'
import { findMergedCellInfo, isInAnySelection } from '../utils'
import { Cell } from './Cell'
// Рендер-проп для ячеек грида
@@ -13,6 +13,7 @@ export const CellRenderer = ({
sheetType,
widths,
selectedCell,
multiSelection,
activeSheet,
isEditing,
editingValue,
@@ -23,6 +24,9 @@ export const CellRenderer = ({
handleCellDoubleClick,
handleCellChange,
handleCellReferenceClick,
handleCellMouseDown,
handleCellMouseEnter,
handleCellMouseUp,
isInEditMode,
stopEditing,
setActiveSheet,
@@ -33,6 +37,40 @@ export const CellRenderer = ({
selectedCell?.row === rowIndex &&
selectedCell?.col === columnIndex &&
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 cellKey = `${rowIndex}-${columnIndex}`
@@ -92,6 +130,12 @@ export const CellRenderer = ({
columnWidth={widths[columnIndex]}
columnWidths={widths}
isSelected={isCellSelected}
isInMultiSelection={isInMultiSelection}
multiSelectionActive={multiSelectionActive}
selectionTop={selectionTop}
selectionBottom={selectionBottom}
selectionLeft={selectionLeft}
selectionRight={selectionRight}
isEditing={isCellEditing}
displayValue={displayValue}
rawValue={rawValue}
@@ -102,6 +146,9 @@ export const CellRenderer = ({
onCellDoubleClick={handleCellDoubleClick}
onCellValueChange={handleCellChange}
onCellReferenceClick={handleCellReferenceClick}
onCellMouseDown={handleCellMouseDown}
onCellMouseEnter={handleCellMouseEnter}
onCellMouseUp={handleCellMouseUp}
isInEditMode={isEditing}
stopEditing={stopEditing}
setActiveSheet={setActiveSheet}

View File

@@ -11,6 +11,7 @@ export const Spreadsheet = ({
spreadsheetWidths,
getCachedCellData,
selectedCell,
multiSelection,
activeSheet,
isEditing,
editingValue,
@@ -19,6 +20,9 @@ export const Spreadsheet = ({
handleCellDoubleClick,
handleCellChange,
handleCellReferenceClick,
handleCellMouseDown,
handleCellMouseEnter,
handleCellMouseUp,
isInEditMode,
stopEditing,
setActiveSheet,
@@ -39,16 +43,22 @@ export const Spreadsheet = ({
sheetType,
widths,
selectedCell,
multiSelection,
activeSheet,
isEditing,
editingValue,
cachedData,
mergedCells,
highlightedCells,
multiSelectionActive:
activeSheet === sheetType && multiSelection.length > 0,
handleCellClick,
handleCellDoubleClick,
handleCellChange,
handleCellReferenceClick,
handleCellMouseDown,
handleCellMouseEnter,
handleCellMouseUp,
isInEditMode,
stopEditing,
setActiveSheet,
@@ -67,6 +77,9 @@ export const Spreadsheet = ({
const parentRef = useRef<HTMLDivElement>(null)
const [gridSize, setGridSize] = useState({ width: 0, height: 0 })
// Состояние текущего скролла грида
const [scrollPos, setScrollPos] = useState({ left: 0, top: 0 })
useEffect(() => {
const parent = parentRef.current
if (!parent) return
@@ -82,13 +95,25 @@ export const Spreadsheet = ({
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 (
<div
ref={parentRef}
className="flex min-w-0 flex-col overflow-hidden"
style={{
flexBasis: `${spreadsheetWidths[SHEET_NAMES[sheetType]] * 100}%`,
transition: 'none',
}}
onClick={() => setActiveSheet(sheetType)}
>
@@ -98,6 +123,7 @@ export const Spreadsheet = ({
}}
className="flex h-full flex-col overflow-hidden border bg-card shadow-sm"
onKeyDown={handleKeyDown}
style={{ userSelect: isEditing ? 'text' : 'none' }}
tabIndex={0}
>
{/* Spreadsheet Body */}
@@ -128,7 +154,9 @@ export const Spreadsheet = ({
<div>
{Array.from({ length: ROW_COUNT }).map((_, rowIndex) => {
const isActiveRow =
selectedCell?.row === rowIndex && activeSheet === sheetType
(selectedCell?.row === rowIndex &&
activeSheet === sheetType) ||
selectedRows.has(rowIndex)
return (
<div
key={rowIndex}
@@ -153,7 +181,8 @@ export const Spreadsheet = ({
<div className="flex">
{Array.from({ length: COL_COUNT }).map((_, i) => {
const isActiveColumn =
selectedCell?.col === i && activeSheet === sheetType
(selectedCell?.col === i && activeSheet === sheetType) ||
selectedCols.has(i)
const handleMouseDown = (e: React.MouseEvent) => {
e.preventDefault()
const startX = e.clientX
@@ -190,7 +219,61 @@ export const Spreadsheet = ({
</div>
{/* 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
ref={(el: VariableSizeGrid | null) => {
gridRefs.current[sheetType] = el
@@ -204,7 +287,10 @@ export const Spreadsheet = ({
itemData={itemData}
overscanColumnCount={20}
overscanRowCount={10}
onScroll={e => onGridScroll(sheetType, e)}
onScroll={e => {
setScrollPos({ left: e.scrollLeft, top: e.scrollTop })
onGridScroll(sheetType, e)
}}
>
{CellRenderer}
</VariableSizeGrid>

View File

@@ -24,6 +24,20 @@ export interface HighlightedCell {
color: string // класс CSS для цвета подсветки
}
// Интерфейс для области выделения
export interface CellSelection {
startRow: number
startCol: number
endRow: number
endCol: number
}
// Интерфейс для выделения множества ячеек
export interface MultiSelection {
sheet: SheetType
selections: CellSelection[]
}
// Кэшированные данные ячеек для оптимизации
export interface CachedCellData {
displayValue: string
@@ -48,13 +62,20 @@ export interface CellProps {
columnWidth: number
columnWidths: number[] // Добавляем массив всех ширин колонок
isSelected: boolean
isInMultiSelection: boolean // Является ли ячейка частью множественного выделения
multiSelectionActive: boolean // Есть ли вообще активное множественное выделение
// Флаги, является ли ячейка на краю выделения
selectionTop?: boolean
selectionBottom?: boolean
selectionLeft?: boolean
selectionRight?: boolean
isEditing: boolean
displayValue: string // Отображаемое значение (вычисленное)
rawValue: string // "Сырое" значение (формула)
isModified: boolean
nextContentCol: number // индекс ближайшей занятой колонки
availablePx: number // суммарная ширина, куда можно расширяться
onCellClick: (row: number, col: number) => void
onCellClick: (row: number, col: number, event?: React.MouseEvent) => void
onCellDoubleClick: (row: number, col: number) => void
onCellValueChange: (newValue: string) => void // Изменение значения во время редактирования
onCellReferenceClick?: (
@@ -62,6 +83,9 @@ export interface CellProps {
row: number,
col: number
) => 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 // Глобальный режим редактирования
stopEditing: (save: boolean) => void
setActiveSheet: (type: SheetType) => void
@@ -102,13 +126,15 @@ export interface CellRendererData {
sheetType: SheetType
widths: number[]
selectedCell: { row: number; col: number } | null
multiSelection: CellSelection[]
activeSheet: SheetType
isEditing: boolean
editingValue: string
cachedData: Record<string, CachedCellData>
mergedCells?: MergedCell[]
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
handleCellChange: (newValue: string) => void
handleCellReferenceClick?: (
@@ -116,6 +142,13 @@ export interface CellRendererData {
row: number,
col: number
) => void // Клик для вставки ссылки
handleCellMouseDown?: (
row: number,
col: number,
event: React.MouseEvent
) => void
handleCellMouseEnter?: (row: number, col: number) => void
handleCellMouseUp?: () => void
isInEditMode: boolean // Глобальный режим редактирования
stopEditing: (save: boolean) => void
setActiveSheet: (type: SheetType) => void
@@ -135,11 +168,12 @@ export interface SpreadsheetProps {
spreadsheetWidths: { L: number; R: number }
getCachedCellData: (sheetType: SheetType) => Record<string, CachedCellData>
selectedCell: { row: number; col: number } | null
multiSelection: CellSelection[]
activeSheet: SheetType
isEditing: boolean
editingValue: string
highlightedCells: HighlightedCell[] // Добавляем подсвеченные ячейки
handleCellClick: (row: number, col: number) => void
handleCellClick: (row: number, col: number, event?: React.MouseEvent) => void
handleCellDoubleClick: (row: number, col: number) => void
handleCellChange: (newValue: string) => void
handleCellReferenceClick?: (
@@ -147,6 +181,13 @@ export interface SpreadsheetProps {
row: number,
col: number
) => void // Клик для вставки ссылки
handleCellMouseDown?: (
row: number,
col: number,
event: React.MouseEvent
) => void
handleCellMouseEnter?: (row: number, col: number) => void
handleCellMouseUp?: () => void
isInEditMode: boolean // Глобальный режим редактирования
stopEditing: (save: boolean) => void
setActiveSheet: (type: SheetType) => void

View File

@@ -303,3 +303,69 @@ export function convertReferencesToHighlights(
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,
}
}