UPDATE: Double click in the outline handle fills column

This also removes React from the equation.
So all event handling is done outside of the React loop.
This simplifies some things and helps us in a possible move
away from React.

This is closer to how we deal with the column and row handle resizers.

I think it works quite well and it is more future proof.

But TBH I just want to try it out and see what is the DX after this.

Fixes #359
This commit is contained in:
Nicolás Hatcher
2025-06-07 10:57:12 +02:00
committed by Nicolás Hatcher Andrés
parent 2eb9266c30
commit 138a483c65
4 changed files with 282 additions and 307 deletions

View File

@@ -24,7 +24,7 @@ import {
TOOLBAR_HEIGHT, TOOLBAR_HEIGHT,
} from "../constants"; } from "../constants";
import type { Cell } from "../types"; import type { Cell } from "../types";
import { AreaType, type WorkbookState } from "../workbookState"; import type { WorkbookState } from "../workbookState";
import CellContextMenu from "./CellContextMenu"; import CellContextMenu from "./CellContextMenu";
import usePointer from "./usePointer"; import usePointer from "./usePointer";
@@ -59,7 +59,6 @@ const Worksheet = forwardRef(
const spacerElement = useRef<HTMLDivElement>(null); const spacerElement = useRef<HTMLDivElement>(null);
const cellOutline = useRef<HTMLDivElement>(null); const cellOutline = useRef<HTMLDivElement>(null);
const areaOutline = useRef<HTMLDivElement>(null); const areaOutline = useRef<HTMLDivElement>(null);
const cellOutlineHandle = useRef<HTMLDivElement>(null);
const extendToOutline = useRef<HTMLDivElement>(null); const extendToOutline = useRef<HTMLDivElement>(null);
const columnResizeGuide = useRef<HTMLDivElement>(null); const columnResizeGuide = useRef<HTMLDivElement>(null);
const rowResizeGuide = useRef<HTMLDivElement>(null); const rowResizeGuide = useRef<HTMLDivElement>(null);
@@ -85,7 +84,6 @@ const Worksheet = forwardRef(
const worksheetRef = worksheetElement.current; const worksheetRef = worksheetElement.current;
const outline = cellOutline.current; const outline = cellOutline.current;
const handle = cellOutlineHandle.current;
const area = areaOutline.current; const area = areaOutline.current;
const extendTo = extendToOutline.current; const extendTo = extendToOutline.current;
const editor = editorElement.current; const editor = editorElement.current;
@@ -97,7 +95,6 @@ const Worksheet = forwardRef(
!columnHeadersRef || !columnHeadersRef ||
!worksheetRef || !worksheetRef ||
!outline || !outline ||
!handle ||
!area || !area ||
!extendTo || !extendTo ||
!scrollElement.current || !scrollElement.current ||
@@ -118,7 +115,6 @@ const Worksheet = forwardRef(
rowGuide: rowGuideRef, rowGuide: rowGuideRef,
columnHeaders: columnHeadersRef, columnHeaders: columnHeadersRef,
cellOutline: outline, cellOutline: outline,
cellOutlineHandle: handle,
areaOutline: area, areaOutline: area,
extendToOutline: extendTo, extendToOutline: extendTo,
editor: editor, editor: editor,
@@ -191,203 +187,74 @@ const Worksheet = forwardRef(
worksheetCanvas.current = canvas; worksheetCanvas.current = canvas;
}); });
const { onPointerMove, onPointerDown, onPointerHandleDown, onPointerUp } = const { onPointerMove, onPointerDown, onPointerUp } = usePointer({
usePointer({ model,
model, workbookState,
workbookState, refresh,
refresh, onColumnSelected: (column: number, shift: boolean) => {
onColumnSelected: (column: number, shift: boolean) => { let firstColumn = column;
let firstColumn = column; let lastColumn = column;
let lastColumn = column; if (shift) {
if (shift) { const { range } = model.getSelectedView();
const { range } = model.getSelectedView(); firstColumn = Math.min(range[1], column, range[3]);
firstColumn = Math.min(range[1], column, range[3]); lastColumn = Math.max(range[3], column, range[1]);
lastColumn = Math.max(range[3], column, range[1]); }
} model.setSelectedCell(1, firstColumn);
model.setSelectedCell(1, firstColumn); model.setSelectedRange(1, firstColumn, LAST_ROW, lastColumn);
model.setSelectedRange(1, firstColumn, LAST_ROW, lastColumn); refresh();
refresh(); },
}, onRowSelected: (row: number, shift: boolean) => {
onRowSelected: (row: number, shift: boolean) => { let firstRow = row;
let firstRow = row; let lastRow = row;
let lastRow = row; if (shift) {
if (shift) { const { range } = model.getSelectedView();
const { range } = model.getSelectedView(); firstRow = Math.min(range[0], row, range[2]);
firstRow = Math.min(range[0], row, range[2]); lastRow = Math.max(range[2], row, range[0]);
lastRow = Math.max(range[2], row, range[0]); }
} model.setSelectedCell(firstRow, 1);
model.setSelectedCell(firstRow, 1); model.setSelectedRange(firstRow, 1, lastRow, LAST_COLUMN);
model.setSelectedRange(firstRow, 1, lastRow, LAST_COLUMN); refresh();
refresh(); },
}, onAllSheetSelected: () => {
onAllSheetSelected: () => { model.setSelectedCell(1, 1);
model.setSelectedCell(1, 1); model.setSelectedRange(1, 1, LAST_ROW, LAST_COLUMN);
model.setSelectedRange(1, 1, LAST_ROW, LAST_COLUMN); },
}, onCellSelected: (cell: Cell, event: React.MouseEvent) => {
onCellSelected: (cell: Cell, event: React.MouseEvent) => { event.preventDefault();
event.preventDefault(); event.stopPropagation();
event.stopPropagation(); model.setSelectedCell(cell.row, cell.column);
model.setSelectedCell(cell.row, cell.column); refresh();
refresh(); },
}, onAreaSelecting: (cell: Cell) => {
onAreaSelecting: (cell: Cell) => { const canvas = worksheetCanvas.current;
if (!canvas) {
return;
}
const { row, column } = cell;
model.onAreaSelecting(row, column);
canvas.renderSheet();
refresh();
},
onAreaSelected: () => {
const styles = workbookState.getCopyStyles();
if (styles?.length) {
model.onPasteStyles(styles);
const canvas = worksheetCanvas.current; const canvas = worksheetCanvas.current;
if (!canvas) { if (!canvas) {
return; return;
} }
const { row, column } = cell;
model.onAreaSelecting(row, column);
canvas.renderSheet(); canvas.renderSheet();
refresh(); }
}, workbookState.setCopyStyles(null);
onAreaSelected: () => { if (worksheetElement.current) {
const styles = workbookState.getCopyStyles(); worksheetElement.current.style.cursor = "auto";
if (styles?.length) { }
model.onPasteStyles(styles); refresh();
const canvas = worksheetCanvas.current; },
if (!canvas) { canvasElement,
return; worksheetElement,
} worksheetCanvas,
canvas.renderSheet(); });
}
workbookState.setCopyStyles(null);
if (worksheetElement.current) {
worksheetElement.current.style.cursor = "auto";
}
refresh();
},
onExtendToCell: (cell) => {
const canvas = worksheetCanvas.current;
if (!canvas) {
return;
}
const { row, column } = cell;
const {
range: [rowStart, columnStart, rowEnd, columnEnd],
} = model.getSelectedView();
// We are either extending by rows or by columns
// And we could be doing it in the positive direction (downwards or right)
// or the negative direction (upwards or left)
if (
row > rowEnd &&
((column <= columnEnd && column >= columnStart) ||
(column < columnStart && columnStart - column < row - rowEnd) ||
(column > columnEnd && column - columnEnd < row - rowEnd))
) {
// rows downwards
const area = {
type: AreaType.rowsDown,
rowStart: rowEnd + 1,
rowEnd: row,
columnStart,
columnEnd,
};
workbookState.setExtendToArea(area);
canvas.renderSheet();
} else if (
row < rowStart &&
((column <= columnEnd && column >= columnStart) ||
(column < columnStart && columnStart - column < rowStart - row) ||
(column > columnEnd && column - columnEnd < rowStart - row))
) {
// rows upwards
const area = {
type: AreaType.rowsUp,
rowStart: row,
rowEnd: rowStart,
columnStart,
columnEnd,
};
workbookState.setExtendToArea(area);
canvas.renderSheet();
} else if (
column > columnEnd &&
((row <= rowEnd && row >= rowStart) ||
(row < rowStart && rowStart - row < column - columnEnd) ||
(row > rowEnd && row - rowEnd < column - columnEnd))
) {
// columns right
const area = {
type: AreaType.columnsRight,
rowStart,
rowEnd,
columnStart: columnEnd + 1,
columnEnd: column,
};
workbookState.setExtendToArea(area);
canvas.renderSheet();
} else if (
column < columnStart &&
((row <= rowEnd && row >= rowStart) ||
(row < rowStart && rowStart - row < columnStart - column) ||
(row > rowEnd && row - rowEnd < columnStart - column))
) {
// columns left
const area = {
type: AreaType.columnsLeft,
rowStart,
rowEnd,
columnStart: column,
columnEnd: columnStart,
};
workbookState.setExtendToArea(area);
canvas.renderSheet();
}
},
onExtendToEnd: () => {
const canvas = worksheetCanvas.current;
if (!canvas) {
return;
}
const { sheet, range } = model.getSelectedView();
const extendedArea = workbookState.getExtendToArea();
if (!extendedArea) {
return;
}
const rowStart = Math.min(range[0], range[2]);
const height = Math.abs(range[2] - range[0]) + 1;
const width = Math.abs(range[3] - range[1]) + 1;
const columnStart = Math.min(range[1], range[3]);
const area = {
sheet,
row: rowStart,
column: columnStart,
width,
height,
};
switch (extendedArea.type) {
case AreaType.rowsDown:
model.autoFillRows(area, extendedArea.rowEnd);
break;
case AreaType.rowsUp: {
model.autoFillRows(area, extendedArea.rowStart);
break;
}
case AreaType.columnsRight: {
model.autoFillColumns(area, extendedArea.columnEnd);
break;
}
case AreaType.columnsLeft: {
model.autoFillColumns(area, extendedArea.columnStart);
break;
}
}
model.setSelectedRange(
Math.min(rowStart, extendedArea.rowStart),
Math.min(columnStart, extendedArea.columnStart),
Math.max(rowStart + height - 1, extendedArea.rowEnd),
Math.max(columnStart + width - 1, extendedArea.columnEnd),
);
workbookState.clearExtendToArea();
canvas.renderSheet();
},
canvasElement,
worksheetElement,
worksheetCanvas,
});
const onScroll = (): void => { const onScroll = (): void => {
if (!scrollElement.current || !worksheetCanvas.current) { if (!scrollElement.current || !worksheetCanvas.current) {
@@ -463,10 +330,6 @@ const Worksheet = forwardRef(
</EditorWrapper> </EditorWrapper>
<AreaOutline ref={areaOutline} /> <AreaOutline ref={areaOutline} />
<ExtendToOutline ref={extendToOutline} /> <ExtendToOutline ref={extendToOutline} />
<CellOutlineHandle
ref={cellOutlineHandle}
onPointerDown={onPointerHandleDown}
/>
<ColumnResizeGuide ref={columnResizeGuide} /> <ColumnResizeGuide ref={columnResizeGuide} />
<RowResizeGuide ref={rowResizeGuide} /> <RowResizeGuide ref={rowResizeGuide} />
<ColumnHeaders ref={columnHeaders} /> <ColumnHeaders ref={columnHeaders} />
@@ -640,15 +503,6 @@ const CellOutline = styled("div")`
display: flex; display: flex;
`; `;
const CellOutlineHandle = styled("div")`
position: absolute;
width: 5px;
height: 5px;
background: ${outlineColor};
cursor: crosshair;
border-radius: 1px;
`;
const ExtendToOutline = styled("div")` const ExtendToOutline = styled("div")`
position: absolute; position: absolute;
border: 1px dashed ${outlineColor}; border: 1px dashed ${outlineColor};

View File

@@ -20,8 +20,6 @@ interface PointerSettings {
onAllSheetSelected: () => void; onAllSheetSelected: () => void;
onAreaSelecting: (cell: Cell) => void; onAreaSelecting: (cell: Cell) => void;
onAreaSelected: () => void; onAreaSelected: () => void;
onExtendToCell: (cell: Cell) => void;
onExtendToEnd: () => void;
model: Model; model: Model;
workbookState: WorkbookState; workbookState: WorkbookState;
refresh: () => void; refresh: () => void;
@@ -31,12 +29,10 @@ interface PointerEvents {
onPointerDown: (event: PointerEvent) => void; onPointerDown: (event: PointerEvent) => void;
onPointerMove: (event: PointerEvent) => void; onPointerMove: (event: PointerEvent) => void;
onPointerUp: (event: PointerEvent) => void; onPointerUp: (event: PointerEvent) => void;
onPointerHandleDown: (event: PointerEvent) => void;
} }
const usePointer = (options: PointerSettings): PointerEvents => { const usePointer = (options: PointerSettings): PointerEvents => {
const isSelecting = useRef(false); const isSelecting = useRef(false);
const isExtending = useRef(false);
const isInsertingRef = useRef(false); const isInsertingRef = useRef(false);
const onPointerMove = useCallback( const onPointerMove = useCallback(
@@ -47,9 +43,7 @@ const usePointer = (options: PointerSettings): PointerEvents => {
return; return;
} }
if ( if (!(isSelecting.current || isInsertingRef.current)) {
!(isSelecting.current || isExtending.current || isInsertingRef.current)
) {
return; return;
} }
const { canvasElement, model, worksheetCanvas } = options; const { canvasElement, model, worksheetCanvas } = options;
@@ -70,8 +64,6 @@ const usePointer = (options: PointerSettings): PointerEvents => {
if (isSelecting.current) { if (isSelecting.current) {
options.onAreaSelecting(cell); options.onAreaSelecting(cell);
} else if (isExtending.current) {
options.onExtendToCell(cell);
} else if (isInsertingRef.current) { } else if (isInsertingRef.current) {
const { refresh, workbookState } = options; const { refresh, workbookState } = options;
const editingCell = workbookState.getEditingCell(); const editingCell = workbookState.getEditingCell();
@@ -103,11 +95,6 @@ const usePointer = (options: PointerSettings): PointerEvents => {
isSelecting.current = false; isSelecting.current = false;
worksheetElement.current?.releasePointerCapture(event.pointerId); worksheetElement.current?.releasePointerCapture(event.pointerId);
options.onAreaSelected(); options.onAreaSelected();
} else if (isExtending.current) {
const { worksheetElement } = options;
isExtending.current = false;
worksheetElement.current?.releasePointerCapture(event.pointerId);
options.onExtendToEnd();
} else if (isInsertingRef.current) { } else if (isInsertingRef.current) {
const { worksheetElement } = options; const { worksheetElement } = options;
isInsertingRef.current = false; isInsertingRef.current = false;
@@ -120,10 +107,14 @@ const usePointer = (options: PointerSettings): PointerEvents => {
const onPointerDown = useCallback( const onPointerDown = useCallback(
(event: PointerEvent) => { (event: PointerEvent) => {
const target = event.target as HTMLElement; const target = event.target as HTMLElement;
if (target !== null && target.className === "column-resize-handle") { if (target.className === "column-resize-handle") {
// we are resizing a column // we are resizing a column
return; return;
} }
if (target.className.includes("ironcalc-cell-handle")) {
// we are extending values
return;
}
let x = event.clientX; let x = event.clientX;
let y = event.clientY; let y = event.clientY;
const { const {
@@ -251,26 +242,10 @@ const usePointer = (options: PointerSettings): PointerEvents => {
[options], [options],
); );
const onPointerHandleDown = useCallback(
(event: PointerEvent) => {
const worksheetWrapper = options.worksheetElement.current;
// Silence the linter
if (!worksheetWrapper) {
return;
}
isExtending.current = true;
worksheetWrapper.setPointerCapture(event.pointerId);
event.stopPropagation();
event.preventDefault();
},
[options],
);
return { return {
onPointerDown, onPointerDown,
onPointerMove, onPointerMove,
onPointerUp, onPointerUp,
onPointerHandleDown,
}; };
}; };

View File

@@ -0,0 +1,211 @@
import { AreaType } from "../workbookState";
import { LAST_COLUMN, LAST_ROW, outlineColor } from "./constants";
import type WorksheetCanvas from "./worksheetCanvas";
export function attachOutlineHandle(
worksheet: WorksheetCanvas,
): HTMLDivElement {
// There is *always* a parent
const parent = worksheet.canvas.parentElement as HTMLDivElement;
// Remove any existing cell outline handles
for (const handle of parent.querySelectorAll(".ironcalc-cell-handle")) {
handle.remove();
}
// Create a new cell outline handle
const cellOutlineHandle = document.createElement("div");
cellOutlineHandle.className = "ironcalc-cell-handle";
parent.appendChild(cellOutlineHandle);
worksheet.cellOutlineHandle = cellOutlineHandle;
Object.assign(cellOutlineHandle.style, {
position: "absolute",
width: "5px",
height: "5px",
background: outlineColor,
cursor: "crosshair",
borderRadius: "1px",
});
// cell handle events
const resizeHandleMove = (event: MouseEvent): void => {
const canvasRect = worksheet.canvas.getBoundingClientRect();
const x = event.clientX - canvasRect.x;
const y = event.clientY - canvasRect.y;
const cell = worksheet.getCellByCoordinates(x, y);
if (!cell) {
return;
}
const { row, column } = cell;
const {
range: [rowStart, columnStart, rowEnd, columnEnd],
} = worksheet.model.getSelectedView();
// We are either extending by rows or by columns
// And we could be doing it in the positive direction (downwards or right)
// or the negative direction (upwards or left)
if (
row > rowEnd &&
((column <= columnEnd && column >= columnStart) ||
(column < columnStart && columnStart - column < row - rowEnd) ||
(column > columnEnd && column - columnEnd < row - rowEnd))
) {
// rows downwards
const area = {
type: AreaType.rowsDown,
rowStart: rowEnd + 1,
rowEnd: row,
columnStart,
columnEnd,
};
worksheet.workbookState.setExtendToArea(area);
worksheet.renderSheet();
} else if (
row < rowStart &&
((column <= columnEnd && column >= columnStart) ||
(column < columnStart && columnStart - column < rowStart - row) ||
(column > columnEnd && column - columnEnd < rowStart - row))
) {
// rows upwards
const area = {
type: AreaType.rowsUp,
rowStart: row,
rowEnd: rowStart,
columnStart,
columnEnd,
};
worksheet.workbookState.setExtendToArea(area);
worksheet.renderSheet();
} else if (
column > columnEnd &&
((row <= rowEnd && row >= rowStart) ||
(row < rowStart && rowStart - row < column - columnEnd) ||
(row > rowEnd && row - rowEnd < column - columnEnd))
) {
// columns right
const area = {
type: AreaType.columnsRight,
rowStart,
rowEnd,
columnStart: columnEnd + 1,
columnEnd: column,
};
worksheet.workbookState.setExtendToArea(area);
worksheet.renderSheet();
} else if (
column < columnStart &&
((row <= rowEnd && row >= rowStart) ||
(row < rowStart && rowStart - row < columnStart - column) ||
(row > rowEnd && row - rowEnd < columnStart - column))
) {
// columns left
const area = {
type: AreaType.columnsLeft,
rowStart,
rowEnd,
columnStart: column,
columnEnd: columnStart,
};
worksheet.workbookState.setExtendToArea(area);
worksheet.renderSheet();
}
};
const resizeHandleUp = (_event: MouseEvent): void => {
document.removeEventListener("pointermove", resizeHandleMove);
document.removeEventListener("pointerup", resizeHandleUp);
const { sheet, range } = worksheet.model.getSelectedView();
const extendedArea = worksheet.workbookState.getExtendToArea();
if (!extendedArea) {
return;
}
const rowStart = Math.min(range[0], range[2]);
const height = Math.abs(range[2] - range[0]) + 1;
const width = Math.abs(range[3] - range[1]) + 1;
const columnStart = Math.min(range[1], range[3]);
const area = {
sheet,
row: rowStart,
column: columnStart,
width,
height,
};
switch (extendedArea.type) {
case AreaType.rowsDown:
worksheet.model.autoFillRows(area, extendedArea.rowEnd);
break;
case AreaType.rowsUp: {
worksheet.model.autoFillRows(area, extendedArea.rowStart);
break;
}
case AreaType.columnsRight: {
worksheet.model.autoFillColumns(area, extendedArea.columnEnd);
break;
}
case AreaType.columnsLeft: {
worksheet.model.autoFillColumns(area, extendedArea.columnStart);
break;
}
}
worksheet.model.setSelectedRange(
Math.min(rowStart, extendedArea.rowStart),
Math.min(columnStart, extendedArea.columnStart),
Math.max(rowStart + height - 1, extendedArea.rowEnd),
Math.max(columnStart + width - 1, extendedArea.columnEnd),
);
worksheet.workbookState.clearExtendToArea();
worksheet.renderSheet();
};
cellOutlineHandle.addEventListener("pointerdown", () => {
document.addEventListener("pointermove", resizeHandleMove);
document.addEventListener("pointerup", resizeHandleUp);
});
cellOutlineHandle.addEventListener("dblclick", (event) => {
// On double-click, we will auto-fill the rows below the selected cell
const [sheet, row, column] = worksheet.model.getSelectedCell();
let lastUsedRow = row + 1;
let testColumn = column - 1;
// The "test column" is the column to the left of the selected cell or the next column if the left one is empty
if (
testColumn < 1 ||
worksheet.model.getFormattedCellValue(sheet, row, column - 1) === ""
) {
testColumn = column + 1;
if (
testColumn > LAST_COLUMN ||
worksheet.model.getFormattedCellValue(sheet, row, testColumn) === ""
) {
return;
}
}
// Find the last used row in the "test column"
for (let r = row + 1; r <= LAST_ROW; r += 1) {
if (worksheet.model.getFormattedCellValue(sheet, r, testColumn) === "") {
break;
}
lastUsedRow = r;
}
const area = {
sheet,
row: row,
column: column,
width: 1,
height: 1,
};
worksheet.model.autoFillRows(area, lastUsedRow);
event.stopPropagation();
worksheet.renderSheet();
});
return cellOutlineHandle;
}

View File

@@ -30,7 +30,6 @@ export interface CanvasSettings {
canvas: HTMLCanvasElement; canvas: HTMLCanvasElement;
cellOutline: HTMLDivElement; cellOutline: HTMLDivElement;
areaOutline: HTMLDivElement; areaOutline: HTMLDivElement;
cellOutlineHandle: HTMLDivElement;
extendToOutline: HTMLDivElement; extendToOutline: HTMLDivElement;
columnGuide: HTMLDivElement; columnGuide: HTMLDivElement;
rowGuide: HTMLDivElement; rowGuide: HTMLDivElement;
@@ -55,70 +54,6 @@ export const defaultCellFontFamily = fonts.regular;
export const headerFontFamily = fonts.regular; export const headerFontFamily = fonts.regular;
export const frozenSeparatorWidth = 3; export const frozenSeparatorWidth = 3;
// Get a 10% transparency of an hex color
function hexToRGBA10Percent(colorHex: string): string {
// Remove the leading hash (#) if present
const hex = colorHex.replace(/^#/, "");
// Parse the hex color
const red = Number.parseInt(hex.substring(0, 2), 16);
const green = Number.parseInt(hex.substring(2, 4), 16);
const blue = Number.parseInt(hex.substring(4, 6), 16);
// Set the alpha (opacity) to 0.1 (10%)
const alpha = 0.1;
// Return the RGBA color string
return `rgba(${red}, ${green}, ${blue}, ${alpha})`;
}
/**
* Splits the given text into multiple lines. If `wrapText` is true, it applies word-wrapping
* based on the specified canvas context, maximum width, and horizontal padding.
*
* - First, the text is split by newline characters so that explicit newlines are respected.
* - If wrapping is enabled, each line is further split into words and measured against the
* available width. Whenever adding an extra word would exceed
* this limit, a new line is started.
*
* @param text The text to split into lines.
* @param wrapText Whether to apply word-wrapping or just return text split by newlines.
* @param context The `CanvasRenderingContext2D` used for measuring text width.
* @param width The maximum width for each line.
* @returns An array of lines (strings), each fitting within the specified width if wrapping is enabled.
*/
function computeWrappedLines(
text: string,
wrapText: boolean,
context: CanvasRenderingContext2D,
width: number,
): string[] {
// Split the text into lines
const rawLines = text.split("\n");
if (!wrapText) {
// If there is no wrapping, return the raw lines
return rawLines;
}
const wrappedLines = [];
for (const line of rawLines) {
const words = line.split(" ");
let currentLine = words[0];
for (let i = 1; i < words.length; i += 1) {
const word = words[i];
const testLine = `${currentLine} ${word}`;
const textWidth = context.measureText(testLine).width;
if (textWidth < width) {
currentLine = testLine;
} else {
wrappedLines.push(currentLine);
currentLine = word;
}
}
wrappedLines.push(currentLine);
}
return wrappedLines;
}
export default class WorksheetCanvas { export default class WorksheetCanvas {
sheetWidth: number; sheetWidth: number;
@@ -171,7 +106,6 @@ export default class WorksheetCanvas {
this.refresh = options.refresh; this.refresh = options.refresh;
this.cellOutline = options.elements.cellOutline; this.cellOutline = options.elements.cellOutline;
this.cellOutlineHandle = options.elements.cellOutlineHandle;
this.areaOutline = options.elements.areaOutline; this.areaOutline = options.elements.areaOutline;
this.extendToOutline = options.elements.extendToOutline; this.extendToOutline = options.elements.extendToOutline;
this.rowGuide = options.elements.rowGuide; this.rowGuide = options.elements.rowGuide;
@@ -181,6 +115,7 @@ export default class WorksheetCanvas {
this.onColumnWidthChanges = options.onColumnWidthChanges; this.onColumnWidthChanges = options.onColumnWidthChanges;
this.onRowHeightChanges = options.onRowHeightChanges; this.onRowHeightChanges = options.onRowHeightChanges;
this.resetHeaders(); this.resetHeaders();
this.cellOutlineHandle = attachOutlineHandle(this);
} }
setScrollPosition(scrollPosition: { left: number; top: number }): void { setScrollPosition(scrollPosition: { left: number; top: number }): void {