UNPKG

react-konva-grid

Version:

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

1,069 lines 44.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const react_1 = __importStar(require("react")); const ReactKonvaCore_1 = require("react-konva/lib/ReactKonvaCore"); const helpers_1 = require("./helpers"); const Cell_1 = require("./Cell"); const utils_1 = require("./utils"); const tiny_invariant_1 = __importDefault(require("tiny-invariant")); const DEFAULT_ESTIMATED_ITEM_SIZE = 50; const defaultShadowSettings = { stroke: "#000", shadowColor: "black", shadowBlur: 5, shadowOpacity: 0.4, shadowOffsetX: 2, }; const defaultRowHeight = () => 20; const defaultColumnWidth = () => 60; const defaultSelectionRenderer = (props) => { return utils_1.createBox(Object.assign({ strokeWidth: 1, strokeBoxWidth: 0 }, props)); }; const RESET_SCROLL_EVENTS_DEBOUNCE_INTERVAL = 150; /** * Grid component using React Konva * @param props */ const Grid = react_1.memo(react_1.forwardRef((props, forwardedRef) => { const { width: containerWidth = 800, height: containerHeight = 600, estimatedColumnWidth, estimatedRowHeight, rowHeight = defaultRowHeight, columnWidth = defaultColumnWidth, rowCount = 0, columnCount = 0, scrollbarSize = 13, onScroll, showScrollbar = true, selectionBackgroundColor = "rgb(14, 101, 235, 0.1)", selectionBorderColor = "#1a73e8", selectionStrokeWidth = 1, activeCellStrokeWidth = 2, activeCell, selections = [], frozenRows = 0, frozenColumns = 0, itemRenderer = Cell_1.CellRenderer, mergedCells = [], snap = false, scrollThrottleTimeout = 100, onViewChange, selectionRenderer = defaultSelectionRenderer, onBeforeRenderRow, showFrozenShadow = true, shadowSettings = defaultShadowSettings, borderStyles = [], children, stageProps, wrapper = (children) => children, cellAreas = [] } = props, rest = __rest(props, ["width", "height", "estimatedColumnWidth", "estimatedRowHeight", "rowHeight", "columnWidth", "rowCount", "columnCount", "scrollbarSize", "onScroll", "showScrollbar", "selectionBackgroundColor", "selectionBorderColor", "selectionStrokeWidth", "activeCellStrokeWidth", "activeCell", "selections", "frozenRows", "frozenColumns", "itemRenderer", "mergedCells", "snap", "scrollThrottleTimeout", "onViewChange", "selectionRenderer", "onBeforeRenderRow", "showFrozenShadow", "shadowSettings", "borderStyles", "children", "stageProps", "wrapper", "cellAreas"]); tiny_invariant_1.default(!(children && typeof children !== "function"), "Children should be a function"); /* Expose some methods in ref */ react_1.useImperativeHandle(forwardedRef, () => { return { scrollTo, scrollToItem, stage: stageRef.current, container: containerRef.current, resetAfterIndices, getScrollPosition, isMergedCell, getCellBounds, getCellCoordsFromOffset, getCellOffsetFromCoords, focus: () => { var _a; return (_a = containerRef.current) === null || _a === void 0 ? void 0 : _a.focus(); }, resizeColumns, resizeRows, getViewPort, }; }); const instanceProps = react_1.useRef({ columnMetadataMap: {}, rowMetadataMap: {}, lastMeasuredColumnIndex: -1, lastMeasuredRowIndex: -1, estimatedColumnWidth: estimatedColumnWidth || DEFAULT_ESTIMATED_ITEM_SIZE, estimatedRowHeight: estimatedRowHeight || DEFAULT_ESTIMATED_ITEM_SIZE, recalcColumnIndices: [], recalcRowIndices: [], }); const stageRef = react_1.useRef(null); const containerRef = react_1.useRef(null); const verticalScrollRef = react_1.useRef(null); const wheelingRef = react_1.useRef(null); const horizontalScrollRef = react_1.useRef(null); const [_, forceRender] = react_1.useReducer((s) => s + 1, 0); const [scrollState, setScrollState] = react_1.useState({ scrollTop: 0, scrollLeft: 0, isScrolling: false, }); const { scrollTop, scrollLeft, isScrolling } = scrollState; /** * Snaps vertical scrollbar to the next/prev visible row */ const snapToRowFn = react_1.useCallback(({ rowStartIndex, rowCount, deltaY }) => { if (!verticalScrollRef.current) return; if (deltaY !== 0) { const nextRowIndex = deltaY < 0 ? // User is scrolling up Math.max(0, rowStartIndex) : Math.min(rowStartIndex + frozenRows, rowCount - 1); /* TODO: Fix bug when frozenRowHeight > minRow height, which causes rowStartIndex to be 1 even after a scroll */ const rowHeight = helpers_1.getRowHeight(nextRowIndex, instanceProps.current); verticalScrollRef.current.scrollTop += (deltaY < 0 ? -1 : 1) * rowHeight; } }, []); /** * Snaps horizontal scrollbar to the next/prev visible column */ const snapToColumnFn = react_1.useCallback(({ columnStartIndex, columnCount, deltaX, frozenColumns, }) => { if (!horizontalScrollRef.current) return; if (deltaX !== 0) { const nextColumnIndex = deltaX < 0 ? Math.max(0, columnStartIndex) : Math.min(columnStartIndex + frozenColumns, columnCount - 1); const columnWidth = helpers_1.getColumnWidth(nextColumnIndex, instanceProps.current); horizontalScrollRef.current.scrollLeft += (deltaX < 0 ? -1 : 1) * columnWidth; } }, []); const snapToRowThrottler = react_1.useRef(helpers_1.throttle(snapToRowFn, scrollThrottleTimeout)); const snapToColumnThrottler = react_1.useRef(helpers_1.throttle(snapToColumnFn, scrollThrottleTimeout)); /** * Imperatively get the current scroll position */ const getScrollPosition = react_1.useCallback(() => { return { scrollTop, scrollLeft, }; }, [scrollTop, scrollLeft]); /* Redraw grid imperatively */ const resetAfterIndices = react_1.useCallback(({ columnIndex, rowIndex }, shouldForceUpdate = true) => { if (typeof columnIndex === "number") { instanceProps.current.lastMeasuredColumnIndex = Math.min(instanceProps.current.lastMeasuredColumnIndex, columnIndex - 1); } if (typeof rowIndex === "number") { instanceProps.current.lastMeasuredRowIndex = Math.min(instanceProps.current.lastMeasuredRowIndex, rowIndex - 1); } if (shouldForceUpdate) forceRender(); }, []); /** * Create a map of merged cells * [rowIndex, columnindex] => [parentRowIndex, parentColumnIndex] */ const mergedCellMap = react_1.useMemo(() => { const mergedCellMap = new Map(); for (let i = 0; i < mergedCells.length; i++) { const bounds = mergedCells[i]; const { top, left } = bounds; for (const cell of helpers_1.getBoundedCells(bounds)) { mergedCellMap.set(cell, bounds); } } return mergedCellMap; }, [mergedCells]); /* Check if a cell is part of a merged cell */ const isMergedCell = react_1.useCallback(({ rowIndex, columnIndex }) => { return mergedCellMap.has(helpers_1.cellIndentifier(rowIndex, columnIndex)); }, [mergedCellMap]); /* Get top, left bounds of a cell */ const getCellBounds = react_1.useCallback(({ rowIndex, columnIndex }) => { const isMerged = isMergedCell({ rowIndex, columnIndex }); if (isMerged) return mergedCellMap.get(helpers_1.cellIndentifier(rowIndex, columnIndex)); return { top: rowIndex, left: columnIndex, right: columnIndex, bottom: rowIndex, }; }, [mergedCellMap]); const rowStartIndex = helpers_1.getRowStartIndexForOffset({ rowHeight, columnWidth, rowCount, columnCount, instanceProps: instanceProps.current, offset: scrollTop, }); const rowStopIndex = helpers_1.getRowStopIndexForStartIndex({ startIndex: rowStartIndex, rowCount, rowHeight, columnWidth, scrollTop, containerHeight, instanceProps: instanceProps.current, }); const columnStartIndex = helpers_1.getColumnStartIndexForOffset({ rowHeight, columnWidth, rowCount, columnCount, instanceProps: instanceProps.current, offset: scrollLeft, }); const columnStopIndex = helpers_1.getColumnStopIndexForStartIndex({ startIndex: columnStartIndex, columnCount, rowHeight, columnWidth, scrollLeft, containerWidth, instanceProps: instanceProps.current, }); const estimatedTotalHeight = helpers_1.getEstimatedTotalHeight(rowCount, instanceProps.current); const estimatedTotalWidth = helpers_1.getEstimatedTotalWidth(columnCount, instanceProps.current); /* Find frozen column boundary */ const isWithinFrozenColumnBoundary = (x) => { return (frozenColumns > 0 && x < helpers_1.getColumnOffset({ index: frozenColumns, rowHeight, columnWidth, instanceProps: instanceProps.current, })); }; /* Find frozen row boundary */ const isWithinFrozenRowBoundary = (y) => { return (frozenRows > 0 && y < helpers_1.getRowOffset({ index: frozenRows, rowHeight, columnWidth, instanceProps: instanceProps.current, })); }; /** * Get cell cordinates from current mouse x/y positions */ const getCellCoordsFromOffset = react_1.useCallback((x, y) => { const rowIndex = helpers_1.getRowStartIndexForOffset({ rowHeight, columnWidth, rowCount, columnCount, instanceProps: instanceProps.current, offset: isWithinFrozenRowBoundary(y) ? y : y + scrollTop, }); const columnIndex = helpers_1.getColumnStartIndexForOffset({ rowHeight, columnWidth, rowCount, columnCount, instanceProps: instanceProps.current, offset: isWithinFrozenColumnBoundary(x) ? x : x + scrollLeft, }); /* To be compatible with merged cells */ const bounds = getCellBounds({ rowIndex, columnIndex }); return { rowIndex: bounds.top, columnIndex: bounds.left }; }, [scrollLeft, scrollTop, rowCount, columnCount]); /** * Get cell offset position from rowIndex, columnIndex */ const getCellOffsetFromCoords = react_1.useCallback(({ rowIndex, columnIndex }) => { const width = helpers_1.getColumnWidth(columnIndex, instanceProps.current); const x = helpers_1.getColumnOffset({ index: columnIndex, rowHeight, columnWidth, instanceProps: instanceProps.current, }); const height = helpers_1.getRowHeight(rowIndex, instanceProps.current); const y = helpers_1.getRowOffset({ index: rowIndex, rowHeight, columnWidth, instanceProps: instanceProps.current, }); return { x, y, width, height, }; }, []); /** * Resize one or more columns */ const resizeColumns = react_1.useCallback((indices) => { const leftMost = Math.min(...indices); resetAfterIndices({ columnIndex: leftMost }, false); instanceProps.current.recalcColumnIndices = indices; forceRender(); }, []); /** * Resize one or more rows */ const resizeRows = react_1.useCallback((indices) => { const topMost = Math.min(...indices); resetAfterIndices({ rowIndex: topMost }, false); instanceProps.current.recalcRowIndices = indices; forceRender(); }, []); /* Always if the viewport changes */ react_1.useEffect(() => { if (instanceProps.current.recalcColumnIndices.length) { instanceProps.current.recalcColumnIndices.length = 0; } if (instanceProps.current.recalcRowIndices.length) { instanceProps.current.recalcRowIndices.length = 0; } }, [rowStopIndex, columnStopIndex]); /* Get current view port of the grid */ const getViewPort = react_1.useCallback(() => { return { rowStartIndex, rowStopIndex, columnStartIndex, columnStopIndex, }; }, [rowStartIndex, rowStopIndex, columnStartIndex, columnStopIndex]); /** * When the grid is scrolling, * 1. Stage does not listen to any mouse events * 2. Div container does not listen to pointer events */ const resetIsScrollingTimeoutID = react_1.useRef(null); const resetIsScrollingDebounced = react_1.useCallback(() => { if (resetIsScrollingTimeoutID.current !== null) { helpers_1.cancelTimeout(resetIsScrollingTimeoutID.current); } resetIsScrollingTimeoutID.current = helpers_1.requestTimeout(resetIsScrolling, RESET_SCROLL_EVENTS_DEBOUNCE_INTERVAL); }, []); /* Reset isScrolling */ const resetIsScrolling = react_1.useCallback(() => { resetIsScrollingTimeoutID.current = null; setScrollState((prev) => { return Object.assign(Object.assign({}, prev), { isScrolling: false }); }); }, []); /* Handle vertical scroll */ const handleScroll = react_1.useCallback((e) => { const { scrollTop } = e.target; setScrollState((prev) => (Object.assign(Object.assign({}, prev), { isScrolling: true, scrollTop }))); /* Scroll callbacks */ onScroll && onScroll({ scrollTop, scrollLeft }); /* Reset isScrolling if required */ resetIsScrollingDebounced(); }, [scrollLeft]); /* Handle horizontal scroll */ const handleScrollLeft = react_1.useCallback((e) => { const { scrollLeft } = e.target; setScrollState((prev) => (Object.assign(Object.assign({}, prev), { isScrolling: true, scrollLeft }))); /* Scroll callbacks */ onScroll && onScroll({ scrollLeft, scrollTop }); /* Reset isScrolling if required */ resetIsScrollingDebounced(); }, [scrollTop]); /* Scroll based on left, top position */ const scrollTo = react_1.useCallback(({ scrollTop, scrollLeft }) => { /* If scrollbar is visible, lets update it which triggers a state change */ if (showScrollbar) { if (horizontalScrollRef.current && scrollLeft !== void 0) horizontalScrollRef.current.scrollLeft = scrollLeft; if (verticalScrollRef.current && scrollTop !== void 0) verticalScrollRef.current.scrollTop = scrollTop; } else { setScrollState((prev) => { return Object.assign(Object.assign({}, prev), { scrollLeft: scrollLeft == void 0 ? prev.scrollLeft : scrollLeft, scrollTop: scrollTop == void 0 ? prev.scrollTop : scrollTop }); }); } }, [showScrollbar]); const scrollToItem = react_1.useCallback(({ rowIndex, columnIndex }, align = helpers_1.Align.smart) => { const frozenColumnOffset = helpers_1.getColumnOffset({ index: frozenColumns, rowHeight, columnWidth, instanceProps: instanceProps.current, }); const newScrollLeft = columnIndex !== void 0 ? helpers_1.getOffsetForColumnAndAlignment({ index: columnIndex, containerHeight, containerWidth, columnCount, columnWidth, rowCount, rowHeight, scrollOffset: scrollLeft, instanceProps: instanceProps.current, scrollbarSize, frozenOffset: frozenColumnOffset, align, }) : void 0; const frozenRowOffset = helpers_1.getRowOffset({ index: frozenRows, rowHeight, columnWidth, instanceProps: instanceProps.current, }); const newScrollTop = rowIndex !== void 0 ? helpers_1.getOffsetForRowAndAlignment({ index: rowIndex, containerHeight, containerWidth, columnCount, columnWidth, rowCount, rowHeight, scrollOffset: scrollTop, instanceProps: instanceProps.current, scrollbarSize, frozenOffset: frozenRowOffset, align, }) : void 0; /* Scroll in the next frame, Useful when user wants to jump from 1st column to last */ window.requestAnimationFrame(() => { scrollTo({ scrollLeft: newScrollLeft, scrollTop: newScrollTop, }); }); }, [ containerHeight, containerWidth, rowCount, columnCount, scrollbarSize, scrollLeft, scrollTop, frozenRows, frozenColumns, ]); /** * Fired when user tries to scroll the canvas */ const handleWheel = react_1.useCallback((event) => { var _a, _b; const { deltaX, deltaY, deltaMode } = event.nativeEvent; /* If snaps are active */ if (snap) { snapToRowThrottler.current({ deltaY, rowStartIndex, rowCount, frozenRows, }); snapToColumnThrottler.current({ deltaX, columnStartIndex, columnCount, frozenColumns, }); return; } /* Scroll natively */ if (wheelingRef.current) return; let dx = deltaX; let dy = deltaY; if (deltaMode === 1) { dy = dy * scrollbarSize; } if (!horizontalScrollRef.current || !verticalScrollRef.current) return; const x = (_a = horizontalScrollRef.current) === null || _a === void 0 ? void 0 : _a.scrollLeft; const y = (_b = verticalScrollRef.current) === null || _b === void 0 ? void 0 : _b.scrollTop; wheelingRef.current = window.requestAnimationFrame(() => { wheelingRef.current = null; if (horizontalScrollRef.current) horizontalScrollRef.current.scrollLeft = x + dx; if (verticalScrollRef.current) verticalScrollRef.current.scrollTop = y + dy; }); }, [ rowStartIndex, columnStartIndex, rowCount, columnCount, snap, frozenColumns, frozenRows, ]); /* Callback when visible rows or columns have changed */ react_1.useEffect(() => { onViewChange && onViewChange({ rowStartIndex, rowStopIndex, columnStartIndex, columnStopIndex, }); }, [rowStartIndex, rowStopIndex, columnStartIndex, columnStopIndex]); /* Draw all cells */ const cells = []; if (columnCount > 0 && rowCount) { for (let rowIndex = rowStartIndex; rowIndex <= rowStopIndex; rowIndex++) { /* Skip frozen rows */ if (rowIndex < frozenRows) { continue; } /** * Do any pre-processing of the row before being renderered. * Useful for `react-table` to call `prepareRow(row)` */ onBeforeRenderRow && onBeforeRenderRow(rowIndex); for (let columnIndex = columnStartIndex; columnIndex <= columnStopIndex; columnIndex++) { /* Skip frozen columns and merged cells */ if (columnIndex < frozenColumns || isMergedCell({ rowIndex, columnIndex })) { continue; } const width = helpers_1.getColumnWidth(columnIndex, instanceProps.current); const x = helpers_1.getColumnOffset({ index: columnIndex, rowHeight, columnWidth, instanceProps: instanceProps.current, }); const height = helpers_1.getRowHeight(rowIndex, instanceProps.current); const y = helpers_1.getRowOffset({ index: rowIndex, rowHeight, columnWidth, instanceProps: instanceProps.current, }); cells.push(itemRenderer({ x, y, width, height, rowIndex, columnIndex, key: helpers_1.itemKey({ rowIndex, columnIndex }), })); } } } /** * Extend certain cells. * Mimics google sheets functionality where * oevrflowed cell content can cover adjacent cells */ const ranges = []; for (const { rowIndex, columnIndex, toColumnIndex } of cellAreas) { /* Skip merged cells, Merged cell cannot be extended */ if (isMergedCell({ rowIndex, columnIndex })) { continue; } const x = helpers_1.getColumnOffset({ index: columnIndex, rowHeight, columnWidth, instanceProps: instanceProps.current, }); const height = helpers_1.getRowHeight(rowIndex, instanceProps.current); const y = helpers_1.getRowOffset({ index: rowIndex, rowHeight, columnWidth, instanceProps: instanceProps.current, }); const { x: offsetX = 0 } = getCellOffsetFromCoords({ rowIndex, columnIndex: toColumnIndex + 1, }); ranges.push(itemRenderer({ x, y, width: offsetX - x, height, rowIndex, columnIndex, key: `range:${helpers_1.itemKey({ rowIndex, columnIndex })}`, })); } /* Draw merged cells */ const mergedCellAreas = []; const frozenColumnMergedCellAreas = []; const frozenRowMergedCellAreas = []; const frozenIntersectionMergedCells = []; for (let i = 0; i < mergedCells.length; i++) { const { top: rowIndex, left: columnIndex, right, bottom } = mergedCells[i]; const isLeftBoundFrozen = columnIndex < frozenColumns; const isTopBoundFrozen = rowIndex < frozenRows; const isIntersectionFrozen = rowIndex < frozenRows && columnIndex < frozenColumns; const x = helpers_1.getColumnOffset({ index: columnIndex, rowHeight, columnWidth, instanceProps: instanceProps.current, }); const y = helpers_1.getRowOffset({ index: rowIndex, rowHeight, columnWidth, instanceProps: instanceProps.current, }); const width = helpers_1.getColumnOffset({ index: right + 1, rowHeight, columnWidth, instanceProps: instanceProps.current, }) - x; const height = helpers_1.getRowOffset({ index: bottom + 1, rowHeight, columnWidth, instanceProps: instanceProps.current, }) - y; const cellRenderer = itemRenderer({ x, y, width, height, rowIndex, columnIndex, key: helpers_1.itemKey({ rowIndex, columnIndex }), }); if (isLeftBoundFrozen) { frozenColumnMergedCellAreas.push(cellRenderer); } if (isTopBoundFrozen) { frozenRowMergedCellAreas.push(cellRenderer); } if (isIntersectionFrozen) frozenIntersectionMergedCells.push(cellRenderer); mergedCellAreas.push(cellRenderer); } /* Draw frozen rows */ const frozenRowCells = []; for (let rowIndex = 0; rowIndex < Math.min(columnStopIndex, frozenRows); rowIndex++) { /** * Do any pre-processing of the row before being renderered. * Useful for `react-table` to call `prepareRow(row)` */ onBeforeRenderRow && onBeforeRenderRow(rowIndex); for (let columnIndex = columnStartIndex; columnIndex <= columnStopIndex; columnIndex++) { /* Skip merged cells columns */ if (isMergedCell({ rowIndex, columnIndex })) { continue; } const width = helpers_1.getColumnWidth(columnIndex, instanceProps.current); const x = helpers_1.getColumnOffset({ index: columnIndex, rowHeight, columnWidth, instanceProps: instanceProps.current, }); const height = helpers_1.getRowHeight(rowIndex, instanceProps.current); const y = helpers_1.getRowOffset({ index: rowIndex, rowHeight, columnWidth, instanceProps: instanceProps.current, }); frozenRowCells.push(itemRenderer({ x, y, width, height, rowIndex, columnIndex, key: helpers_1.itemKey({ rowIndex, columnIndex }), })); } } /* Draw frozen columns */ const frozenColumnCells = []; for (let rowIndex = rowStartIndex; rowIndex <= rowStopIndex; rowIndex++) { /** * Do any pre-processing of the row before being renderered. * Useful for `react-table` to call `prepareRow(row)` */ onBeforeRenderRow && onBeforeRenderRow(rowIndex); for (let columnIndex = 0; columnIndex < Math.min(columnStopIndex, frozenColumns); columnIndex++) { /* Skip merged cells columns */ if (isMergedCell({ rowIndex, columnIndex })) { continue; } const width = helpers_1.getColumnWidth(columnIndex, instanceProps.current); const x = helpers_1.getColumnOffset({ index: columnIndex, rowHeight, columnWidth, instanceProps: instanceProps.current, }); const height = helpers_1.getRowHeight(rowIndex, instanceProps.current); const y = helpers_1.getRowOffset({ index: rowIndex, rowHeight, columnWidth, instanceProps: instanceProps.current, }); frozenColumnCells.push(itemRenderer({ x, y, width, height, rowIndex, columnIndex, key: helpers_1.itemKey({ rowIndex, columnIndex }), })); } } /** * Frozen column shadow */ const frozenColumnShadow = react_1.useMemo(() => { if (showFrozenShadow == false || frozenColumns === 0 || scrollLeft === 0) return null; const frozenColumnLineX = helpers_1.getColumnOffset({ index: frozenColumns, rowHeight, columnWidth, instanceProps: instanceProps.current, }); return (react_1.default.createElement(ReactKonvaCore_1.Line, Object.assign({ points: [frozenColumnLineX, 0, frozenColumnLineX, containerHeight], offsetX: 1 }, shadowSettings))); }, [ shadowSettings, showFrozenShadow, frozenColumns, containerHeight, scrollLeft, ]); /** * Frozen row shadow */ const frozenRowShadow = react_1.useMemo(() => { if (showFrozenShadow === false || frozenRows === 0 || scrollTop === 0) return null; const frozenRowLineY = helpers_1.getRowOffset({ index: frozenRows, rowHeight, columnWidth, instanceProps: instanceProps.current, }); return (react_1.default.createElement(ReactKonvaCore_1.Line, Object.assign({ points: [0, frozenRowLineY, containerWidth, frozenRowLineY], offsetY: 1 }, shadowSettings))); }, [ shadowSettings, showFrozenShadow, frozenRows, containerWidth, scrollTop, ]); /* Draw frozen intersection cells */ const frozenIntersectionCells = []; for (let rowIndex = 0; rowIndex < Math.min(rowStopIndex, frozenRows); rowIndex++) { /** * Do any pre-processing of the row before being renderered. * Useful for `react-table` to call `prepareRow(row)` */ onBeforeRenderRow && onBeforeRenderRow(rowIndex); for (let columnIndex = 0; columnIndex < Math.min(columnStopIndex, frozenColumns); columnIndex++) { /* Skip merged cells columns */ if (isMergedCell({ rowIndex, columnIndex })) { continue; } const width = helpers_1.getColumnWidth(columnIndex, instanceProps.current); const x = helpers_1.getColumnOffset({ index: columnIndex, rowHeight, columnWidth, instanceProps: instanceProps.current, }); const height = helpers_1.getRowHeight(rowIndex, instanceProps.current); const y = helpers_1.getRowOffset({ index: rowIndex, rowHeight, columnWidth, instanceProps: instanceProps.current, }); frozenIntersectionCells.push(itemRenderer({ x, y, width, height, rowIndex, columnIndex, key: helpers_1.itemKey({ rowIndex, columnIndex }), })); } } /** * Convert selections to area * Removed useMemo as changes to lastMeasureRowIndex, lastMeasuredColumnIndex, * does not trigger useMemo * Dependencies : [selections, rowStopIndex, columnStopIndex, instanceProps] */ const selectionAreas = []; const selectionAreasFrozenColumns = []; const selectionAreasFrozenRows = []; const selectionAreasIntersection = []; for (let i = 0; i < selections.length; i++) { const { bounds, inProgress } = selections[i]; const { top, left, right, bottom } = bounds; const selectionBounds = { x: 0, y: 0, width: 0, height: 0 }; const actualBottom = Math.min(rowStopIndex, bottom); const actualRight = Math.min(columnStopIndex, right); const isLeftBoundFrozen = left < frozenColumns; const isTopBoundFrozen = top < frozenRows; const isIntersectionFrozen = top < frozenRows && left < frozenColumns; const styles = { stroke: inProgress ? "transparent" : selectionBorderColor, fill: selectionBackgroundColor, }; selectionBounds.y = helpers_1.getRowOffset({ index: top, rowHeight, columnWidth, instanceProps: instanceProps.current, }); selectionBounds.height = helpers_1.getRowOffset({ index: actualBottom, rowHeight, columnWidth, instanceProps: instanceProps.current, }) - selectionBounds.y + helpers_1.getRowHeight(actualBottom, instanceProps.current); selectionBounds.x = helpers_1.getColumnOffset({ index: left, rowHeight, columnWidth, instanceProps: instanceProps.current, }); selectionBounds.width = helpers_1.getColumnOffset({ index: actualRight, rowHeight, columnWidth, instanceProps: instanceProps.current, }) - selectionBounds.x + helpers_1.getColumnWidth(actualRight, instanceProps.current); if (isLeftBoundFrozen) { const frozenColumnSelectionWidth = Math.min(selectionBounds.width, helpers_1.getColumnOffset({ index: frozenColumns - left, rowHeight, columnWidth, instanceProps: instanceProps.current, })); selectionAreasFrozenColumns.push(selectionRenderer(Object.assign(Object.assign({}, styles), { key: i, x: selectionBounds.x, y: selectionBounds.y, width: frozenColumnSelectionWidth, height: selectionBounds.height, strokeRightWidth: frozenColumnSelectionWidth === selectionBounds.width ? selectionStrokeWidth : 0 }))); } if (isTopBoundFrozen) { const frozenRowSelectionHeight = Math.min(selectionBounds.height, helpers_1.getRowOffset({ index: frozenRows - top, rowHeight, columnWidth, instanceProps: instanceProps.current, })); selectionAreasFrozenRows.push(selectionRenderer(Object.assign(Object.assign({}, styles), { key: i, x: selectionBounds.x, y: selectionBounds.y, width: selectionBounds.width, height: frozenRowSelectionHeight, strokeBottomWidth: frozenRowSelectionHeight === selectionBounds.height ? selectionStrokeWidth : 0 }))); } if (isIntersectionFrozen) { const frozenIntersectionSelectionHeight = Math.min(selectionBounds.height, helpers_1.getRowOffset({ index: frozenRows - top, rowHeight, columnWidth, instanceProps: instanceProps.current, })); const frozenIntersectionSelectionWidth = Math.min(selectionBounds.width, helpers_1.getColumnOffset({ index: frozenColumns - left, rowHeight, columnWidth, instanceProps: instanceProps.current, })); selectionAreasIntersection.push(selectionRenderer(Object.assign(Object.assign({}, styles), { key: i, x: selectionBounds.x, y: selectionBounds.y, width: frozenIntersectionSelectionWidth, height: frozenIntersectionSelectionHeight, strokeBottomWidth: frozenIntersectionSelectionHeight === selectionBounds.height ? selectionStrokeWidth : 0, strokeRightWidth: frozenIntersectionSelectionWidth === selectionBounds.width ? selectionStrokeWidth : 0 }))); } selectionAreas.push(selectionRenderer(Object.assign(Object.assign({}, styles), { key: i, x: selectionBounds.x, y: selectionBounds.y, width: selectionBounds.width, height: selectionBounds.height }))); } /** * Renders active cell */ let activeCellSelection = null; let activeCellSelectionFrozenColumn = null; let activeCellSelectionFrozenRow = null; let activeCellSelectionFrozenIntersection = null; if (activeCell) { const bounds = getCellBounds(activeCell); const { top, left, right, bottom } = bounds; const actualBottom = Math.min(rowStopIndex, bottom); const actualRight = Math.min(columnStopIndex, right); const isInFrozenColumn = left < frozenColumns; const isInFrozenRow = top < frozenRows; const isInFrozenIntersection = isInFrozenRow && isInFrozenColumn; const y = helpers_1.getRowOffset({ index: top, rowHeight, columnWidth, instanceProps: instanceProps.current, }); const height = helpers_1.getRowOffset({ index: actualBottom, rowHeight, columnWidth, instanceProps: instanceProps.current, }) - y + helpers_1.getRowHeight(actualBottom, instanceProps.current); const x = helpers_1.getColumnOffset({ index: left, rowHeight, columnWidth, instanceProps: instanceProps.current, }); const width = helpers_1.getColumnOffset({ index: actualRight, rowHeight, columnWidth, instanceProps: instanceProps.current, }) - x + helpers_1.getColumnWidth(actualRight, instanceProps.current); const cell = selectionRenderer({ stroke: selectionBorderColor, strokeWidth: activeCellStrokeWidth, fill: "transparent", x: x, y: y, width: width, height: height, }); if (isInFrozenIntersection) { activeCellSelectionFrozenIntersection = cell; } else if (isInFrozenRow) { activeCellSelectionFrozenRow = cell; } else if (isInFrozenColumn) { activeCellSelectionFrozenColumn = cell; } else { activeCellSelection = cell; } } const borderStylesCells = react_1.useMemo(() => { const borderStyleCells = []; for (let i = 0; i < borderStyles.length; i++) { const { bounds, style } = borderStyles[i]; const { top, right, bottom, left } = bounds; const x = helpers_1.getColumnOffset({ index: left, rowHeight, columnWidth, instanceProps: instanceProps.current, }); const y = helpers_1.getRowOffset({ index: top, rowHeight, columnWidth, instanceProps: instanceProps.current, }); const width = helpers_1.getColumnOffset({ index: Math.min(columnCount - 1, right + 1), rowHeight, columnWidth, instanceProps: instanceProps.current, }) - x; const height = helpers_1.getRowOffset({ index: Math.min(rowCount - 1, bottom + 1), rowHeight, columnWidth, instanceProps: instanceProps.current, }) - y; borderStyleCells.push(utils_1.createBox(Object.assign({ x, y, width, height }, style))); } return borderStyleCells; }, [borderStyles, columnStopIndex, rowStopIndex, columnCount, rowCount]); /** * Prevents drawing hit region when scrolling */ const listenToEvents = !isScrolling; const stageChildren = (react_1.default.createElement(react_1.default.Fragment, null, react_1.default.createElement(ReactKonvaCore_1.Layer, null, react_1.default.createElement(ReactKonvaCore_1.Group, { offsetY: scrollTop, offsetX: scrollLeft, perfectDrawEnabled: false }, cells, mergedCellAreas, ranges)), react_1.default.createElement(ReactKonvaCore_1.Layer, null, react_1.default.createElement(ReactKonvaCore_1.Group, { offsetY: scrollTop, offsetX: scrollLeft, listening: false }, borderStylesCells, selectionAreas, activeCellSelection), frozenColumnShadow, frozenRowShadow, react_1.default.createElement(ReactKonvaCore_1.Group, { offsetY: 0, offsetX: scrollLeft }, frozenRowCells, frozenRowMergedCellAreas), react_1.default.createElement(ReactKonvaCore_1.Group, { offsetY: 0, offsetX: scrollLeft, listening: false }, selectionAreasFrozenRows, activeCellSelectionFrozenRow), react_1.default.createElement(ReactKonvaCore_1.Group, { offsetY: scrollTop, offsetX: 0 }, frozenColumnCells, frozenColumnMergedCellAreas), react_1.default.createElement(ReactKonvaCore_1.Group, { offsetY: scrollTop, offsetX: 0, listening: false }, selectionAreasFrozenColumns, activeCellSelectionFrozenColumn), react_1.default.createElement(ReactKonvaCore_1.Group, { offsetY: 0, offsetX: 0 }, frozenIntersectionCells, frozenIntersectionMergedCells), react_1.default.createElement(ReactKonvaCore_1.Group, { offsetY: 0, offsetX: 0, listening: false }, selectionAreasIntersection, activeCellSelectionFrozenIntersection)), children && typeof children === "function" ? children({ scrollLeft, scrollTop, }) : null)); return (react_1.default.createElement("div", { style: { position: "relative", width: containerWidth } }, react_1.default.createElement("div", Object.assign({ onWheel: handleWheel, tabIndex: 0, ref: containerRef }, rest), react_1.default.createElement(ReactKonvaCore_1.Stage, Object.assign({ width: containerWidth, height: containerHeight, ref: stageRef, listening: listenToEvents }, stageProps), wrapper(stageChildren))), showScrollbar ? (react_1.default.createElement(react_1.default.Fragment, null, react_1.default.createElement("div", { tabIndex: -1, style: { height: containerHeight, overflow: "scroll", position: "absolute", right: 0, top: 0, width: scrollbarSize, willChange: "transform", }, onScroll: handleScroll, ref: verticalScrollRef }, react_1.default.createElement("div", { style: { position: "absolute", height: estimatedTotalHeight, width: 1, } })), react_1.default.createElement("div", { tabIndex: -1, style: { overflow: "scroll", position: "absolute", bottom: 0, left: 0, width: containerWidth, height: scrollbarSize, willChange: "transform", }, onScroll: handleScrollLeft, ref: horizontalScrollRef }, react_1.default.createElement("div", { style: { position: "absolute", width: estimatedTotalWidth, height: 1, } })))) : null)); })); exports.default = Grid; //# sourceMappingURL=Grid.js.map