перепрыгивая текста
This commit is contained in:
@@ -127,7 +127,7 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
|
|||||||
|
|
||||||
const getCachedCellData = useCallback(
|
const getCachedCellData = useCallback(
|
||||||
(sheetType: SheetType): Record<string, CachedCellData> => {
|
(sheetType: SheetType): Record<string, CachedCellData> => {
|
||||||
const cacheKey = `${sheetType}-${revision}`
|
const cacheKey = `${sheetType}-${revision}-${JSON.stringify(columnWidths[sheetType])}`
|
||||||
|
|
||||||
if (cellDataCache.current[cacheKey]) {
|
if (cellDataCache.current[cacheKey]) {
|
||||||
return cellDataCache.current[cacheKey]
|
return cellDataCache.current[cacheKey]
|
||||||
@@ -147,23 +147,32 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const rightContentMap: Record<string, boolean> = {}
|
// 1. Находим ближайшую занятую колонку справа для каждой ячейки строки
|
||||||
|
const nextContentInRow: Record<number, number[]> = {}
|
||||||
for (let row = 0; row < ROW_COUNT; row++) {
|
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--) {
|
for (let col = COL_COUNT - 1; col >= 0; col--) {
|
||||||
const cellKey = `${row}-${col}`
|
const cellKey = `${row}-${col}`
|
||||||
rightContentMap[cellKey] = hasContentOnRight
|
nextContentInRow[row][col] = next // сохранить для использования ниже
|
||||||
if (allCellValues[cellKey]) {
|
if (allCellValues[cellKey]) {
|
||||||
hasContentOnRight = true
|
next = col // текущая колонка занята → "новый" предел
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. Заполняем cache
|
||||||
for (let row = 0; row < ROW_COUNT; row++) {
|
for (let row = 0; row < ROW_COUNT; row++) {
|
||||||
for (let col = 0; col < COL_COUNT; col++) {
|
for (let col = 0; col < COL_COUNT; col++) {
|
||||||
const cellKey = `${row}-${col}`
|
const cellKey = `${row}-${col}`
|
||||||
const rawValue = multiSheetEngine.getCellRawValue(sheetName, row, col)
|
const rawValue = multiSheetEngine.getCellRawValue(sheetName, row, col)
|
||||||
const displayValue = allCellValues[cellKey]
|
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 cellName = getColumnLabel(col) + (row + 1)
|
||||||
const baseFileValue = initialTemplateValues.current[cellName] ?? '' // Базовое значение из файла
|
const baseFileValue = initialTemplateValues.current[cellName] ?? '' // Базовое значение из файла
|
||||||
@@ -209,7 +218,8 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
|
|||||||
displayValue,
|
displayValue,
|
||||||
rawValue,
|
rawValue,
|
||||||
isModified,
|
isModified,
|
||||||
hasRightContent: rightContentMap[cellKey],
|
nextContentCol: nextCol,
|
||||||
|
availablePx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -226,7 +236,7 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
|
|||||||
cellDataCache.current[cacheKey] = cachedData
|
cellDataCache.current[cacheKey] = cachedData
|
||||||
return cachedData
|
return cachedData
|
||||||
},
|
},
|
||||||
[revision, multiSheetEngine, initialTemplateValues]
|
[revision, multiSheetEngine, initialTemplateValues, columnWidths]
|
||||||
)
|
)
|
||||||
|
|
||||||
const formulaBarValue = useMemo(() => {
|
const formulaBarValue = useMemo(() => {
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ export const Cell = memo(
|
|||||||
displayValue,
|
displayValue,
|
||||||
rawValue,
|
rawValue,
|
||||||
isModified,
|
isModified,
|
||||||
hasRightContent,
|
nextContentCol,
|
||||||
|
availablePx,
|
||||||
onCellClick,
|
onCellClick,
|
||||||
onCellDoubleClick,
|
onCellDoubleClick,
|
||||||
onCellValueChange,
|
onCellValueChange,
|
||||||
@@ -28,9 +29,7 @@ export const Cell = memo(
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const className = `border border-border ${isSelected ? 'bg-accent' : ''} ${
|
const className = `border border-border ${isModified && !mergedCellInfo?.isMerged ? 'bg-yellow-200' : ''}`
|
||||||
isModified && !mergedCellInfo?.isMerged ? 'bg-yellow-200' : ''
|
|
||||||
}`
|
|
||||||
|
|
||||||
// Отладка стилей для измененных ячеек
|
// Отладка стилей для измененных ячеек
|
||||||
if (isModified) {
|
if (isModified) {
|
||||||
@@ -108,6 +107,7 @@ export const Cell = memo(
|
|||||||
type="text"
|
type="text"
|
||||||
value={rawValue} // Во время редактирования показываем текущее редактируемое значение
|
value={rawValue} // Во время редактирования показываем текущее редактируемое значение
|
||||||
onChange={e => onCellValueChange(e.target.value)}
|
onChange={e => onCellValueChange(e.target.value)}
|
||||||
|
style={{ width: columnWidth - 4 }}
|
||||||
onBlur={e => {
|
onBlur={e => {
|
||||||
// Проверяем, не переходит ли фокус в formula bar
|
// Проверяем, не переходит ли фокус в formula bar
|
||||||
const relatedTarget = e.relatedTarget as HTMLElement
|
const relatedTarget = e.relatedTarget as HTMLElement
|
||||||
@@ -125,13 +125,14 @@ export const Cell = memo(
|
|||||||
stopEditing(false)
|
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"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
// Контейнер, который допускает перепрыгивание текста, если впереди есть свободное место
|
||||||
<div
|
<div
|
||||||
className="relative flex h-full w-full items-center px-2"
|
className="relative flex h-full w-full items-center px-2"
|
||||||
style={{
|
style={{
|
||||||
overflow: hasRightContent ? 'hidden' : 'visible',
|
overflow: availablePx > columnWidth ? 'visible' : 'hidden',
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
...(isSelected
|
...(isSelected
|
||||||
? { boxShadow: 'inset 0 0 0 2px hsl(var(--primary))' }
|
? { boxShadow: 'inset 0 0 0 2px hsl(var(--primary))' }
|
||||||
@@ -139,12 +140,17 @@ export const Cell = memo(
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="relative"
|
className="absolute left-1 top-0 flex h-full items-center"
|
||||||
style={{
|
style={{
|
||||||
overflow: hasRightContent ? 'hidden' : 'visible',
|
width:
|
||||||
textOverflow: hasRightContent ? 'ellipsis' : 'clip',
|
availablePx > columnWidth
|
||||||
|
? availablePx - 8
|
||||||
|
: columnWidth - 16,
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
maxWidth: hasRightContent ? `${columnWidth - 16}px` : 'none',
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
pointerEvents: 'none', // чтобы клики шли в "родную" ячейку
|
||||||
|
zIndex: 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{cellDisplayValue}
|
{cellDisplayValue}
|
||||||
|
|||||||
@@ -82,7 +82,8 @@ export const CellRenderer = ({
|
|||||||
displayValue={displayValue}
|
displayValue={displayValue}
|
||||||
rawValue={rawValue}
|
rawValue={rawValue}
|
||||||
isModified={cellData?.isModified || false}
|
isModified={cellData?.isModified || false}
|
||||||
hasRightContent={cellData?.hasRightContent || false}
|
nextContentCol={cellData?.nextContentCol || 0}
|
||||||
|
availablePx={cellData?.availablePx || widths[columnIndex]}
|
||||||
onCellClick={handleCellClick}
|
onCellClick={handleCellClick}
|
||||||
onCellDoubleClick={handleCellDoubleClick}
|
onCellDoubleClick={handleCellDoubleClick}
|
||||||
onCellValueChange={handleCellChange}
|
onCellValueChange={handleCellChange}
|
||||||
|
|||||||
@@ -107,14 +107,18 @@ export const Spreadsheet = ({
|
|||||||
style={{ height: gridSize.height }}
|
style={{ height: gridSize.height }}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
{Array.from({ length: ROW_COUNT }).map((_, rowIndex) => (
|
{Array.from({ length: ROW_COUNT }).map((_, rowIndex) => {
|
||||||
|
const isActiveRow =
|
||||||
|
selectedCell?.row === rowIndex && activeSheet === sheetType
|
||||||
|
return (
|
||||||
<div
|
<div
|
||||||
key={rowIndex}
|
key={rowIndex}
|
||||||
className="flex h-7 w-10 items-center justify-center border-b border-r border-border text-xs font-medium text-muted-foreground"
|
className={`flex h-7 w-10 items-center justify-center border-b border-r border-border text-xs font-medium ${isActiveRow ? 'bg-primary/15 font-medium text-foreground' : 'text-muted-foreground'}`}
|
||||||
>
|
>
|
||||||
{rowIndex + 1}
|
{rowIndex + 1}
|
||||||
</div>
|
</div>
|
||||||
))}
|
)
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -127,6 +131,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 =
|
||||||
|
selectedCell?.col === i && activeSheet === sheetType
|
||||||
const handleMouseDown = (e: React.MouseEvent) => {
|
const handleMouseDown = (e: React.MouseEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
const startX = e.clientX
|
const startX = e.clientX
|
||||||
@@ -148,7 +154,7 @@ export const Spreadsheet = ({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={i}
|
||||||
className="relative flex h-7 flex-shrink-0 items-center justify-center border-b border-r border-border bg-muted text-center text-xs font-medium text-muted-foreground"
|
className={`relative flex h-7 flex-shrink-0 items-center justify-center border-b border-r border-border bg-muted text-center text-xs font-medium ${isActiveColumn ? 'bg-primary/15 font-medium text-foreground' : 'text-muted-foreground'}`}
|
||||||
style={{ width: widths[i] }}
|
style={{ width: widths[i] }}
|
||||||
>
|
>
|
||||||
{getColumnLabel(i)}
|
{getColumnLabel(i)}
|
||||||
@@ -175,6 +181,8 @@ export const Spreadsheet = ({
|
|||||||
height={gridSize.height}
|
height={gridSize.height}
|
||||||
width={gridSize.width}
|
width={gridSize.width}
|
||||||
itemData={itemData}
|
itemData={itemData}
|
||||||
|
overscanColumnCount={20}
|
||||||
|
overscanRowCount={10}
|
||||||
onScroll={e => onGridScroll(sheetType, e)}
|
onScroll={e => onGridScroll(sheetType, e)}
|
||||||
>
|
>
|
||||||
{CellRenderer}
|
{CellRenderer}
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ export interface CachedCellData {
|
|||||||
displayValue: string
|
displayValue: string
|
||||||
rawValue: string
|
rawValue: string
|
||||||
isModified: boolean
|
isModified: boolean
|
||||||
hasRightContent: boolean
|
nextContentCol: number // индекс ближайшей занятой колонки (COL_COUNT, если нет)
|
||||||
|
availablePx: number // суммарная ширина, куда можно расширяться
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DualSpreadsheetProps {
|
export interface DualSpreadsheetProps {
|
||||||
@@ -41,7 +42,8 @@ export interface CellProps {
|
|||||||
displayValue: string // Отображаемое значение (вычисленное)
|
displayValue: string // Отображаемое значение (вычисленное)
|
||||||
rawValue: string // "Сырое" значение (формула)
|
rawValue: string // "Сырое" значение (формула)
|
||||||
isModified: boolean
|
isModified: boolean
|
||||||
hasRightContent: boolean // Есть ли содержимое в ячейках справа
|
nextContentCol: number // индекс ближайшей занятой колонки
|
||||||
|
availablePx: number // суммарная ширина, куда можно расширяться
|
||||||
onCellClick: (row: number, col: number) => void
|
onCellClick: (row: number, col: number) => void
|
||||||
onCellDoubleClick: (row: number, col: number) => void
|
onCellDoubleClick: (row: number, col: number) => void
|
||||||
onCellValueChange: (newValue: string) => void // Изменение значения во время редактирования
|
onCellValueChange: (newValue: string) => void // Изменение значения во время редактирования
|
||||||
|
|||||||
Reference in New Issue
Block a user