react-konva-grid
Version:
Declarative React Canvas Grid primitive for Data table, Pivot table, Excel Worksheets
203 lines (188 loc) • 5.08 kB
text/typescript
import React, { useCallback, useState, useRef, useEffect } from "react";
import { ViewPortProps, GridRef, CellInterface, ItemSizer } from "./../Grid";
import { debounce, AutoSizerCanvas } from "./../helpers";
import invariant from "tiny-invariant";
export interface IProps {
/**
* Used to access grid functions
*/
gridRef: React.MutableRefObject<GridRef>;
/**
* Value getter for a cell
*/
getValue: (cell: CellInterface) => any;
/**
* Visible rows when the grid is first visible, Since we do not know how many rows can fit
*/
initialVisibleRows?: number;
/**
* Restrict column width by this number
*/
minColumnWidth?: number;
/**
* Cell padding, used for width calculation
*/
cellSpacing?: number;
/**
* Scroll timeout
*/
timeout?: number;
/**
* Calculate width and resize the grid on scroll
*/
resizeOnScroll?: boolean;
/**
* Font used to calculate width
*/
font?: string;
/**
* Strategy used to calculate column width
* lazy = visible rows
* full = all rows
*
* columns are always lazy
*/
resizeStrategy?: ResizeStrategy;
/**
* No of rows in teh grid
*/
rowCount?: number;
/**
* Enable autoresize
*/
autoResize?: boolean;
}
export enum ResizeStrategy {
"lazy" = "lazy",
"full" = "full",
}
export interface AutoResizerResults {
/**
* Column width function consumed by the grid
*/
columnWidth?: ItemSizer;
/**
* Callback when viewport is changed
*/
onViewChange: (cells: ViewPortProps) => void;
/**
* Resize a column by index
*/
resizeColumn: (columnIndex: number) => void;
/**
* Text size getter
*/
getTextMetrics: (text: string) => TextMetrics | undefined;
}
/**
* Auto sizer hook
* @param param
*
* TODO
* Dynamically resize columns after user has scrolled down/view port changed ?
*/
const useAutoSizer = ({
gridRef,
getValue,
initialVisibleRows = 20,
cellSpacing = 10,
minColumnWidth = 60,
timeout = 300,
resizeStrategy = ResizeStrategy.lazy,
rowCount,
resizeOnScroll = true,
font = "12px Arial",
autoResize = true,
}: IProps): AutoResizerResults => {
invariant(
!(resizeStrategy === ResizeStrategy.full && rowCount === void 0),
"Row count should be specified if resize stragtegy is full"
);
const autoSizer = useRef(AutoSizerCanvas(font));
const [viewport, setViewport] = useState<ViewPortProps>({
rowStartIndex: 0,
rowStopIndex: 0,
columnStartIndex: 0,
columnStopIndex: 0,
});
const isMounted = useRef(false);
const debounceResizer = useRef(
debounce(
({ rowIndex, columnIndex }: CellInterface) =>
gridRef.current.resetAfterIndices({ rowIndex, columnIndex }),
timeout
)
);
useEffect(() => {
isMounted.current = true;
}, []);
/* Update any styles, fonts if necessary */
useEffect(() => {
autoSizer.current.setFont(font);
}, [font]);
const getTextMetrics = (text: string) => {
return autoSizer.current.measureText(text);
};
const getColumnWidth = useCallback(
(columnIndex: number) => {
const { rowStartIndex, rowStopIndex } = viewport;
const visibleRows =
resizeStrategy === ResizeStrategy.full
? (rowCount as number)
: rowStopIndex || initialVisibleRows;
let start = resizeStrategy === ResizeStrategy.full ? 0 : rowStartIndex;
let maxWidth = minColumnWidth;
while (start < visibleRows) {
const value =
getValue({
rowIndex: start,
columnIndex,
}) ?? null;
if (value !== null) {
const metrics = autoSizer.current.measureText(value);
if (metrics) {
const width = Math.ceil(metrics.width) + cellSpacing;
if (width > maxWidth) maxWidth = width;
}
}
start++;
}
return maxWidth;
},
[viewport, getValue, initialVisibleRows]
);
const handleResizeColumn = useCallback((columnIndex: number) => {
const width = getColumnWidth(columnIndex);
gridRef.current.resizeColumns([columnIndex]);
}, []);
const handleViewChange = useCallback(
(cells: ViewPortProps) => {
/* Update viewport cells */
setViewport(cells);
/* Check if viewport has changed */
if (
resizeStrategy === ResizeStrategy.full ||
!resizeOnScroll ||
(cells.rowStartIndex === viewport.rowStartIndex &&
cells.columnStartIndex === viewport.columnStartIndex)
)
return;
if (gridRef.current) {
/* During first mount, column width is calculated. Do not re-calculate */
if (!isMounted.current) return;
debounceResizer.current({
rowIndex: cells.rowStartIndex,
columnIndex: cells.columnStartIndex,
});
}
},
[resizeOnScroll, viewport, resizeStrategy]
);
return {
columnWidth: autoResize ? getColumnWidth : undefined,
resizeColumn: handleResizeColumn,
onViewChange: handleViewChange,
getTextMetrics,
};
};
export default useAutoSizer;