From 5defbed5d33217eb13e668661f39b9f084fd333e Mon Sep 17 00:00:00 2001 From: tlartem Date: Sun, 20 Jul 2025 19:42:48 +0300 Subject: [PATCH] =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D0=BF=D1=80=D1=8B?= =?UTF-8?q?=D0=B3=D0=B8=D0=B2=D0=B0=D1=8F=20=D1=82=D0=B5=D0=BA=D1=81=D1=82?= =?UTF-8?q?=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DualSpreadsheet/DualSpreadsheet.tsx | 24 ++++++++++++----- .../DualSpreadsheet/components/Cell.tsx | 26 ++++++++++++------- .../components/CellRenderer.tsx | 3 ++- .../components/Spreadsheet.tsx | 26 ++++++++++++------- src/components/DualSpreadsheet/types.ts | 6 +++-- 5 files changed, 56 insertions(+), 29 deletions(-) diff --git a/src/components/DualSpreadsheet/DualSpreadsheet.tsx b/src/components/DualSpreadsheet/DualSpreadsheet.tsx index 2b9b4ee..d0e8cad 100644 --- a/src/components/DualSpreadsheet/DualSpreadsheet.tsx +++ b/src/components/DualSpreadsheet/DualSpreadsheet.tsx @@ -127,7 +127,7 @@ const DualSpreadsheet: FC = ({ const getCachedCellData = useCallback( (sheetType: SheetType): Record => { - const cacheKey = `${sheetType}-${revision}` + const cacheKey = `${sheetType}-${revision}-${JSON.stringify(columnWidths[sheetType])}` if (cellDataCache.current[cacheKey]) { return cellDataCache.current[cacheKey] @@ -147,23 +147,32 @@ const DualSpreadsheet: FC = ({ } } - const rightContentMap: Record = {} + // 1. Находим ближайшую занятую колонку справа для каждой ячейки строки + const nextContentInRow: Record = {} for (let row = 0; row < ROW_COUNT; row++) { - let hasContentOnRight = false + nextContentInRow[row] = Array(COL_COUNT).fill(COL_COUNT) + let next = COL_COUNT for (let col = COL_COUNT - 1; col >= 0; col--) { const cellKey = `${row}-${col}` - rightContentMap[cellKey] = hasContentOnRight + nextContentInRow[row][col] = next // сохранить для использования ниже if (allCellValues[cellKey]) { - hasContentOnRight = true + next = col // текущая колонка занята → "новый" предел } } } + // 2. Заполняем cache for (let row = 0; row < ROW_COUNT; row++) { for (let col = 0; col < COL_COUNT; col++) { const cellKey = `${row}-${col}` const rawValue = multiSheetEngine.getCellRawValue(sheetName, row, col) const displayValue = allCellValues[cellKey] + const nextCol = nextContentInRow[row][col] + + // ширина от текущей колонки до nextCol-1 включительно + const availablePx = columnWidths[sheetType] + .slice(col, nextCol) + .reduce((sum, w) => sum + w, 0) const cellName = getColumnLabel(col) + (row + 1) const baseFileValue = initialTemplateValues.current[cellName] ?? '' // Базовое значение из файла @@ -209,7 +218,8 @@ const DualSpreadsheet: FC = ({ displayValue, rawValue, isModified, - hasRightContent: rightContentMap[cellKey], + nextContentCol: nextCol, + availablePx, } } } @@ -226,7 +236,7 @@ const DualSpreadsheet: FC = ({ cellDataCache.current[cacheKey] = cachedData return cachedData }, - [revision, multiSheetEngine, initialTemplateValues] + [revision, multiSheetEngine, initialTemplateValues, columnWidths] ) const formulaBarValue = useMemo(() => { diff --git a/src/components/DualSpreadsheet/components/Cell.tsx b/src/components/DualSpreadsheet/components/Cell.tsx index 33a6942..86775f6 100644 --- a/src/components/DualSpreadsheet/components/Cell.tsx +++ b/src/components/DualSpreadsheet/components/Cell.tsx @@ -13,7 +13,8 @@ export const Cell = memo( displayValue, rawValue, isModified, - hasRightContent, + nextContentCol, + availablePx, onCellClick, onCellDoubleClick, onCellValueChange, @@ -28,9 +29,7 @@ export const Cell = memo( return null } - const className = `border border-border ${isSelected ? 'bg-accent' : ''} ${ - isModified && !mergedCellInfo?.isMerged ? 'bg-yellow-200' : '' - }` + const className = `border border-border ${isModified && !mergedCellInfo?.isMerged ? 'bg-yellow-200' : ''}` // Отладка стилей для измененных ячеек if (isModified) { @@ -108,6 +107,7 @@ export const Cell = memo( type="text" value={rawValue} // Во время редактирования показываем текущее редактируемое значение onChange={e => onCellValueChange(e.target.value)} + style={{ width: columnWidth - 4 }} onBlur={e => { // Проверяем, не переходит ли фокус в formula bar const relatedTarget = e.relatedTarget as HTMLElement @@ -125,13 +125,14 @@ export const Cell = memo( stopEditing(false) } }} - className="absolute left-0 top-0 z-20 box-border h-full w-full border-2 border-primary px-2 py-1" + className="absolute left-0 top-0 z-20 box-border h-full border-2 border-primary px-2 py-1" /> ) : ( + // Контейнер, который допускает перепрыгивание текста, если впереди есть свободное место
columnWidth ? 'visible' : 'hidden', whiteSpace: 'nowrap', ...(isSelected ? { boxShadow: 'inset 0 0 0 2px hsl(var(--primary))' } @@ -139,12 +140,17 @@ export const Cell = memo( }} >
columnWidth + ? availablePx - 8 + : columnWidth - 16, whiteSpace: 'nowrap', - maxWidth: hasRightContent ? `${columnWidth - 16}px` : 'none', + overflow: 'hidden', + textOverflow: 'ellipsis', + pointerEvents: 'none', // чтобы клики шли в "родную" ячейку + zIndex: 1, }} > {cellDisplayValue} diff --git a/src/components/DualSpreadsheet/components/CellRenderer.tsx b/src/components/DualSpreadsheet/components/CellRenderer.tsx index 694e36c..86c7510 100644 --- a/src/components/DualSpreadsheet/components/CellRenderer.tsx +++ b/src/components/DualSpreadsheet/components/CellRenderer.tsx @@ -82,7 +82,8 @@ export const CellRenderer = ({ displayValue={displayValue} rawValue={rawValue} isModified={cellData?.isModified || false} - hasRightContent={cellData?.hasRightContent || false} + nextContentCol={cellData?.nextContentCol || 0} + availablePx={cellData?.availablePx || widths[columnIndex]} onCellClick={handleCellClick} onCellDoubleClick={handleCellDoubleClick} onCellValueChange={handleCellChange} diff --git a/src/components/DualSpreadsheet/components/Spreadsheet.tsx b/src/components/DualSpreadsheet/components/Spreadsheet.tsx index c511ea6..d7b478d 100644 --- a/src/components/DualSpreadsheet/components/Spreadsheet.tsx +++ b/src/components/DualSpreadsheet/components/Spreadsheet.tsx @@ -107,14 +107,18 @@ export const Spreadsheet = ({ style={{ height: gridSize.height }} >
- {Array.from({ length: ROW_COUNT }).map((_, rowIndex) => ( -
- {rowIndex + 1} -
- ))} + {Array.from({ length: ROW_COUNT }).map((_, rowIndex) => { + const isActiveRow = + selectedCell?.row === rowIndex && activeSheet === sheetType + return ( +
+ {rowIndex + 1} +
+ ) + })}
@@ -127,6 +131,8 @@ export const Spreadsheet = ({ >
{Array.from({ length: COL_COUNT }).map((_, i) => { + const isActiveColumn = + selectedCell?.col === i && activeSheet === sheetType const handleMouseDown = (e: React.MouseEvent) => { e.preventDefault() const startX = e.clientX @@ -148,7 +154,7 @@ export const Spreadsheet = ({ return (
{getColumnLabel(i)} @@ -175,6 +181,8 @@ export const Spreadsheet = ({ height={gridSize.height} width={gridSize.width} itemData={itemData} + overscanColumnCount={20} + overscanRowCount={10} onScroll={e => onGridScroll(sheetType, e)} > {CellRenderer} diff --git a/src/components/DualSpreadsheet/types.ts b/src/components/DualSpreadsheet/types.ts index bc98555..249c5df 100644 --- a/src/components/DualSpreadsheet/types.ts +++ b/src/components/DualSpreadsheet/types.ts @@ -21,7 +21,8 @@ export interface CachedCellData { displayValue: string rawValue: string isModified: boolean - hasRightContent: boolean + nextContentCol: number // индекс ближайшей занятой колонки (COL_COUNT, если нет) + availablePx: number // суммарная ширина, куда можно расширяться } export interface DualSpreadsheetProps { @@ -41,7 +42,8 @@ export interface CellProps { displayValue: string // Отображаемое значение (вычисленное) rawValue: string // "Сырое" значение (формула) isModified: boolean - hasRightContent: boolean // Есть ли содержимое в ячейках справа + nextContentCol: number // индекс ближайшей занятой колонки + availablePx: number // суммарная ширина, куда можно расширяться onCellClick: (row: number, col: number) => void onCellDoubleClick: (row: number, col: number) => void onCellValueChange: (newValue: string) => void // Изменение значения во время редактирования