UNPKG

react-konva-grid

Version:

Declarative React Canvas Grid primitive for Data table, Pivot table, Excel Worksheets

157 lines (142 loc) 4.15 kB
import React, { useCallback, useState, useRef, useEffect } from "react"; import { ViewPortProps, GridRef, CellInterface, ItemSizer } from "./../Grid"; import { debounce } from "./../helpers"; import invariant from "tiny-invariant"; interface IProps { gridRef: React.MutableRefObject<GridRef>; getValue: (cell: CellInterface) => any; initialVisibleRows?: number; minColumnWidth?: number; cellSpacing?: number; timeout?: number; resizeOnScroll?: boolean; font?: string; resizeStrategy?: ResizeStrategy; rowCount?: number; } enum ResizeStrategy { "lazy" = "lazy", "full" = "full", } interface AutoResizerResults { columnWidth: ItemSizer; onViewChange: (cells: ViewPortProps) => void; } /** * 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", }: 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 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 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: getColumnWidth, onViewChange: handleViewChange, }; }; /* Canvas element */ const AutoSizerCanvas = (defaultFont: string) => { const canvas = <HTMLCanvasElement>document.createElement("canvas"); const context = canvas.getContext("2d"); const setFont = (font: string = defaultFont) => { if (context) context.font = font; }; const measureText = (text: string) => context?.measureText(text); /* Set font in constructor */ setFont(defaultFont); return { context, measureText, setFont, }; }; export default useAutoSizer;